fix #1772, add the old editor back in the foss app flavor

This commit is contained in:
tibbi 2020-03-05 23:17:48 +01:00
parent caa41cdd0f
commit 23b7e2fef1
20 changed files with 897 additions and 48 deletions

View file

@ -88,6 +88,7 @@ dependencies {
implementation 'com.google.vr:sdk-panowidget:1.180.0'
implementation 'com.google.vr:sdk-videowidget:1.180.0'
implementation 'org.apache.sanselan:sanselan:0.97-incubator'
implementation 'info.androidhive:imagefilters:1.0.7'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.caverock:androidsvg-aar:1.3'
implementation 'com.github.tibbi:gestureviews:512f929d82'

View file

@ -6,6 +6,7 @@ import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat
import android.graphics.Color
import android.graphics.Point
import android.media.ExifInterface
import android.net.Uri
import android.os.Build
@ -14,6 +15,16 @@ import android.provider.MediaStore
import android.view.Menu
import android.view.MenuItem
import android.widget.RelativeLayout
import androidx.recyclerview.widget.LinearLayoutManager
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.DecodeFormat
import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import com.simplemobiletools.commons.dialogs.ColorPickerDialog
import com.simplemobiletools.commons.dialogs.ConfirmationDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.PERMISSION_WRITE_STORAGE
@ -23,29 +34,62 @@ import com.simplemobiletools.commons.helpers.isNougatPlus
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.gallery.pro.BuildConfig
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.adapters.FiltersAdapter
import com.simplemobiletools.gallery.pro.dialogs.OtherAspectRatioDialog
import com.simplemobiletools.gallery.pro.dialogs.ResizeDialog
import com.simplemobiletools.gallery.pro.dialogs.SaveAsDialog
import com.simplemobiletools.gallery.pro.extensions.config
import com.simplemobiletools.gallery.pro.extensions.fixDateTaken
import com.simplemobiletools.gallery.pro.extensions.openEditor
import com.simplemobiletools.gallery.pro.helpers.*
import com.simplemobiletools.gallery.pro.models.FilterItem
import com.theartofdev.edmodo.cropper.CropImageView
import com.zomato.photofilters.FilterPack
import com.zomato.photofilters.imageprocessors.Filter
import kotlinx.android.synthetic.main.activity_edit.*
import kotlinx.android.synthetic.main.bottom_actions_aspect_ratio.*
import kotlinx.android.synthetic.main.bottom_editor_actions_filter.*
import kotlinx.android.synthetic.main.bottom_editor_crop_rotate_actions.*
import kotlinx.android.synthetic.main.bottom_editor_draw_actions.*
import kotlinx.android.synthetic.main.bottom_editor_primary_actions.*
import java.io.*
class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener {
companion object {
init {
System.loadLibrary("NativeImageProcessor")
}
}
private val TEMP_FOLDER_NAME = "images"
private val ASPECT_X = "aspectX"
private val ASPECT_Y = "aspectY"
private val CROP = "crop"
// constants for bottom primary action groups
private val PRIMARY_ACTION_NONE = 0
private val PRIMARY_ACTION_FILTER = 1
private val PRIMARY_ACTION_CROP_ROTATE = 2
private val PRIMARY_ACTION_DRAW = 3
private val CROP_ROTATE_NONE = 0
private val CROP_ROTATE_ASPECT_RATIO = 1
private lateinit var uri: Uri
private lateinit var saveUri: Uri
private var resizeWidth = 0
private var resizeHeight = 0
private var drawColor = 0
private var lastOtherAspectRatio: Pair<Float, Float>? = null
private var currPrimaryAction = PRIMARY_ACTION_NONE
private var currCropRotateAction = CROP_ROTATE_ASPECT_RATIO
private var currAspectRatio = ASPECT_RATIO_FREE
private var isCropIntent = false
private var isEditingWithThirdParty = false
private var isSharingBitmap = false
private var wasDrawCanvasPositioned = false
private var oldExif: ExifInterface? = null
private var filterInitialBitmap: Bitmap? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -65,6 +109,19 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
}
override fun onResume() {
super.onResume()
isEditingWithThirdParty = false
bottom_draw_width.setColors(config.textColor, getAdjustedPrimaryColor(), config.backgroundColor)
}
override fun onStop() {
super.onStop()
if (isEditingWithThirdParty) {
finish()
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.menu_editor, menu)
updateMenuItemColors(menu)
@ -74,6 +131,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.save_as -> saveImage()
R.id.edit -> editWith()
R.id.share -> shareImage()
else -> return super.onOptionsItemSelected(item)
}
@ -112,9 +170,14 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
else -> uri
}
isCropIntent = intent.extras?.get(CROP) == "true"
if (isCropIntent) {
bottom_editor_primary_actions.beGone()
(bottom_editor_crop_rotate_actions.layoutParams as RelativeLayout.LayoutParams).addRule(RelativeLayout.ALIGN_PARENT_BOTTOM, 1)
setupCropRotateActionButtons()
setupAspectRatioButtons()
}
loadDefaultImageView()
setupBottomActions()
if (config.lastEditorCropAspectRatio == ASPECT_RATIO_OTHER) {
if (config.lastEditorCropOtherAspectRatioX == 0f) {
@ -127,26 +190,115 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
lastOtherAspectRatio = Pair(config.lastEditorCropOtherAspectRatioX, config.lastEditorCropOtherAspectRatioY)
}
updateAspectRatio(config.lastEditorCropAspectRatio)
crop_image_view.guidelines = CropImageView.Guidelines.ON
bottom_aspect_ratios.beVisible()
}
private fun loadDefaultImageView() {
default_image_view.beVisible()
crop_image_view.beGone()
editor_draw_canvas.beGone()
val options = RequestOptions()
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
Glide.with(this)
.asBitmap()
.load(uri)
.apply(options)
.listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean) = false
override fun onResourceReady(bitmap: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
val currentFilter = getFiltersAdapter()?.getCurrentFilter()
if (filterInitialBitmap == null) {
loadCropImageView()
bottomCropRotateClicked()
}
if (filterInitialBitmap != null && currentFilter != null && currentFilter.filter.name != getString(R.string.none)) {
default_image_view.onGlobalLayout {
applyFilter(currentFilter)
}
} else {
filterInitialBitmap = bitmap
}
if (isCropIntent) {
bottom_primary_filter.beGone()
bottom_primary_draw.beGone()
}
return false
}
}).into(default_image_view)
}
private fun loadCropImageView() {
default_image_view.beGone()
editor_draw_canvas.beGone()
crop_image_view.apply {
beVisible()
setOnCropImageCompleteListener(this@EditActivity)
setImageUriAsync(uri)
guidelines = CropImageView.Guidelines.ON
if (shouldCropSquare()) {
updateAspectRatio(ASPECT_RATIO_ONE_ONE)
if (isCropIntent && shouldCropSquare()) {
currAspectRatio = ASPECT_RATIO_ONE_ONE
setFixedAspectRatio(true)
bottom_aspect_ratios.beGone()
bottom_aspect_ratio.beGone()
}
}
}
private fun loadDrawCanvas() {
default_image_view.beGone()
crop_image_view.beGone()
editor_draw_canvas.beVisible()
if (!wasDrawCanvasPositioned) {
wasDrawCanvasPositioned = true
editor_draw_canvas.onGlobalLayout {
ensureBackgroundThread {
fillCanvasBackground()
}
}
}
}
private fun fillCanvasBackground() {
val size = Point()
windowManager.defaultDisplay.getSize(size)
val options = RequestOptions()
.format(DecodeFormat.PREFER_ARGB_8888)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.fitCenter()
try {
val builder = Glide.with(applicationContext)
.asBitmap()
.load(uri)
.apply(options)
.into(editor_draw_canvas.width, editor_draw_canvas.height)
val bitmap = builder.get()
runOnUiThread {
editor_draw_canvas.apply {
updateBackgroundBitmap(bitmap)
layoutParams.width = bitmap.width
layoutParams.height = bitmap.height
y = (height - bitmap.height) / 2f
requestLayout()
}
}
} catch (e: Exception) {
showErrorToast(e)
}
}
@TargetApi(Build.VERSION_CODES.N)
private fun saveImage() {
var inputStream: InputStream? = null
@ -160,16 +312,68 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
inputStream?.close()
}
if (crop_image_view.isVisible()) {
crop_image_view.getCroppedImageAsync()
} else if (editor_draw_canvas.isVisible()) {
val bitmap = editor_draw_canvas.getBitmap()
if (saveUri.scheme == "file") {
SaveAsDialog(this, saveUri.path!!, true) {
saveBitmapToFile(bitmap, it, true)
}
} else if (saveUri.scheme == "content") {
val filePathGetter = getNewFilePath()
SaveAsDialog(this, filePathGetter.first, filePathGetter.second) {
saveBitmapToFile(bitmap, it, true)
}
}
} else {
val currentFilter = getFiltersAdapter()?.getCurrentFilter() ?: return
val filePathGetter = getNewFilePath()
SaveAsDialog(this, filePathGetter.first, filePathGetter.second) {
toast(R.string.saving)
// clean up everything to free as much memory as possible
default_image_view.setImageResource(0)
crop_image_view.setImageBitmap(null)
bottom_actions_filter_list.adapter = null
bottom_actions_filter_list.beGone()
ensureBackgroundThread {
try {
val originalBitmap = Glide.with(applicationContext).asBitmap().load(uri).submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get()
currentFilter.filter.processFilter(originalBitmap)
saveBitmapToFile(originalBitmap, it, false)
} catch (e: OutOfMemoryError) {
toast(R.string.out_of_memory_error)
}
}
}
}
}
private fun shareImage() {
ensureBackgroundThread {
when {
default_image_view.isVisible() -> {
val currentFilter = getFiltersAdapter()?.getCurrentFilter()
if (currentFilter == null) {
toast(R.string.unknown_error_occurred)
return@ensureBackgroundThread
}
val originalBitmap = Glide.with(applicationContext).asBitmap().load(uri).submit(Target.SIZE_ORIGINAL, Target.SIZE_ORIGINAL).get()
currentFilter.filter.processFilter(originalBitmap)
shareBitmap(originalBitmap)
}
crop_image_view.isVisible() -> {
isSharingBitmap = true
runOnUiThread {
crop_image_view.getCroppedImageAsync()
}
}
editor_draw_canvas.isVisible() -> shareBitmap(editor_draw_canvas.getBitmap())
}
}
}
private fun getTempImagePath(bitmap: Bitmap, callback: (path: String?) -> Unit) {
@ -212,11 +416,66 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
}
private fun getFiltersAdapter() = bottom_actions_filter_list.adapter as? FiltersAdapter
private fun setupBottomActions() {
setupPrimaryActionButtons()
setupCropRotateActionButtons()
setupAspectRatioButtons()
setupDrawButtons()
}
private fun setupPrimaryActionButtons() {
bottom_primary_filter.setOnClickListener {
bottomFilterClicked()
}
bottom_primary_crop_rotate.setOnClickListener {
bottomCropRotateClicked()
}
bottom_primary_draw.setOnClickListener {
bottomDrawClicked()
}
}
private fun bottomFilterClicked() {
currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_FILTER) {
PRIMARY_ACTION_NONE
} else {
PRIMARY_ACTION_FILTER
}
updatePrimaryActionButtons()
}
private fun bottomCropRotateClicked() {
currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) {
PRIMARY_ACTION_NONE
} else {
PRIMARY_ACTION_CROP_ROTATE
}
updatePrimaryActionButtons()
}
private fun bottomDrawClicked() {
currPrimaryAction = if (currPrimaryAction == PRIMARY_ACTION_DRAW) {
PRIMARY_ACTION_NONE
} else {
PRIMARY_ACTION_DRAW
}
updatePrimaryActionButtons()
}
private fun setupCropRotateActionButtons() {
bottom_rotate.setOnClickListener {
crop_image_view.rotateImage(90)
}
bottom_resize.beGoneIf(isCropIntent)
bottom_resize.setOnClickListener {
resizeImage()
}
bottom_flip_horizontally.setOnClickListener {
crop_image_view.flipImageHorizontally()
}
@ -224,6 +483,19 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
bottom_flip_vertically.setOnClickListener {
crop_image_view.flipImageVertically()
}
bottom_aspect_ratio.setOnClickListener {
currCropRotateAction = if (currCropRotateAction == CROP_ROTATE_ASPECT_RATIO) {
crop_image_view.guidelines = CropImageView.Guidelines.OFF
bottom_aspect_ratios.beGone()
CROP_ROTATE_NONE
} else {
crop_image_view.guidelines = CropImageView.Guidelines.ON
bottom_aspect_ratios.beVisible()
CROP_ROTATE_ASPECT_RATIO
}
updateCropRotateActionButtons()
}
}
private fun setupAspectRatioButtons() {
@ -255,6 +527,126 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
updateAspectRatioButtons()
}
private fun setupDrawButtons() {
updateDrawColor(config.lastEditorDrawColor)
bottom_draw_width.progress = config.lastEditorBrushSize
updateBrushSize(config.lastEditorBrushSize)
bottom_draw_color_clickable.setOnClickListener {
ColorPickerDialog(this, drawColor) { wasPositivePressed, color ->
if (wasPositivePressed) {
updateDrawColor(color)
}
}
}
bottom_draw_width.onSeekBarChangeListener {
config.lastEditorBrushSize = it
updateBrushSize(it)
}
bottom_draw_undo.setOnClickListener {
editor_draw_canvas.undo()
}
}
private fun updateBrushSize(percent: Int) {
editor_draw_canvas.updateBrushSize(percent)
val scale = Math.max(0.03f, percent / 100f)
bottom_draw_color.scaleX = scale
bottom_draw_color.scaleY = scale
}
private fun updatePrimaryActionButtons() {
if (crop_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) {
loadCropImageView()
} else if (default_image_view.isGone() && currPrimaryAction == PRIMARY_ACTION_FILTER) {
loadDefaultImageView()
} else if (editor_draw_canvas.isGone() && currPrimaryAction == PRIMARY_ACTION_DRAW) {
loadDrawCanvas()
}
arrayOf(bottom_primary_filter, bottom_primary_crop_rotate, bottom_primary_draw).forEach {
it.applyColorFilter(Color.WHITE)
}
val currentPrimaryActionButton = when (currPrimaryAction) {
PRIMARY_ACTION_FILTER -> bottom_primary_filter
PRIMARY_ACTION_CROP_ROTATE -> bottom_primary_crop_rotate
PRIMARY_ACTION_DRAW -> bottom_primary_draw
else -> null
}
currentPrimaryActionButton?.applyColorFilter(getAdjustedPrimaryColor())
bottom_editor_filter_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_FILTER)
bottom_editor_crop_rotate_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE)
bottom_editor_draw_actions.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_DRAW)
if (currPrimaryAction == PRIMARY_ACTION_FILTER && bottom_actions_filter_list.adapter == null) {
ensureBackgroundThread {
val thumbnailSize = resources.getDimension(R.dimen.bottom_filters_thumbnail_size).toInt()
val bitmap = try {
Glide.with(this)
.asBitmap()
.load(uri).listener(object : RequestListener<Bitmap> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Bitmap>?, isFirstResource: Boolean): Boolean {
showErrorToast(e.toString())
return false
}
override fun onResourceReady(resource: Bitmap?, model: Any?, target: Target<Bitmap>?, dataSource: DataSource?, isFirstResource: Boolean) = false
})
.submit(thumbnailSize, thumbnailSize)
.get()
} catch (e: GlideException) {
showErrorToast(e)
finish()
return@ensureBackgroundThread
}
runOnUiThread {
val filterThumbnailsManager = FilterThumbnailsManager()
filterThumbnailsManager.clearThumbs()
val noFilter = Filter(getString(R.string.none))
filterThumbnailsManager.addThumb(FilterItem(bitmap, noFilter))
FilterPack.getFilterPack(this).forEach {
val filterItem = FilterItem(bitmap, it)
filterThumbnailsManager.addThumb(filterItem)
}
val filterItems = filterThumbnailsManager.processThumbs()
val adapter = FiltersAdapter(applicationContext, filterItems) {
val layoutManager = bottom_actions_filter_list.layoutManager as LinearLayoutManager
applyFilter(filterItems[it])
if (it == layoutManager.findLastCompletelyVisibleItemPosition() || it == layoutManager.findLastVisibleItemPosition()) {
bottom_actions_filter_list.smoothScrollBy(thumbnailSize, 0)
} else if (it == layoutManager.findFirstCompletelyVisibleItemPosition() || it == layoutManager.findFirstVisibleItemPosition()) {
bottom_actions_filter_list.smoothScrollBy(-thumbnailSize, 0)
}
}
bottom_actions_filter_list.adapter = adapter
adapter.notifyDataSetChanged()
}
}
}
if (currPrimaryAction != PRIMARY_ACTION_CROP_ROTATE) {
bottom_aspect_ratios.beGone()
currCropRotateAction = CROP_ROTATE_NONE
}
updateCropRotateActionButtons()
}
private fun applyFilter(filterItem: FilterItem) {
val newBitmap = Bitmap.createBitmap(filterInitialBitmap!!)
default_image_view.setImageBitmap(filterItem.filter.processFilter(newBitmap))
}
private fun updateAspectRatio(aspectRatio: Int) {
currAspectRatio = aspectRatio
config.lastEditorCropAspectRatio = aspectRatio
@ -292,6 +684,40 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
currentAspectRatioButton.setTextColor(getAdjustedPrimaryColor())
}
private fun updateCropRotateActionButtons() {
arrayOf(bottom_aspect_ratio).forEach {
it.applyColorFilter(Color.WHITE)
}
val primaryActionView = when (currCropRotateAction) {
CROP_ROTATE_ASPECT_RATIO -> bottom_aspect_ratio
else -> null
}
primaryActionView?.applyColorFilter(getAdjustedPrimaryColor())
}
private fun updateDrawColor(color: Int) {
drawColor = color
bottom_draw_color.applyColorFilter(color)
config.lastEditorDrawColor = color
editor_draw_canvas.updateColor(color)
}
private fun resizeImage() {
val point = getAreaSize()
if (point == null) {
toast(R.string.unknown_error_occurred)
return
}
ResizeDialog(this, point) {
resizeWidth = it.x
resizeHeight = it.y
crop_image_view.getCroppedImageAsync()
}
}
private fun shouldCropSquare(): Boolean {
val extras = intent.extras
return if (extras != null && extras.containsKey(ASPECT_X) && extras.containsKey(ASPECT_Y)) {
@ -301,6 +727,16 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
}
private fun getAreaSize(): Point? {
val rect = crop_image_view.cropRect ?: return null
val rotation = crop_image_view.rotatedDegrees
return if (rotation == 0 || rotation == 180) {
Point(rect.width(), rect.height())
} else {
Point(rect.height(), rect.width())
}
}
override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) {
if (result.error == null) {
val bitmap = result.bitmap
@ -310,8 +746,9 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
return
}
if (isCropIntent) {
if (saveUri.scheme == "file") {
saveBitmapToFile(bitmap, saveUri.path!!)
saveBitmapToFile(bitmap, saveUri.path!!, true)
} else {
var inputStream: InputStream? = null
var outputStream: OutputStream? = null
@ -333,12 +770,48 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
finish()
}
} else if (saveUri.scheme == "file") {
SaveAsDialog(this, saveUri.path!!, true) {
saveBitmapToFile(bitmap, it, true)
}
} else if (saveUri.scheme == "content") {
val filePathGetter = getNewFilePath()
SaveAsDialog(this, filePathGetter.first, filePathGetter.second) {
saveBitmapToFile(bitmap, it, true)
}
} else {
toast(R.string.unknown_file_location)
}
} else {
toast("${getString(R.string.image_editing_failed)}: ${result.error.message}")
}
}
private fun saveBitmapToFile(bitmap: Bitmap, path: String) {
private fun getNewFilePath(): Pair<String, Boolean> {
var newPath = applicationContext.getRealPathFromURI(saveUri) ?: ""
if (newPath.startsWith("/mnt/")) {
newPath = ""
}
var shouldAppendFilename = true
if (newPath.isEmpty()) {
val filename = applicationContext.getFilenameFromContentUri(saveUri) ?: ""
if (filename.isNotEmpty()) {
val path = if (intent.extras?.containsKey(REAL_FILE_PATH) == true) intent.getStringExtra(REAL_FILE_PATH).getParentPath() else internalStoragePath
newPath = "$path/$filename"
shouldAppendFilename = false
}
}
if (newPath.isEmpty()) {
newPath = "$internalStoragePath/${getCurrentFormattedDateTime()}.${saveUri.toString().getFilenameExtension()}"
shouldAppendFilename = false
}
return Pair(newPath, shouldAppendFilename)
}
private fun saveBitmapToFile(bitmap: Bitmap, path: String, showSavingToast: Boolean) {
if (!packageName.contains("slootelibomelpmis".reversed(), true)) {
if (baseConfig.appRunCount > 100) {
val label = "sknahT .moc.slootelibomelpmis.www morf eno lanigiro eht daolnwod ytefas nwo ruoy roF .ppa eht fo noisrev ekaf a gnisu era uoY".reversed()
@ -357,7 +830,7 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
val fileDirItem = FileDirItem(path, path.getFilenameFromPath())
getFileOutputStream(fileDirItem, true) {
if (it != null) {
saveBitmap(file, bitmap, it)
saveBitmap(file, bitmap, it, showSavingToast)
} else {
toast(R.string.image_editing_failed)
}
@ -371,8 +844,10 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
}
@TargetApi(Build.VERSION_CODES.N)
private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream) {
private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream, showSavingToast: Boolean) {
if (showSavingToast) {
toast(R.string.saving)
}
if (resizeWidth > 0 && resizeHeight > 0) {
val resized = Bitmap.createScaledBitmap(bitmap, resizeWidth, resizeHeight, false)
@ -394,6 +869,11 @@ class EditActivity : SimpleActivity(), CropImageView.OnCropImageCompleteListener
out.close()
}
private fun editWith() {
openEditor(uri.toString(), true)
isEditingWithThirdParty = true
}
private fun scanFinalPath(path: String) {
val paths = arrayListOf(path)
rescanPaths(paths) {

View file

@ -0,0 +1,58 @@
package com.simplemobiletools.gallery.pro.adapters
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.simplemobiletools.gallery.pro.R
import com.simplemobiletools.gallery.pro.models.FilterItem
import kotlinx.android.synthetic.main.editor_filter_item.view.*
import java.util.*
class FiltersAdapter(val context: Context, val filterItems: ArrayList<FilterItem>, val itemClick: (Int) -> Unit) : RecyclerView.Adapter<FiltersAdapter.ViewHolder>() {
private var currentSelection = filterItems.first()
private var strokeBackground = context.resources.getDrawable(R.drawable.stroke_background)
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindView(filterItems[position])
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.editor_filter_item, parent, false)
return ViewHolder(view)
}
override fun getItemCount() = filterItems.size
fun getCurrentFilter() = currentSelection
private fun setCurrentFilter(position: Int) {
val filterItem = filterItems.getOrNull(position) ?: return
if (currentSelection != filterItem) {
currentSelection = filterItem
notifyDataSetChanged()
itemClick.invoke(position)
}
}
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bindView(filterItem: FilterItem): View {
itemView.apply {
editor_filter_item_label.text = filterItem.filter.name
editor_filter_item_thumbnail.setImageBitmap(filterItem.bitmap)
editor_filter_item_thumbnail.background = if (getCurrentFilter() == filterItem) {
strokeBackground
} else {
null
}
setOnClickListener {
setCurrentFilter(adapterPosition)
}
}
return itemView
}
}
}

View file

@ -80,7 +80,7 @@ fun Activity.launchCamera() {
fun SimpleActivity.launchAbout() {
val licenses = LICENSE_GLIDE or LICENSE_CROPPER or LICENSE_RTL or LICENSE_SUBSAMPLING or LICENSE_PATTERN or LICENSE_REPRINT or LICENSE_GIF_DRAWABLE or
LICENSE_PICASSO or LICENSE_EXOPLAYER or LICENSE_PANORAMA_VIEW or LICENSE_SANSELAN or LICENSE_GESTURE_VIEWS
LICENSE_PICASSO or LICENSE_EXOPLAYER or LICENSE_PANORAMA_VIEW or LICENSE_SANSELAN or LICENSE_FILTERS or LICENSE_GESTURE_VIEWS
val faqItems = arrayListOf(
FAQItem(R.string.faq_5_title_commons, R.string.faq_5_text_commons),

View file

@ -475,6 +475,14 @@ class Config(context: Context) : BaseConfig(context) {
get() = prefs.getBoolean(ALLOW_ROTATING_WITH_GESTURES, true)
set(allowRotatingWithGestures) = prefs.edit().putBoolean(ALLOW_ROTATING_WITH_GESTURES, allowRotatingWithGestures).apply()
var lastEditorDrawColor: Int
get() = prefs.getInt(LAST_EDITOR_DRAW_COLOR, primaryColor)
set(lastEditorDrawColor) = prefs.edit().putInt(LAST_EDITOR_DRAW_COLOR, lastEditorDrawColor).apply()
var lastEditorBrushSize: Int
get() = prefs.getInt(LAST_EDITOR_BRUSH_SIZE, 50)
set(lastEditorBrushSize) = prefs.edit().putInt(LAST_EDITOR_BRUSH_SIZE, lastEditorBrushSize).apply()
var showNotch: Boolean
get() = prefs.getBoolean(SHOW_NOTCH, true)
set(showNotch) = prefs.edit().putBoolean(SHOW_NOTCH, showNotch).apply()

View file

@ -73,6 +73,8 @@ const val GROUP_DIRECT_SUBFOLDERS = "group_direct_subfolders"
const val SHOW_WIDGET_FOLDER_NAME = "show_widget_folder_name"
const val ALLOW_ONE_TO_ONE_ZOOM = "allow_one_to_one_zoom"
const val ALLOW_ROTATING_WITH_GESTURES = "allow_rotating_with_gestures"
const val LAST_EDITOR_DRAW_COLOR = "last_editor_draw_color"
const val LAST_EDITOR_BRUSH_SIZE = "last_editor_brush_size"
const val SHOW_NOTCH = "show_notch"
const val FILE_LOADING_PRIORITY = "file_loading_priority"
const val SPAM_FOLDERS_CHECKED = "spam_folders_checked"

View file

@ -0,0 +1,27 @@
package com.simplemobiletools.gallery.pro.helpers
import android.graphics.Bitmap
import com.simplemobiletools.gallery.pro.models.FilterItem
import java.util.*
class FilterThumbnailsManager {
private var filterThumbnails = ArrayList<FilterItem>(10)
private var processedThumbnails = ArrayList<FilterItem>(10)
fun addThumb(filterItem: FilterItem) {
filterThumbnails.add(filterItem)
}
fun processThumbs(): ArrayList<FilterItem> {
for (filterItem in filterThumbnails) {
filterItem.bitmap = filterItem.filter.processFilter(Bitmap.createBitmap(filterItem.bitmap))
processedThumbnails.add(filterItem)
}
return processedThumbnails
}
fun clearThumbs() {
filterThumbnails = ArrayList()
processedThumbnails = ArrayList()
}
}

View file

@ -0,0 +1,6 @@
package com.simplemobiletools.gallery.pro.models
import android.graphics.Bitmap
import com.zomato.photofilters.imageprocessors.Filter
data class FilterItem(var bitmap: Bitmap, val filter: Filter)

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19,12h-2v3h-3v2h5v-5zM7,9h3L10,7L5,7v5h2L7,9zM21,3L3,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,19.01L3,19.01L3,4.99h18v14.02z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M7.47,21.49C4.2,19.93 1.86,16.76 1.5,13L0,13c0.51,6.16 5.66,11 11.95,11 0.23,0 0.44,-0.02 0.66,-0.03L8.8,20.15l-1.33,1.34zM12.05,0c-0.23,0 -0.44,0.02 -0.66,0.04l3.81,3.81 1.33,-1.33C19.8,4.07 22.14,7.24 22.5,11L24,11c-0.51,-6.16 -5.66,-11 -11.95,-11zM16,14h2L18,8c0,-1.11 -0.9,-2 -2,-2h-6v2h6v6zM8,16L8,4L6,4v2L4,6v2h2v8c0,1.1 0.89,2 2,2h8v2h2v-2h2v-2L8,16z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4.59,6.89c0.7,-0.71 1.4,-1.35 1.71,-1.22 0.5,0.2 0,1.03 -0.3,1.52 -0.25,0.42 -2.86,3.89 -2.86,6.31 0,1.28 0.48,2.34 1.34,2.98 0.75,0.56 1.74,0.73 2.64,0.46 1.07,-0.31 1.95,-1.4 3.06,-2.77 1.21,-1.49 2.83,-3.44 4.08,-3.44 1.63,0 1.65,1.01 1.76,1.79 -3.78,0.64 -5.38,3.67 -5.38,5.37 0,1.7 1.44,3.09 3.21,3.09 1.63,0 4.29,-1.33 4.69,-6.1L21,14.88v-2.5h-2.47c-0.15,-1.65 -1.09,-4.2 -4.03,-4.2 -2.25,0 -4.18,1.91 -4.94,2.84 -0.58,0.73 -2.06,2.48 -2.29,2.72 -0.25,0.3 -0.68,0.84 -1.11,0.84 -0.45,0 -0.72,-0.83 -0.36,-1.92 0.35,-1.09 1.4,-2.86 1.85,-3.52 0.78,-1.14 1.3,-1.92 1.3,-3.28C8.95,3.69 7.31,3 6.44,3 5.12,3 3.97,4 3.72,4.25c-0.36,0.36 -0.66,0.66 -0.88,0.93l1.75,1.71zM13.88,18.55c-0.31,0 -0.74,-0.26 -0.74,-0.72 0,-0.6 0.73,-2.2 2.87,-2.76 -0.3,2.69 -1.43,3.48 -2.13,3.48z"/>
</vector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M19.02,10v9L5,19L5,5h9L14,3L5.02,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2v-9h-2zM17,10l0.94,-2.06L20,7l-2.06,-0.94L17,4l-0.94,2.06L14,7l2.06,0.94zM13.25,10.75L12,8l-1.25,2.75L8,12l2.75,1.25L12,16l1.25,-2.75L16,12z"/>
</vector>

View file

@ -1,18 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/activity_edit_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/default_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@+id/bottom_editor_crop_rotate_actions"
android:layout_marginBottom="@dimen/bottom_filters_height_with_margin"/>
<com.theartofdev.edmodo.cropper.CropImageView
android:id="@+id/crop_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="@dimen/bottom_actions_height_bigger"
android:visibility="gone"
app:cropBackgroundColor="@color/crop_image_view_background"
app:cropInitialCropWindowPaddingRatio="0"/>
<com.simplemobiletools.gallery.pro.views.EditorDrawCanvas
android:id="@+id/editor_draw_canvas"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_centerHorizontal="true"
android:layout_marginBottom="@dimen/bottom_actions_height_double"
android:background="@android:color/transparent"
android:visibility="gone"/>
<RelativeLayout
android:id="@+id/bottom_editor_actions_background"
android:layout_width="match_parent"
@ -20,19 +38,40 @@
android:layout_alignParentBottom="true"
android:background="@drawable/gradient_background"/>
<include
android:id="@+id/bottom_editor_primary_actions"
layout="@layout/bottom_editor_primary_actions"/>
<include
android:id="@+id/bottom_aspect_ratios"
layout="@layout/bottom_actions_aspect_ratio"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/bottom_editor_crop_rotate_actions" />
android:layout_above="@+id/bottom_editor_crop_rotate_actions"
android:visibility="gone"/>
<include
android:id="@+id/bottom_editor_filter_actions"
layout="@layout/bottom_editor_actions_filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/bottom_editor_primary_actions"
android:visibility="gone"/>
<include
android:id="@+id/bottom_editor_crop_rotate_actions"
layout="@layout/bottom_editor_crop_rotate_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/medium_margin" />
android:layout_above="@+id/bottom_editor_primary_actions"
android:visibility="gone"/>
<include
android:id="@+id/bottom_editor_draw_actions"
layout="@layout/bottom_editor_draw_actions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/bottom_editor_primary_actions"
android:visibility="gone"/>
</RelativeLayout>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_actions_filter_wrapper"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_filters_height"
android:paddingTop="@dimen/medium_margin">
<com.simplemobiletools.commons.views.MyRecyclerView
android:id="@+id/bottom_actions_filter_list"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_filters_height"
android:background="@color/crop_image_view_background"
android:orientation="horizontal"
android:overScrollMode="never"
app:layoutManager="com.simplemobiletools.commons.views.MyLinearLayoutManager"/>
</RelativeLayout>

View file

@ -16,11 +16,39 @@
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_rotate_right_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottom_flip_horizontally"
app:layout_constraintEnd_toStartOf="@+id/bottom_resize"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_resize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/resize"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_minimize"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottom_aspect_ratio"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bottom_rotate"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_aspect_ratio"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/free_aspect_ratio"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_aspect_ratio_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottom_flip_horizontally"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bottom_resize"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_flip_horizontally"
android:layout_width="wrap_content"
@ -32,7 +60,7 @@
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottom_flip_vertically"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="@+id/bottom_rotate"
app:layout_constraintStart_toEndOf="@+id/bottom_aspect_ratio"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_editor_draw_actions_wrapper"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_actions_height"
android:layout_alignParentBottom="true">
<com.simplemobiletools.commons.views.MySeekBar
android:id="@+id/bottom_draw_width"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="@dimen/activity_margin"
android:layout_marginRight="@dimen/activity_margin"
android:max="100"
android:progress="50"
app:layout_constraintBottom_toBottomOf="@id/bottom_draw_color"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toLeftOf="@+id/bottom_draw_color"
app:layout_constraintTop_toTopOf="@+id/bottom_draw_color"/>
<ImageView
android:id="@+id/bottom_draw_color_clickable"
android:layout_width="@dimen/bottom_editor_color_picker_size"
android:layout_height="@dimen/bottom_editor_color_picker_size"
android:layout_marginEnd="@dimen/small_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/change_color"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@+id/bottom_draw_undo"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_draw_color"
android:layout_width="@dimen/bottom_editor_color_picker_size"
android:layout_height="@dimen/bottom_editor_color_picker_size"
android:layout_marginEnd="@dimen/small_margin"
android:clickable="false"
android:contentDescription="@null"
android:padding="@dimen/small_margin"
android:src="@drawable/circle_background"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toLeftOf="@+id/bottom_draw_undo"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_draw_undo"
android:layout_width="@dimen/bottom_editor_color_picker_size"
android:layout_height="@dimen/bottom_editor_color_picker_size"
android:layout_marginEnd="@dimen/normal_margin"
android:background="?attr/selectableItemBackgroundBorderless"
android:clickable="false"
android:contentDescription="@string/undo"
android:padding="@dimen/medium_margin"
android:src="@drawable/ic_undo_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/bottom_editor_primary_actions_wrapper"
android:layout_width="match_parent"
android:layout_height="@dimen/bottom_actions_height"
android:layout_alignParentBottom="true">
<ImageView
android:id="@+id/bottom_primary_filter"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/filter"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_photo_filter_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottom_primary_crop_rotate"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_primary_crop_rotate"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_crop_rotate_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/bottom_primary_draw"
app:layout_constraintStart_toEndOf="@+id/bottom_primary_filter"
app:layout_constraintTop_toTopOf="parent"/>
<ImageView
android:id="@+id/bottom_primary_draw"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="@dimen/normal_margin"
android:src="@drawable/ic_draw_vector"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/bottom_primary_crop_rotate"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/editor_filter_item_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/editor_filter_item_thumbnail"
android:layout_width="@dimen/bottom_filters_thumbnail_size"
android:layout_height="@dimen/bottom_filters_thumbnail_size"
android:layout_above="@+id/editor_filter_item_label"
android:background="@drawable/stroke_background"
android:contentDescription="@null"
android:padding="1dp"/>
<TextView
android:id="@+id/editor_filter_item_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:gravity="center_horizontal"
android:textColor="@android:color/white"
android:textSize="@dimen/smaller_text_size"
tools:text="Filter"/>
</RelativeLayout>

View file

@ -6,6 +6,11 @@
android:icon="@drawable/ic_check_vector"
android:title="@string/save_as"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/edit"
android:icon="@drawable/ic_edit_vector"
android:title="@string/edit_with"
app:showAsAction="ifRoom"/>
<item
android:id="@+id/share"
android:icon="@drawable/ic_share_vector"

View file

@ -14,6 +14,9 @@
<dimen name="bottom_actions_height_double">128dp</dimen>
<dimen name="bottom_actions_height_bigger">164dp</dimen>
<dimen name="bottom_editor_color_picker_size">48dp</dimen>
<dimen name="bottom_filters_thumbnail_size">76dp</dimen>
<dimen name="bottom_filters_height">90dp</dimen>
<dimen name="bottom_filters_height_with_margin">98dp</dimen>
<dimen name="bottom_editor_actions_shadow_height">180dp</dimen>
<dimen name="portrait_photos_stripe_height">48dp</dimen>
<dimen name="default_status_action_height">86dp</dimen>