embedded photo editor
This commit is contained in:
parent
e1161bcb22
commit
7c5826c945
12
build.gradle
12
build.gradle
|
@ -4,18 +4,22 @@ buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
maven { url "https://www.jitpack.io" }
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.21"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'org.jetbrains.kotlin.android'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
jcenter()
|
||||||
|
maven { url "https://www.jitpack.io" }
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
@ -46,7 +50,7 @@ dependencies {
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.6'
|
implementation 'androidx.exifinterface:exifinterface:1.3.6'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.8.0'
|
implementation 'com.google.android.material:material:1.9.0'
|
||||||
|
|
||||||
implementation "androidx.emoji2:emoji2:1.2.0"
|
implementation "androidx.emoji2:emoji2:1.2.0"
|
||||||
freeImplementation "androidx.emoji2:emoji2-bundled:1.2.0"
|
freeImplementation "androidx.emoji2:emoji2-bundled:1.2.0"
|
||||||
|
@ -77,6 +81,12 @@ dependencies {
|
||||||
implementation 'com.google.guava:guava:31.1-android'
|
implementation 'com.google.guava:guava:31.1-android'
|
||||||
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.49'
|
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.49'
|
||||||
implementation 'im.conversations.webrtc:webrtc-android:104.0.0'
|
implementation 'im.conversations.webrtc:webrtc-android:104.0.0'
|
||||||
|
|
||||||
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
|
implementation "androidx.recyclerview:recyclerview:1.2.1"
|
||||||
|
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||||
|
implementation 'info.androidhive:imagefilters:1.0.7'
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
|
|
@ -306,6 +306,12 @@
|
||||||
android:name=".ui.MediaBrowserActivity"
|
android:name=".ui.MediaBrowserActivity"
|
||||||
android:label="@string/media_browser" />
|
android:label="@string/media_browser" />
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name="eu.siacs.conversations.medialib.activities.EditActivity"
|
||||||
|
android:theme="@style/ConversationsTheme.FullScreen"
|
||||||
|
android:configChanges="orientation"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<service android:name=".services.ExportBackupService" />
|
<service android:name=".services.ExportBackupService" />
|
||||||
<service android:name=".services.ImportBackupService" />
|
<service android:name=".services.ImportBackupService" />
|
||||||
<service
|
<service
|
||||||
|
|
|
@ -0,0 +1,739 @@
|
||||||
|
package eu.siacs.conversations.medialib.activities
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.TargetApi
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Bitmap.CompressFormat
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.Handler
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
|
import android.view.WindowInsets
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.RelativeLayout
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.net.toUri
|
||||||
|
import androidx.core.view.marginTop
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
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.leinardi.android.speeddial.UiUtils.getPrimaryColor
|
||||||
|
import com.theartofdev.edmodo.cropper.CropImageView
|
||||||
|
import com.zomato.photofilters.FilterPack
|
||||||
|
import com.zomato.photofilters.imageprocessors.Filter
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.databinding.ActivityEditBinding
|
||||||
|
import eu.siacs.conversations.medialib.adapters.FiltersAdapter
|
||||||
|
import eu.siacs.conversations.medialib.dialogs.ColorPickerDialog
|
||||||
|
import eu.siacs.conversations.medialib.dialogs.OtherAspectRatioDialog
|
||||||
|
import eu.siacs.conversations.medialib.dialogs.ResizeDialog
|
||||||
|
import eu.siacs.conversations.medialib.extensions.*
|
||||||
|
import eu.siacs.conversations.medialib.helpers.*
|
||||||
|
import eu.siacs.conversations.medialib.models.FileDirItem
|
||||||
|
import eu.siacs.conversations.medialib.models.FilterItem
|
||||||
|
import java.io.*
|
||||||
|
import java.lang.Float.max
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
class EditActivity : AppCompatActivity(), CropImageView.OnCropImageCompleteListener {
|
||||||
|
companion object {
|
||||||
|
const val KEY_CHAT_NAME = "editActivity_chatName"
|
||||||
|
const val KEY_EDITED_URI = "editActivity_edited_uri"
|
||||||
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("NativeImageProcessor")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 var uri: Uri? = null
|
||||||
|
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 wasDrawCanvasPositioned = false
|
||||||
|
private var oldExif: ExifInterface? = null
|
||||||
|
private var filterInitialBitmap: Bitmap? = null
|
||||||
|
private var originalUri: Uri? = null
|
||||||
|
|
||||||
|
private lateinit var binding: ActivityEditBinding
|
||||||
|
|
||||||
|
private val launchSavingToastRunnable = Runnable {
|
||||||
|
toast(R.string.saving)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityEditBinding.inflate(layoutInflater)
|
||||||
|
val view = binding.root
|
||||||
|
setContentView(view)
|
||||||
|
|
||||||
|
supportActionBar?.hide();
|
||||||
|
|
||||||
|
binding.editorToolbar.title = intent.getStringExtra(KEY_CHAT_NAME)
|
||||||
|
binding.editorToolbar.setTitleTextColor(Color.WHITE)
|
||||||
|
binding.editorToolbar.setNavigationIconTint(Color.WHITE)
|
||||||
|
binding.editorToolbar.setNavigationOnClickListener {
|
||||||
|
onBackPressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.editorToolbar.inflateMenu(R.menu.menu_done)
|
||||||
|
binding.editorToolbar.setOnMenuItemClickListener {
|
||||||
|
when (it.itemId) {
|
||||||
|
R.id.action_done -> saveImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
window.statusBarColor = ContextCompat.getColor(this, R.color.black26)
|
||||||
|
|
||||||
|
initEditActivity()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initEditActivity() {
|
||||||
|
if (intent.data == null) {
|
||||||
|
toast(R.string.invalid_image_path)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uri = intent.data!!
|
||||||
|
originalUri = uri
|
||||||
|
if (uri!!.scheme != "file" && uri!!.scheme != "content") {
|
||||||
|
toast(R.string.unknown_file_location)
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (intent.extras?.containsKey(REAL_FILE_PATH) == true) {
|
||||||
|
val realPath = intent.extras!!.getString(REAL_FILE_PATH)
|
||||||
|
uri = when {
|
||||||
|
realPath!!.startsWith("file:/") -> Uri.parse(realPath)
|
||||||
|
else -> Uri.fromFile(File(realPath))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
(getRealPathFromURI(uri!!))?.apply {
|
||||||
|
uri = Uri.fromFile(File(this))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDefaultImageView()
|
||||||
|
setupBottomActions()
|
||||||
|
|
||||||
|
if (config.lastEditorCropAspectRatio == ASPECT_RATIO_OTHER) {
|
||||||
|
if (config.lastEditorCropOtherAspectRatioX == 0f) {
|
||||||
|
config.lastEditorCropOtherAspectRatioX = 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.lastEditorCropOtherAspectRatioY == 0f) {
|
||||||
|
config.lastEditorCropOtherAspectRatioY = 1f
|
||||||
|
}
|
||||||
|
|
||||||
|
lastOtherAspectRatio = Pair(config.lastEditorCropOtherAspectRatioX, config.lastEditorCropOtherAspectRatioY)
|
||||||
|
}
|
||||||
|
updateAspectRatio(config.lastEditorCropAspectRatio)
|
||||||
|
binding.cropImageView.guidelines = CropImageView.Guidelines.ON
|
||||||
|
binding.bottomAspectRatios.root.beVisible()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadDefaultImageView() {
|
||||||
|
binding.defaultImageView.beVisible()
|
||||||
|
binding.cropImageView.beGone()
|
||||||
|
binding.editorDrawCanvas.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): Boolean {
|
||||||
|
if (uri != originalUri) {
|
||||||
|
uri = originalUri
|
||||||
|
Handler().post {
|
||||||
|
loadDefaultImageView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 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)) {
|
||||||
|
binding.defaultImageView.onGlobalLayout {
|
||||||
|
applyFilter(currentFilter)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
filterInitialBitmap = bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}).into(binding.defaultImageView)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadCropImageView() {
|
||||||
|
binding.defaultImageView.beGone()
|
||||||
|
binding.editorDrawCanvas.beGone()
|
||||||
|
binding.cropImageView.apply {
|
||||||
|
beVisible()
|
||||||
|
setOnCropImageCompleteListener(this@EditActivity)
|
||||||
|
setImageUriAsync(uri)
|
||||||
|
guidelines = CropImageView.Guidelines.ON
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadDrawCanvas() {
|
||||||
|
binding.defaultImageView.beGone()
|
||||||
|
binding.cropImageView.beGone()
|
||||||
|
binding.editorDrawCanvas.beVisible()
|
||||||
|
|
||||||
|
if (!wasDrawCanvasPositioned) {
|
||||||
|
wasDrawCanvasPositioned = true
|
||||||
|
binding.editorDrawCanvas.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(binding.editorDrawCanvas.width, binding.editorDrawCanvas.height)
|
||||||
|
|
||||||
|
val bitmap = builder.get()
|
||||||
|
runOnUiThread {
|
||||||
|
binding.editorDrawCanvas.apply {
|
||||||
|
updateBackgroundBitmap(bitmap)
|
||||||
|
layoutParams.width = bitmap.width
|
||||||
|
layoutParams.height = bitmap.height
|
||||||
|
android.util.Log.e("31fd", bitmap.height.toString() + " " + height)
|
||||||
|
|
||||||
|
translationY = max((height - bitmap.height) / 2f, 0f)
|
||||||
|
requestLayout()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private fun saveImage() {
|
||||||
|
setOldExif()
|
||||||
|
|
||||||
|
if (binding.cropImageView.isVisible()) {
|
||||||
|
binding.cropImageView.getCroppedImageAsync()
|
||||||
|
} else if (binding.editorDrawCanvas.isVisible()) {
|
||||||
|
val bitmap = binding.editorDrawCanvas.getBitmap()
|
||||||
|
saveBitmapToFile(bitmap, true)
|
||||||
|
} else {
|
||||||
|
val currentFilter = getFiltersAdapter()?.getCurrentFilter() ?: return
|
||||||
|
toast(R.string.saving)
|
||||||
|
|
||||||
|
// clean up everything to free as much memory as possible
|
||||||
|
binding.defaultImageView.setImageResource(0)
|
||||||
|
binding.cropImageView.setImageBitmap(null)
|
||||||
|
binding.bottomEditorFilterActions.bottomActionsFilterList.adapter = null
|
||||||
|
binding.bottomEditorFilterActions.bottomActionsFilterList.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, false)
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
toast(R.string.out_of_memory_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private fun setOldExif() {
|
||||||
|
var inputStream: InputStream? = null
|
||||||
|
try {
|
||||||
|
if (isNougatPlus()) {
|
||||||
|
inputStream = contentResolver.openInputStream(uri!!)
|
||||||
|
oldExif = ExifInterface(inputStream!!)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
} finally {
|
||||||
|
inputStream?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFiltersAdapter() = binding.bottomEditorFilterActions.bottomActionsFilterList.adapter as? FiltersAdapter
|
||||||
|
|
||||||
|
private fun setupBottomActions() {
|
||||||
|
setupPrimaryActionButtons()
|
||||||
|
setupCropRotateActionButtons()
|
||||||
|
setupAspectRatioButtons()
|
||||||
|
setupDrawButtons()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupPrimaryActionButtons() {
|
||||||
|
binding.bottomEditorPrimaryActions.bottomPrimaryFilter.setOnClickListener {
|
||||||
|
bottomFilterClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorPrimaryActions.bottomPrimaryCropRotate.setOnClickListener {
|
||||||
|
bottomCropRotateClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorPrimaryActions.bottomPrimaryDraw.setOnClickListener {
|
||||||
|
bottomDrawClicked()
|
||||||
|
}
|
||||||
|
arrayOf(binding.bottomEditorPrimaryActions.bottomPrimaryFilter, binding.bottomEditorPrimaryActions.bottomPrimaryCropRotate, binding.bottomEditorPrimaryActions.bottomPrimaryDraw).forEach {
|
||||||
|
setupLongPress(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
||||||
|
binding.bottomEditorCropRotateActions.bottomRotate.setOnClickListener {
|
||||||
|
binding.cropImageView.rotateImage(90)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorCropRotateActions.bottomResize.setOnClickListener {
|
||||||
|
resizeImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorCropRotateActions.bottomFlipHorizontally.setOnClickListener {
|
||||||
|
binding.cropImageView.flipImageHorizontally()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorCropRotateActions.bottomFlipVertically.setOnClickListener {
|
||||||
|
binding.cropImageView.flipImageVertically()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorCropRotateActions.bottomAspectRatio.setOnClickListener {
|
||||||
|
currCropRotateAction = if (currCropRotateAction == CROP_ROTATE_ASPECT_RATIO) {
|
||||||
|
binding.cropImageView.guidelines = CropImageView.Guidelines.OFF
|
||||||
|
binding.bottomAspectRatios.root.beGone()
|
||||||
|
CROP_ROTATE_NONE
|
||||||
|
} else {
|
||||||
|
binding.cropImageView.guidelines = CropImageView.Guidelines.ON
|
||||||
|
binding.bottomAspectRatios.root.beVisible()
|
||||||
|
CROP_ROTATE_ASPECT_RATIO
|
||||||
|
}
|
||||||
|
updateCropRotateActionButtons()
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayOf(binding.bottomEditorCropRotateActions.bottomRotate, binding.bottomEditorCropRotateActions.bottomResize, binding.bottomEditorCropRotateActions.bottomFlipHorizontally, binding.bottomEditorCropRotateActions.bottomFlipVertically, binding.bottomEditorCropRotateActions.bottomAspectRatio).forEach {
|
||||||
|
setupLongPress(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupAspectRatioButtons() {
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioFree.setOnClickListener {
|
||||||
|
updateAspectRatio(ASPECT_RATIO_FREE)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioOneOne.setOnClickListener {
|
||||||
|
updateAspectRatio(ASPECT_RATIO_ONE_ONE)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioFourThree.setOnClickListener {
|
||||||
|
updateAspectRatio(ASPECT_RATIO_FOUR_THREE)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioSixteenNine.setOnClickListener {
|
||||||
|
updateAspectRatio(ASPECT_RATIO_SIXTEEN_NINE)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioOther.setOnClickListener {
|
||||||
|
OtherAspectRatioDialog(this, lastOtherAspectRatio) {
|
||||||
|
lastOtherAspectRatio = it
|
||||||
|
config.lastEditorCropOtherAspectRatioX = it.first
|
||||||
|
config.lastEditorCropOtherAspectRatioY = it.second
|
||||||
|
updateAspectRatio(ASPECT_RATIO_OTHER)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateAspectRatioButtons()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupDrawButtons() {
|
||||||
|
updateDrawColor(config.lastEditorDrawColor)
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawWidth.progress = config.lastEditorBrushSize
|
||||||
|
updateBrushSize(config.lastEditorBrushSize)
|
||||||
|
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawColorClickable.setOnClickListener {
|
||||||
|
ColorPickerDialog(this, drawColor) { wasPositivePressed, color ->
|
||||||
|
if (wasPositivePressed) {
|
||||||
|
updateDrawColor(color)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawWidth.onSeekBarChangeListener {
|
||||||
|
config.lastEditorBrushSize = it
|
||||||
|
updateBrushSize(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawUndo.setOnClickListener {
|
||||||
|
binding.editorDrawCanvas.undo()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateBrushSize(percent: Int) {
|
||||||
|
binding.editorDrawCanvas.updateBrushSize(percent)
|
||||||
|
val scale = Math.max(0.03f, percent / 100f)
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawColor.scaleX = scale
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawColor.scaleY = scale
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updatePrimaryActionButtons() {
|
||||||
|
if (binding.cropImageView.isGone() && currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE) {
|
||||||
|
loadCropImageView()
|
||||||
|
} else if (binding.defaultImageView.isGone() && currPrimaryAction == PRIMARY_ACTION_FILTER) {
|
||||||
|
loadDefaultImageView()
|
||||||
|
} else if (binding.editorDrawCanvas.isGone() && currPrimaryAction == PRIMARY_ACTION_DRAW) {
|
||||||
|
loadDrawCanvas()
|
||||||
|
}
|
||||||
|
|
||||||
|
arrayOf(binding.bottomEditorPrimaryActions.bottomPrimaryFilter, binding.bottomEditorPrimaryActions.bottomPrimaryCropRotate, binding.bottomEditorPrimaryActions.bottomPrimaryDraw).forEach {
|
||||||
|
it.applyColorFilter(Color.WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentPrimaryActionButton = when (currPrimaryAction) {
|
||||||
|
PRIMARY_ACTION_FILTER -> binding.bottomEditorPrimaryActions.bottomPrimaryFilter
|
||||||
|
PRIMARY_ACTION_CROP_ROTATE -> binding.bottomEditorPrimaryActions.bottomPrimaryCropRotate
|
||||||
|
PRIMARY_ACTION_DRAW -> binding.bottomEditorPrimaryActions.bottomPrimaryDraw
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
currentPrimaryActionButton?.applyColorFilter(getPrimaryColor(this))
|
||||||
|
binding.bottomEditorFilterActions
|
||||||
|
binding.bottomEditorFilterActions.root.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_FILTER)
|
||||||
|
binding.bottomEditorCropRotateActions.root.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_CROP_ROTATE)
|
||||||
|
binding.bottomEditorDrawActions.root.beVisibleIf(currPrimaryAction == PRIMARY_ACTION_DRAW)
|
||||||
|
|
||||||
|
if (currPrimaryAction == PRIMARY_ACTION_FILTER && binding.bottomEditorFilterActions.bottomActionsFilterList.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 = binding.bottomEditorFilterActions.bottomActionsFilterList.layoutManager as LinearLayoutManager
|
||||||
|
applyFilter(filterItems[it])
|
||||||
|
|
||||||
|
if (it == layoutManager.findLastCompletelyVisibleItemPosition() || it == layoutManager.findLastVisibleItemPosition()) {
|
||||||
|
binding.bottomEditorFilterActions.bottomActionsFilterList.smoothScrollBy(thumbnailSize, 0)
|
||||||
|
} else if (it == layoutManager.findFirstCompletelyVisibleItemPosition() || it == layoutManager.findFirstVisibleItemPosition()) {
|
||||||
|
binding.bottomEditorFilterActions.bottomActionsFilterList.smoothScrollBy(-thumbnailSize, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.bottomEditorFilterActions.bottomActionsFilterList.adapter = adapter
|
||||||
|
adapter.notifyDataSetChanged()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currPrimaryAction != PRIMARY_ACTION_CROP_ROTATE) {
|
||||||
|
binding.bottomAspectRatios.root.beGone()
|
||||||
|
currCropRotateAction = CROP_ROTATE_NONE
|
||||||
|
}
|
||||||
|
updateCropRotateActionButtons()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyFilter(filterItem: FilterItem) {
|
||||||
|
val newBitmap = Bitmap.createBitmap(filterInitialBitmap!!)
|
||||||
|
binding.defaultImageView.setImageBitmap(filterItem.filter.processFilter(newBitmap))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateAspectRatio(aspectRatio: Int) {
|
||||||
|
currAspectRatio = aspectRatio
|
||||||
|
config.lastEditorCropAspectRatio = aspectRatio
|
||||||
|
updateAspectRatioButtons()
|
||||||
|
|
||||||
|
binding.cropImageView.apply {
|
||||||
|
if (aspectRatio == ASPECT_RATIO_FREE) {
|
||||||
|
setFixedAspectRatio(false)
|
||||||
|
} else {
|
||||||
|
val newAspectRatio = when (aspectRatio) {
|
||||||
|
ASPECT_RATIO_ONE_ONE -> Pair(1f, 1f)
|
||||||
|
ASPECT_RATIO_FOUR_THREE -> Pair(4f, 3f)
|
||||||
|
ASPECT_RATIO_SIXTEEN_NINE -> Pair(16f, 9f)
|
||||||
|
else -> Pair(lastOtherAspectRatio!!.first, lastOtherAspectRatio!!.second)
|
||||||
|
}
|
||||||
|
|
||||||
|
setAspectRatio(newAspectRatio.first.toInt(), newAspectRatio.second.toInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateAspectRatioButtons() {
|
||||||
|
arrayOf(
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioFree,
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioOneOne,
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioFourThree,
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioSixteenNine,
|
||||||
|
binding.bottomAspectRatios.bottomAspectRatioOther,
|
||||||
|
).forEach {
|
||||||
|
it.setTextColor(Color.WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val currentAspectRatioButton = when (currAspectRatio) {
|
||||||
|
ASPECT_RATIO_FREE -> binding.bottomAspectRatios.bottomAspectRatioFree
|
||||||
|
ASPECT_RATIO_ONE_ONE -> binding.bottomAspectRatios.bottomAspectRatioOneOne
|
||||||
|
ASPECT_RATIO_FOUR_THREE -> binding.bottomAspectRatios.bottomAspectRatioFourThree
|
||||||
|
ASPECT_RATIO_SIXTEEN_NINE -> binding.bottomAspectRatios.bottomAspectRatioSixteenNine
|
||||||
|
else -> binding.bottomAspectRatios.bottomAspectRatioOther
|
||||||
|
}
|
||||||
|
|
||||||
|
currentAspectRatioButton.setTextColor(getPrimaryColor(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateCropRotateActionButtons() {
|
||||||
|
arrayOf(binding.bottomEditorCropRotateActions.bottomAspectRatio).forEach {
|
||||||
|
it.applyColorFilter(Color.WHITE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val primaryActionView = when (currCropRotateAction) {
|
||||||
|
CROP_ROTATE_ASPECT_RATIO -> binding.bottomEditorCropRotateActions.bottomAspectRatio
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
primaryActionView?.applyColorFilter(getPrimaryColor(this))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateDrawColor(color: Int) {
|
||||||
|
drawColor = color
|
||||||
|
binding.bottomEditorDrawActions.bottomDrawColor.applyColorFilter(color)
|
||||||
|
config.lastEditorDrawColor = color
|
||||||
|
binding.editorDrawCanvas.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
|
||||||
|
binding.cropImageView.getCroppedImageAsync()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldCropSquare(): Boolean {
|
||||||
|
val extras = intent.extras
|
||||||
|
return if (extras != null && extras.containsKey(ASPECT_X) && extras.containsKey(ASPECT_Y)) {
|
||||||
|
extras.getInt(ASPECT_X) == extras.getInt(ASPECT_Y)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAreaSize(): Point? {
|
||||||
|
val rect = binding.cropImageView.cropRect ?: return null
|
||||||
|
val rotation = binding.cropImageView.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) {
|
||||||
|
setOldExif()
|
||||||
|
|
||||||
|
val bitmap = result.bitmap
|
||||||
|
|
||||||
|
saveBitmapToFile(bitmap, true)
|
||||||
|
} else {
|
||||||
|
toast("${getString(R.string.image_editing_failed)}: ${result.error.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveBitmapToFile(bitmap: Bitmap, showSavingToast: Boolean) {
|
||||||
|
val file = File(cacheDir, "editedImages/${UUID.randomUUID()}.jpg")
|
||||||
|
|
||||||
|
file.deleteRecursively()
|
||||||
|
file.parentFile?.mkdirs()
|
||||||
|
|
||||||
|
try {
|
||||||
|
ensureBackgroundThread {
|
||||||
|
try {
|
||||||
|
val out = FileOutputStream(file)
|
||||||
|
saveBitmap(file, bitmap, out, showSavingToast)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
toast(R.string.image_editing_failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
} catch (e: OutOfMemoryError) {
|
||||||
|
toast(R.string.out_of_memory_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.N)
|
||||||
|
private fun saveBitmap(file: File, bitmap: Bitmap, out: OutputStream, showSavingToast: Boolean) {
|
||||||
|
if (showSavingToast) {
|
||||||
|
binding.root.postDelayed(launchSavingToastRunnable, 500)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resizeWidth > 0 && resizeHeight > 0) {
|
||||||
|
val resized = Bitmap.createScaledBitmap(bitmap, resizeWidth, resizeHeight, false)
|
||||||
|
resized.compress(file.absolutePath.getCompressionFormat(), 90, out)
|
||||||
|
} else {
|
||||||
|
bitmap.compress(file.absolutePath.getCompressionFormat(), 90, out)
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (isNougatPlus()) {
|
||||||
|
val newExif = ExifInterface(file.absolutePath)
|
||||||
|
oldExif?.copyNonDimensionAttributesTo(newExif)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
|
||||||
|
intent.putExtra(KEY_EDITED_URI, file.toUri())
|
||||||
|
setResult(Activity.RESULT_OK, intent)
|
||||||
|
out.close()
|
||||||
|
binding.root.removeCallbacks(launchSavingToastRunnable)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupLongPress(view: ImageView) {
|
||||||
|
view.setOnLongClickListener {
|
||||||
|
val contentDescription = view.contentDescription
|
||||||
|
if (contentDescription != null) {
|
||||||
|
toast(contentDescription.toString())
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package eu.siacs.conversations.medialib.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.databinding.EditorFilterItemBinding
|
||||||
|
import eu.siacs.conversations.medialib.models.FilterItem
|
||||||
|
|
||||||
|
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) {
|
||||||
|
private val binding = EditorFilterItemBinding.bind(view)
|
||||||
|
|
||||||
|
fun bindView(filterItem: FilterItem): View {
|
||||||
|
itemView.apply {
|
||||||
|
binding.editorFilterItemLabel.text = filterItem.filter.name
|
||||||
|
binding.editorFilterItemThumbnail.setImageBitmap(filterItem.bitmap)
|
||||||
|
binding.editorFilterItemThumbnail.background = if (getCurrentFilter() == filterItem) {
|
||||||
|
strokeBackground
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
setOnClickListener {
|
||||||
|
setCurrentFilter(adapterPosition)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return itemView
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,254 @@
|
||||||
|
package eu.siacs.conversations.medialib.dialogs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import android.view.View.OnTouchListener
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.databinding.DialogColorPickerBinding
|
||||||
|
import eu.siacs.conversations.medialib.extensions.*
|
||||||
|
import eu.siacs.conversations.medialib.views.ColorPickerSquare
|
||||||
|
import java.util.LinkedList
|
||||||
|
|
||||||
|
private const val RECENT_COLORS_NUMBER = 5
|
||||||
|
|
||||||
|
// forked from https://github.com/yukuku/ambilwarna
|
||||||
|
class ColorPickerDialog(
|
||||||
|
val activity: Activity,
|
||||||
|
color: Int,
|
||||||
|
val removeDimmedBackground: Boolean = false,
|
||||||
|
val addDefaultColorButton: Boolean = false,
|
||||||
|
val currentColorCallback: ((color: Int) -> Unit)? = null,
|
||||||
|
val callback: (wasPositivePressed: Boolean, color: Int) -> Unit
|
||||||
|
) {
|
||||||
|
var viewHue: View
|
||||||
|
var viewSatVal: ColorPickerSquare
|
||||||
|
var viewCursor: ImageView
|
||||||
|
var viewNewColor: ImageView
|
||||||
|
var viewTarget: ImageView
|
||||||
|
var newHexField: EditText
|
||||||
|
var viewContainer: ViewGroup
|
||||||
|
private val baseConfig = activity.config
|
||||||
|
private val currentColorHsv = FloatArray(3)
|
||||||
|
private val backgroundColor = Color.BLACK
|
||||||
|
private var isHueBeingDragged = false
|
||||||
|
private var wasDimmedBackgroundRemoved = false
|
||||||
|
private var dialog: AlertDialog? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
Color.colorToHSV(color, currentColorHsv)
|
||||||
|
|
||||||
|
val binding = DialogColorPickerBinding.inflate(activity.layoutInflater)
|
||||||
|
val view = binding.root.apply {
|
||||||
|
if (isQPlus()) {
|
||||||
|
isForceDarkAllowed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
viewHue = binding.colorPickerHue
|
||||||
|
viewSatVal = binding.colorPickerSquare
|
||||||
|
viewCursor = binding.colorPickerCursor
|
||||||
|
|
||||||
|
viewNewColor = binding.colorPickerNewColor
|
||||||
|
viewTarget = binding.colorPickerCursor
|
||||||
|
viewContainer = binding.colorPickerHolder
|
||||||
|
newHexField = binding.colorPickerNewHex
|
||||||
|
|
||||||
|
viewSatVal.setHue(getHue())
|
||||||
|
|
||||||
|
viewNewColor.setFillWithStroke(getColor(), backgroundColor)
|
||||||
|
binding.colorPickerOldColor.setFillWithStroke(color, backgroundColor)
|
||||||
|
|
||||||
|
val hexCode = getHexCode(color)
|
||||||
|
binding.colorPickerOldHex.text = "#$hexCode"
|
||||||
|
binding.colorPickerOldHex.setOnLongClickListener {
|
||||||
|
activity.copyToClipboard(hexCode)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
newHexField.setText(hexCode)
|
||||||
|
setupRecentColors(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewHue.setOnTouchListener(OnTouchListener { v, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||||
|
isHueBeingDragged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.action == MotionEvent.ACTION_MOVE || event.action == MotionEvent.ACTION_DOWN || event.action == MotionEvent.ACTION_UP) {
|
||||||
|
var y = event.y
|
||||||
|
if (y < 0f)
|
||||||
|
y = 0f
|
||||||
|
|
||||||
|
if (y > viewHue.measuredHeight) {
|
||||||
|
y = viewHue.measuredHeight - 0.001f // to avoid jumping the cursor from bottom to top.
|
||||||
|
}
|
||||||
|
var hue = 360f - 360f / viewHue.measuredHeight * y
|
||||||
|
if (hue == 360f)
|
||||||
|
hue = 0f
|
||||||
|
|
||||||
|
currentColorHsv[0] = hue
|
||||||
|
updateHue()
|
||||||
|
newHexField.setText(getHexCode(getColor()))
|
||||||
|
|
||||||
|
if (event.action == MotionEvent.ACTION_UP) {
|
||||||
|
isHueBeingDragged = false
|
||||||
|
}
|
||||||
|
return@OnTouchListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
|
||||||
|
viewSatVal.setOnTouchListener(OnTouchListener { v, event ->
|
||||||
|
if (event.action == MotionEvent.ACTION_MOVE || event.action == MotionEvent.ACTION_DOWN || event.action == MotionEvent.ACTION_UP) {
|
||||||
|
var x = event.x
|
||||||
|
var y = event.y
|
||||||
|
|
||||||
|
if (x < 0f)
|
||||||
|
x = 0f
|
||||||
|
if (x > viewSatVal.measuredWidth)
|
||||||
|
x = viewSatVal.measuredWidth.toFloat()
|
||||||
|
if (y < 0f)
|
||||||
|
y = 0f
|
||||||
|
if (y > viewSatVal.measuredHeight)
|
||||||
|
y = viewSatVal.measuredHeight.toFloat()
|
||||||
|
|
||||||
|
currentColorHsv[1] = 1f / viewSatVal.measuredWidth * x
|
||||||
|
currentColorHsv[2] = 1f - 1f / viewSatVal.measuredHeight * y
|
||||||
|
|
||||||
|
moveColorPicker()
|
||||||
|
viewNewColor.setFillWithStroke(getColor(), backgroundColor)
|
||||||
|
newHexField.setText(getHexCode(getColor()))
|
||||||
|
return@OnTouchListener true
|
||||||
|
}
|
||||||
|
false
|
||||||
|
})
|
||||||
|
|
||||||
|
newHexField.onTextChangeListener {
|
||||||
|
if (it.length == 6 && !isHueBeingDragged) {
|
||||||
|
try {
|
||||||
|
val newColor = Color.parseColor("#$it")
|
||||||
|
Color.colorToHSV(newColor, currentColorHsv)
|
||||||
|
updateHue()
|
||||||
|
moveColorPicker()
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// val textColor = activity.getProperTextColor()
|
||||||
|
val builder = MaterialAlertDialogBuilder(activity)
|
||||||
|
.setPositiveButton(R.string.ok) { _, _ -> confirmNewColor() }
|
||||||
|
.setNegativeButton(R.string.cancel) { _, _ -> dialogDismissed() }
|
||||||
|
.setOnCancelListener { dialogDismissed() }
|
||||||
|
.apply {
|
||||||
|
if (addDefaultColorButton) {
|
||||||
|
setNeutralButton(R.string.default_color) { _, _ -> confirmDefaultColor() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.apply {
|
||||||
|
activity.setupDialogStuff(view, this) { alertDialog ->
|
||||||
|
dialog = alertDialog
|
||||||
|
//view.color_picker_arrow.applyColorFilter(textColor)
|
||||||
|
//view.color_picker_hex_arrow.applyColorFilter(textColor)
|
||||||
|
// viewCursor.applyColorFilter(textColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.onGlobalLayout {
|
||||||
|
moveHuePicker()
|
||||||
|
moveColorPicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun View.setupRecentColors(binding: DialogColorPickerBinding) {
|
||||||
|
val recentColors = baseConfig.colorPickerRecentColors
|
||||||
|
if (recentColors.isNotEmpty()) {
|
||||||
|
binding.recentColors.beVisible()
|
||||||
|
val squareSize = context.resources.getDimensionPixelSize(R.dimen.colorpicker_hue_width)
|
||||||
|
recentColors.take(RECENT_COLORS_NUMBER).forEach { recentColor ->
|
||||||
|
val recentColorView = ImageView(context)
|
||||||
|
recentColorView.id = View.generateViewId()
|
||||||
|
recentColorView.layoutParams = ViewGroup.LayoutParams(squareSize, squareSize)
|
||||||
|
recentColorView.setFillWithStroke(recentColor, backgroundColor)
|
||||||
|
recentColorView.setOnClickListener { newHexField.setText(getHexCode(recentColor)) }
|
||||||
|
binding.recentColors.addView(recentColorView)
|
||||||
|
binding.recentColorsFlow.addView(recentColorView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dialogDismissed() {
|
||||||
|
callback(false, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun confirmDefaultColor() {
|
||||||
|
callback(true, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun confirmNewColor() {
|
||||||
|
val hexValue = newHexField.value
|
||||||
|
val newColor = if (hexValue.length == 6) {
|
||||||
|
Color.parseColor("#$hexValue")
|
||||||
|
} else {
|
||||||
|
getColor()
|
||||||
|
}
|
||||||
|
|
||||||
|
addRecentColor(newColor)
|
||||||
|
callback(true, newColor)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addRecentColor(color: Int) {
|
||||||
|
var recentColors = baseConfig.colorPickerRecentColors
|
||||||
|
|
||||||
|
recentColors.remove(color)
|
||||||
|
if (recentColors.size >= RECENT_COLORS_NUMBER) {
|
||||||
|
val numberOfColorsToDrop = recentColors.size - RECENT_COLORS_NUMBER + 1
|
||||||
|
recentColors = LinkedList(recentColors.dropLast(numberOfColorsToDrop))
|
||||||
|
}
|
||||||
|
recentColors.addFirst(color)
|
||||||
|
|
||||||
|
baseConfig.colorPickerRecentColors = recentColors
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHexCode(color: Int) = color.toHex().substring(1)
|
||||||
|
|
||||||
|
private fun updateHue() {
|
||||||
|
viewSatVal.setHue(getHue())
|
||||||
|
moveHuePicker()
|
||||||
|
viewNewColor.setFillWithStroke(getColor(), backgroundColor)
|
||||||
|
if (removeDimmedBackground && !wasDimmedBackgroundRemoved) {
|
||||||
|
dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||||
|
wasDimmedBackgroundRemoved = true
|
||||||
|
}
|
||||||
|
|
||||||
|
currentColorCallback?.invoke(getColor())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun moveHuePicker() {
|
||||||
|
var y = viewHue.measuredHeight - getHue() * viewHue.measuredHeight / 360f
|
||||||
|
if (y == viewHue.measuredHeight.toFloat())
|
||||||
|
y = 0f
|
||||||
|
|
||||||
|
viewCursor.x = (viewHue.left - viewCursor.width).toFloat()
|
||||||
|
viewCursor.y = viewHue.top + y - viewCursor.height / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun moveColorPicker() {
|
||||||
|
val x = getSat() * viewSatVal.measuredWidth
|
||||||
|
val y = (1f - getVal()) * viewSatVal.measuredHeight
|
||||||
|
viewTarget.x = viewSatVal.left + x - viewTarget.width / 2
|
||||||
|
viewTarget.y = viewSatVal.top + y - viewTarget.height / 2
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getColor() = Color.HSVToColor(currentColorHsv)
|
||||||
|
private fun getHue() = currentColorHsv[0]
|
||||||
|
private fun getSat() = currentColorHsv[1]
|
||||||
|
private fun getVal() = currentColorHsv[2]
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package eu.siacs.conversations.medialib.dialogs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.databinding.DialogCustomAspectRatioBinding
|
||||||
|
import eu.siacs.conversations.medialib.extensions.setupDialogStuff
|
||||||
|
import eu.siacs.conversations.medialib.extensions.showKeyboard
|
||||||
|
import eu.siacs.conversations.medialib.extensions.value
|
||||||
|
|
||||||
|
class CustomAspectRatioDialog(
|
||||||
|
val activity: Activity, val defaultCustomAspectRatio: Pair<Float, Float>?, val callback: (aspectRatio: Pair<Float, Float>) -> Unit
|
||||||
|
) {
|
||||||
|
init {
|
||||||
|
val binding = DialogCustomAspectRatioBinding.inflate(activity.layoutInflater)
|
||||||
|
val view = binding.root.apply {
|
||||||
|
binding.aspectRatioWidth.setText(defaultCustomAspectRatio?.first?.toInt()?.toString() ?: "")
|
||||||
|
binding.aspectRatioHeight.setText(defaultCustomAspectRatio?.second?.toInt()?.toString() ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.apply {
|
||||||
|
activity.setupDialogStuff(view, this) { alertDialog ->
|
||||||
|
alertDialog.showKeyboard(binding.aspectRatioWidth)
|
||||||
|
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val width = getViewValue(binding.aspectRatioWidth)
|
||||||
|
val height = getViewValue(binding.aspectRatioHeight)
|
||||||
|
callback(Pair(width, height))
|
||||||
|
alertDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getViewValue(view: EditText): Float {
|
||||||
|
val textValue = view.value
|
||||||
|
return if (textValue.isEmpty()) 0f else textValue.toFloat()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package eu.siacs.conversations.medialib.dialogs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.databinding.DialogOtherAspectRatioBinding
|
||||||
|
import eu.siacs.conversations.medialib.extensions.setupDialogStuff
|
||||||
|
|
||||||
|
class OtherAspectRatioDialog(
|
||||||
|
val activity: Activity,
|
||||||
|
val lastOtherAspectRatio: Pair<Float, Float>?,
|
||||||
|
val callback: (aspectRatio: Pair<Float, Float>) -> Unit
|
||||||
|
) {
|
||||||
|
private var dialog: AlertDialog? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
val binding = DialogOtherAspectRatioBinding.inflate(activity.layoutInflater)
|
||||||
|
val view = binding.root.apply {
|
||||||
|
binding.otherAspectRatio21.setOnClickListener { ratioPicked(Pair(2f, 1f)) }
|
||||||
|
binding.otherAspectRatio32.setOnClickListener { ratioPicked(Pair(3f, 2f)) }
|
||||||
|
binding.otherAspectRatio43.setOnClickListener { ratioPicked(Pair(4f, 3f)) }
|
||||||
|
binding.otherAspectRatio53.setOnClickListener { ratioPicked(Pair(5f, 3f)) }
|
||||||
|
binding.otherAspectRatio169.setOnClickListener { ratioPicked(Pair(16f, 9f)) }
|
||||||
|
binding.otherAspectRatio199.setOnClickListener { ratioPicked(Pair(19f, 9f)) }
|
||||||
|
binding.otherAspectRatioCustom.setOnClickListener { customRatioPicked() }
|
||||||
|
|
||||||
|
binding.otherAspectRatio12.setOnClickListener { ratioPicked(Pair(1f, 2f)) }
|
||||||
|
binding.otherAspectRatio23.setOnClickListener { ratioPicked(Pair(2f, 3f)) }
|
||||||
|
binding.otherAspectRatio34.setOnClickListener { ratioPicked(Pair(3f, 4f)) }
|
||||||
|
binding.otherAspectRatio35.setOnClickListener { ratioPicked(Pair(3f, 5f)) }
|
||||||
|
binding.otherAspectRatio916.setOnClickListener { ratioPicked(Pair(9f, 16f)) }
|
||||||
|
binding.otherAspectRatio919.setOnClickListener { ratioPicked(Pair(9f, 19f)) }
|
||||||
|
|
||||||
|
val radio1SelectedItemId = when (lastOtherAspectRatio) {
|
||||||
|
Pair(2f, 1f) -> binding.otherAspectRatio21.id
|
||||||
|
Pair(3f, 2f) -> binding.otherAspectRatio32.id
|
||||||
|
Pair(4f, 3f) -> binding.otherAspectRatio43.id
|
||||||
|
Pair(5f, 3f) -> binding.otherAspectRatio53.id
|
||||||
|
Pair(16f, 9f) -> binding.otherAspectRatio169.id
|
||||||
|
Pair(19f, 9f) -> binding.otherAspectRatio199.id
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
binding.otherAspectRatioDialogRadio1.check(radio1SelectedItemId)
|
||||||
|
|
||||||
|
val radio2SelectedItemId = when (lastOtherAspectRatio) {
|
||||||
|
Pair(1f, 2f) -> binding.otherAspectRatio12.id
|
||||||
|
Pair(2f, 3f) -> binding.otherAspectRatio23.id
|
||||||
|
Pair(3f, 4f) -> binding.otherAspectRatio34.id
|
||||||
|
Pair(3f, 5f) -> binding.otherAspectRatio35.id
|
||||||
|
Pair(9f, 16f) -> binding.otherAspectRatio916.id
|
||||||
|
Pair(9f, 19f) -> binding.otherAspectRatio919.id
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
binding.otherAspectRatioDialogRadio2.check(radio2SelectedItemId)
|
||||||
|
|
||||||
|
if (radio1SelectedItemId == 0 && radio2SelectedItemId == 0) {
|
||||||
|
binding.otherAspectRatioDialogRadio1.check(binding.otherAspectRatioCustom.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.apply {
|
||||||
|
activity.setupDialogStuff(view, this) { alertDialog ->
|
||||||
|
dialog = alertDialog
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun customRatioPicked() {
|
||||||
|
CustomAspectRatioDialog(activity, lastOtherAspectRatio) {
|
||||||
|
callback(it)
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ratioPicked(pair: Pair<Float, Float>) {
|
||||||
|
callback(pair)
|
||||||
|
dialog?.dismiss()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package eu.siacs.conversations.medialib.dialogs
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.graphics.Point
|
||||||
|
import android.widget.EditText
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.databinding.DialogResizeImageBinding
|
||||||
|
import eu.siacs.conversations.medialib.extensions.*
|
||||||
|
|
||||||
|
class ResizeDialog(val activity: Activity, val size: Point, val callback: (newSize: Point) -> Unit) {
|
||||||
|
init {
|
||||||
|
val binding = DialogResizeImageBinding.inflate(activity.layoutInflater)
|
||||||
|
val view = binding.root
|
||||||
|
val widthView = binding.resizeImageWidth
|
||||||
|
val heightView = binding.resizeImageHeight
|
||||||
|
|
||||||
|
widthView.setText(size.x.toString())
|
||||||
|
heightView.setText(size.y.toString())
|
||||||
|
|
||||||
|
val ratio = size.x / size.y.toFloat()
|
||||||
|
|
||||||
|
widthView.onTextChangeListener {
|
||||||
|
if (widthView.hasFocus()) {
|
||||||
|
var width = getViewValue(widthView)
|
||||||
|
if (width > size.x) {
|
||||||
|
widthView.setText(size.x.toString())
|
||||||
|
width = size.x
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.keepAspectRatio.isChecked) {
|
||||||
|
heightView.setText((width / ratio).toInt().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heightView.onTextChangeListener {
|
||||||
|
if (heightView.hasFocus()) {
|
||||||
|
var height = getViewValue(heightView)
|
||||||
|
if (height > size.y) {
|
||||||
|
heightView.setText(size.y.toString())
|
||||||
|
height = size.y
|
||||||
|
}
|
||||||
|
|
||||||
|
if (binding.keepAspectRatio.isChecked) {
|
||||||
|
widthView.setText((height * ratio).toInt().toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialAlertDialogBuilder(activity)
|
||||||
|
.setPositiveButton(R.string.ok, null)
|
||||||
|
.setNegativeButton(R.string.cancel, null)
|
||||||
|
.apply {
|
||||||
|
activity.setupDialogStuff(view, this, R.string.resize_and_save) { alertDialog ->
|
||||||
|
alertDialog.showKeyboard(binding.resizeImageWidth)
|
||||||
|
alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val width = getViewValue(widthView)
|
||||||
|
val height = getViewValue(heightView)
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
activity.toast(R.string.invalid_values)
|
||||||
|
return@setOnClickListener
|
||||||
|
}
|
||||||
|
|
||||||
|
val newSize = Point(getViewValue(widthView), getViewValue(heightView))
|
||||||
|
callback(newSize)
|
||||||
|
alertDialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getViewValue(view: EditText): Int {
|
||||||
|
val textValue = view.value
|
||||||
|
return if (textValue.isEmpty()) 0 else textValue.toInt()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.view.View
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.medialib.models.FileDirItem
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileNotFoundException
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
fun Activity.setupDialogStuff(
|
||||||
|
view: View,
|
||||||
|
dialog: AlertDialog.Builder,
|
||||||
|
titleId: Int = 0,
|
||||||
|
titleText: String = "",
|
||||||
|
cancelOnTouchOutside: Boolean = true,
|
||||||
|
callback: ((alertDialog: AlertDialog) -> Unit)? = null
|
||||||
|
) {
|
||||||
|
if (isDestroyed || isFinishing) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.create().apply {
|
||||||
|
if (titleId != 0) {
|
||||||
|
setTitle(titleId)
|
||||||
|
} else if (titleText.isNotEmpty()) {
|
||||||
|
setTitle(titleText)
|
||||||
|
}
|
||||||
|
|
||||||
|
setView(view)
|
||||||
|
setCancelable(cancelOnTouchOutside)
|
||||||
|
if (!isFinishing) {
|
||||||
|
show()
|
||||||
|
}
|
||||||
|
|
||||||
|
callback?.invoke(this)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.view.WindowManager
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.widget.AppCompatEditText
|
||||||
|
|
||||||
|
// in dialogs, lets use findViewById, because while some dialogs use MyEditText, material theme dialogs use TextInputEditText so the system takes care of it
|
||||||
|
fun AlertDialog.showKeyboard(editText: AppCompatEditText) {
|
||||||
|
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||||
|
editText.apply {
|
||||||
|
requestFocus()
|
||||||
|
onGlobalLayout {
|
||||||
|
setSelection(text.toString().length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AlertDialog.hideKeyboard() {
|
||||||
|
window!!.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.os.Build
|
||||||
|
import androidx.annotation.ChecksSdkIntAtLeast
|
||||||
|
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N)
|
||||||
|
fun isNougatPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N_MR1)
|
||||||
|
fun isNougatMR1Plus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
||||||
|
fun isOreoPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O_MR1)
|
||||||
|
fun isOreoMr1Plus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
|
||||||
|
fun isPiePlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.Q)
|
||||||
|
fun isQPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
|
||||||
|
fun isRPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||||
|
|
||||||
|
@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
|
||||||
|
fun isSPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
|
||||||
|
|
||||||
|
//@ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
|
||||||
|
//fun isTiramisuPlus() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||||
|
|
||||||
|
fun ensureBackgroundThread(callback: () -> Unit) {
|
||||||
|
if (isOnMainThread()) {
|
||||||
|
Thread {
|
||||||
|
callback()
|
||||||
|
}.start()
|
||||||
|
} else {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,183 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.*
|
||||||
|
import android.media.MediaScannerConnection
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.provider.OpenableColumns
|
||||||
|
import android.widget.Toast
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.medialib.helpers.Config
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.*
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
val Context.config: Config get() = Config.newInstance(applicationContext)
|
||||||
|
|
||||||
|
private const val ANDROID_DATA_DIR = "/Android/data/"
|
||||||
|
private const val ANDROID_OBB_DIR = "/Android/obb/"
|
||||||
|
val DIRS_ACCESSIBLE_ONLY_WITH_SAF = listOf(ANDROID_DATA_DIR, ANDROID_OBB_DIR)
|
||||||
|
|
||||||
|
fun isOnMainThread() = Looper.myLooper() == Looper.getMainLooper()
|
||||||
|
|
||||||
|
|
||||||
|
fun Context.toast(id: Int, length: Int = Toast.LENGTH_SHORT) {
|
||||||
|
toast(getString(id), length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.toast(msg: String, length: Int = Toast.LENGTH_SHORT) {
|
||||||
|
try {
|
||||||
|
if (isOnMainThread()) {
|
||||||
|
doToast(this, msg, length)
|
||||||
|
} else {
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
doToast(this, msg, length)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.showErrorToast(msg: String, length: Int = Toast.LENGTH_LONG) {
|
||||||
|
toast(String.format(getString(R.string.error), msg), length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.showErrorToast(exception: Exception, length: Int = Toast.LENGTH_LONG) {
|
||||||
|
showErrorToast(exception.toString(), length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.copyToClipboard(text: String) {
|
||||||
|
val clip = ClipData.newPlainText(getString(R.string.simple_commons), text)
|
||||||
|
(getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager).setPrimaryClip(clip)
|
||||||
|
val toastText = String.format(getString(R.string.value_copied_to_clipboard_show), text)
|
||||||
|
toast(toastText)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getFilenameFromContentUri(uri: Uri): String? {
|
||||||
|
val projection = arrayOf(
|
||||||
|
OpenableColumns.DISPLAY_NAME
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val cursor = contentResolver.query(uri, projection, null, null, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
return cursor.getStringValue(OpenableColumns.DISPLAY_NAME)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// avoid calling this multiple times in row, it can delete whole folder contents
|
||||||
|
fun Context.rescanPaths(paths: List<String>, callback: (() -> Unit)? = null) {
|
||||||
|
if (paths.isEmpty()) {
|
||||||
|
callback?.invoke()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (path in paths) {
|
||||||
|
Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE).apply {
|
||||||
|
data = Uri.fromFile(File(path))
|
||||||
|
sendBroadcast(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cnt = paths.size
|
||||||
|
MediaScannerConnection.scanFile(applicationContext, paths.toTypedArray(), null) { s, uri ->
|
||||||
|
if (--cnt == 0) {
|
||||||
|
callback?.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// some helper functions were taken from https://github.com/iPaulPro/aFileChooser/blob/master/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
|
||||||
|
fun Context.getRealPathFromURI(uri: Uri): String? {
|
||||||
|
if (uri.scheme == "file") {
|
||||||
|
return uri.path
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDownloadsDocument(uri)) {
|
||||||
|
val id = DocumentsContract.getDocumentId(uri)
|
||||||
|
if (id.areDigitsOnly()) {
|
||||||
|
val newUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), id.toLong())
|
||||||
|
val path = getDataColumn(newUri)
|
||||||
|
if (path != null) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isExternalStorageDocument(uri)) {
|
||||||
|
val documentId = DocumentsContract.getDocumentId(uri)
|
||||||
|
val parts = documentId.split(":")
|
||||||
|
if (parts[0].equals("primary", true)) {
|
||||||
|
return "${Environment.getExternalStorageDirectory().absolutePath}/${parts[1]}"
|
||||||
|
}
|
||||||
|
} else if (isMediaDocument(uri)) {
|
||||||
|
val documentId = DocumentsContract.getDocumentId(uri)
|
||||||
|
val split = documentId.split(":").dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
val type = split[0]
|
||||||
|
|
||||||
|
val contentUri = when (type) {
|
||||||
|
"video" -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
||||||
|
"audio" -> MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
|
||||||
|
else -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
|
||||||
|
}
|
||||||
|
|
||||||
|
val selection = "_id=?"
|
||||||
|
val selectionArgs = arrayOf(split[1])
|
||||||
|
val path = getDataColumn(contentUri, selection, selectionArgs)
|
||||||
|
if (path != null) {
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return getDataColumn(uri)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getDataColumn(uri: Uri, selection: String? = null, selectionArgs: Array<String>? = null): String? {
|
||||||
|
try {
|
||||||
|
val projection = arrayOf(MediaStore.Files.FileColumns.DATA)
|
||||||
|
val cursor = contentResolver.query(uri, projection, selection, selectionArgs, null)
|
||||||
|
cursor?.use {
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
val data = cursor.getStringValue(MediaStore.Files.FileColumns.DATA)
|
||||||
|
if (data != "null") {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Context.getInternalStoragePath() =
|
||||||
|
if (File("/storage/emulated/0").exists()) "/storage/emulated/0" else Environment.getExternalStorageDirectory().absolutePath.trimEnd('/')
|
||||||
|
|
||||||
|
fun Context.getCurrentFormattedDateTime(): String {
|
||||||
|
val simpleDateFormat = SimpleDateFormat("yyyy_MM_dd_HH_mm_ss", Locale.getDefault())
|
||||||
|
return simpleDateFormat.format(Date(System.currentTimeMillis()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isDownloadsDocument(uri: Uri) = uri.authority == "com.android.providers.downloads.documents"
|
||||||
|
|
||||||
|
private fun isExternalStorageDocument(uri: Uri) = uri.authority == "com.android.externalstorage.documents"
|
||||||
|
|
||||||
|
private fun isMediaDocument(uri: Uri) = uri.authority == "com.android.providers.media.documents"
|
||||||
|
|
||||||
|
private fun doToast(context: Context, message: String, length: Int) {
|
||||||
|
if (context is Activity) {
|
||||||
|
if (!context.isFinishing && !context.isDestroyed) {
|
||||||
|
Toast.makeText(context, message, length).show()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, message, length).show()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.database.Cursor
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getStringValue(key: String) = getString(getColumnIndex(key))
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getStringValueOrNull(key: String) = if (isNull(getColumnIndex(key))) null else getString(getColumnIndex(key))
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getIntValue(key: String) = getInt(getColumnIndex(key))
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getIntValueOrNull(key: String) = if (isNull(getColumnIndex(key))) null else getInt(getColumnIndex(key))
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getLongValue(key: String) = getLong(getColumnIndex(key))
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getLongValueOrNull(key: String) = if (isNull(getColumnIndex(key))) null else getLong(getColumnIndex(key))
|
||||||
|
|
||||||
|
@SuppressLint("Range")
|
||||||
|
fun Cursor.getBlobValue(key: String) = getBlob(getColumnIndex(key))
|
|
@ -0,0 +1,22 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.Spannable
|
||||||
|
import android.text.SpannableString
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.text.style.BackgroundColorSpan
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.core.graphics.ColorUtils
|
||||||
|
|
||||||
|
val EditText.value: String get() = text.toString().trim()
|
||||||
|
|
||||||
|
fun EditText.onTextChangeListener(onTextChangedAction: (newText: String) -> Unit) = addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
onTextChangedAction(s.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||||
|
})
|
|
@ -0,0 +1,58 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import androidx.exifinterface.media.ExifInterface
|
||||||
|
import java.lang.reflect.Field
|
||||||
|
import java.lang.reflect.Modifier
|
||||||
|
|
||||||
|
fun ExifInterface.copyNonDimensionAttributesTo(destination: ExifInterface) {
|
||||||
|
val attributes = ExifInterfaceAttributes.AllNonDimensionAttributes
|
||||||
|
|
||||||
|
attributes.forEach {
|
||||||
|
val value = getAttribute(it)
|
||||||
|
if (value != null) {
|
||||||
|
destination.setAttribute(it, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
destination.saveAttributes()
|
||||||
|
} catch (ignored: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ExifInterfaceAttributes {
|
||||||
|
companion object {
|
||||||
|
val AllNonDimensionAttributes = getAllNonDimensionExifAttributes()
|
||||||
|
|
||||||
|
private fun getAllNonDimensionExifAttributes(): List<String> {
|
||||||
|
val tagFields = ExifInterface::class.java.fields.filter { field -> isExif(field) }
|
||||||
|
|
||||||
|
val excludeAttributes = arrayListOf(
|
||||||
|
ExifInterface.TAG_IMAGE_LENGTH,
|
||||||
|
ExifInterface.TAG_IMAGE_WIDTH,
|
||||||
|
ExifInterface.TAG_PIXEL_X_DIMENSION,
|
||||||
|
ExifInterface.TAG_PIXEL_Y_DIMENSION,
|
||||||
|
ExifInterface.TAG_THUMBNAIL_IMAGE_LENGTH,
|
||||||
|
ExifInterface.TAG_THUMBNAIL_IMAGE_WIDTH,
|
||||||
|
ExifInterface.TAG_ORIENTATION
|
||||||
|
)
|
||||||
|
|
||||||
|
return tagFields
|
||||||
|
.map { tagField -> tagField.get(null) as String }
|
||||||
|
.filter { x -> !excludeAttributes.contains(x) }
|
||||||
|
.distinct()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isExif(field: Field): Boolean {
|
||||||
|
return field.type == String::class.java &&
|
||||||
|
isPublicStaticFinal(field.modifiers) &&
|
||||||
|
field.name.startsWith("TAG_")
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val publicStaticFinal = Modifier.PUBLIC or Modifier.STATIC or Modifier.FINAL
|
||||||
|
|
||||||
|
private fun isPublicStaticFinal(modifiers: Int): Boolean {
|
||||||
|
return modifiers and publicStaticFinal > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.graphics.PorterDuff
|
||||||
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.annotation.DrawableRes
|
||||||
|
|
||||||
|
fun ImageView.setFillWithStroke(fillColor: Int, backgroundColor: Int, drawRectangle: Boolean = false) {
|
||||||
|
GradientDrawable().apply {
|
||||||
|
shape = if (drawRectangle) GradientDrawable.RECTANGLE else GradientDrawable.OVAL
|
||||||
|
setColor(fillColor)
|
||||||
|
background = this
|
||||||
|
|
||||||
|
if (backgroundColor == fillColor || fillColor == -2 && backgroundColor == -1) {
|
||||||
|
val strokeColor = backgroundColor.getContrastColor().adjustAlpha(0.5f)
|
||||||
|
setStroke(2, strokeColor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ImageView.applyColorFilter(color: Int) = setColorFilter(color, PorterDuff.Mode.SRC_IN)
|
||||||
|
|
||||||
|
fun ImageView.setImageResourceOrBeGone(@DrawableRes imageRes: Int?) {
|
||||||
|
if (imageRes != null) {
|
||||||
|
beVisible()
|
||||||
|
setImageResource(imageRes)
|
||||||
|
} else {
|
||||||
|
beGone()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import eu.siacs.conversations.medialib.helpers.DARK_GREY
|
||||||
|
|
||||||
|
fun Int.getContrastColor(): Int {
|
||||||
|
val y = (299 * Color.red(this) + 587 * Color.green(this) + 114 * Color.blue(this)) / 1000
|
||||||
|
return if (y >= 149 && this != Color.BLACK) DARK_GREY else Color.WHITE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Int.toHex() = String.format("#%06X", 0xFFFFFF and this).toUpperCase()
|
||||||
|
|
||||||
|
fun Int.adjustAlpha(factor: Float): Int {
|
||||||
|
val alpha = Math.round(Color.alpha(this) * factor)
|
||||||
|
val red = Color.red(this)
|
||||||
|
val green = Color.green(this)
|
||||||
|
val blue = Color.blue(this)
|
||||||
|
return Color.argb(alpha, red, green, blue)
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.widget.SeekBar
|
||||||
|
|
||||||
|
fun SeekBar.onSeekBarChangeListener(seekBarChangeListener: (progress: Int) -> Unit) = setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||||
|
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||||
|
seekBarChangeListener(progress)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||||
|
})
|
|
@ -0,0 +1,17 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
|
||||||
|
fun String.getFilenameFromPath() = substring(lastIndexOf("/") + 1)
|
||||||
|
|
||||||
|
fun String.getFilenameExtension() = substring(lastIndexOf(".") + 1)
|
||||||
|
|
||||||
|
fun String.getCompressionFormat() = when (getFilenameExtension().lowercase()) {
|
||||||
|
"png" -> Bitmap.CompressFormat.PNG
|
||||||
|
"webp" -> Bitmap.CompressFormat.WEBP
|
||||||
|
else -> Bitmap.CompressFormat.JPEG
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.areDigitsOnly() = matches(Regex("[0-9]+"))
|
||||||
|
|
||||||
|
fun String.getParentPath() = removeSuffix("/${getFilenameFromPath()}")
|
|
@ -0,0 +1,40 @@
|
||||||
|
package eu.siacs.conversations.medialib.extensions
|
||||||
|
|
||||||
|
import android.view.HapticFeedbackConstants
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewTreeObserver
|
||||||
|
|
||||||
|
fun View.beInvisibleIf(beInvisible: Boolean) = if (beInvisible) beInvisible() else beVisible()
|
||||||
|
|
||||||
|
fun View.beVisibleIf(beVisible: Boolean) = if (beVisible) beVisible() else beGone()
|
||||||
|
|
||||||
|
fun View.beGoneIf(beGone: Boolean) = beVisibleIf(!beGone)
|
||||||
|
|
||||||
|
fun View.beInvisible() {
|
||||||
|
visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.beVisible() {
|
||||||
|
visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.beGone() {
|
||||||
|
visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.onGlobalLayout(callback: () -> Unit) {
|
||||||
|
viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
if (viewTreeObserver != null) {
|
||||||
|
viewTreeObserver.removeOnGlobalLayoutListener(this)
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fun View.isVisible() = visibility == View.VISIBLE
|
||||||
|
|
||||||
|
fun View.isInvisible() = visibility == View.INVISIBLE
|
||||||
|
|
||||||
|
fun View.isGone() = visibility == View.GONE
|
|
@ -0,0 +1,50 @@
|
||||||
|
package eu.siacs.conversations.medialib.helpers
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class Config(private val context: Context) {
|
||||||
|
protected val prefs = context.getSharedPreferences("media_config_prefs", Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance(context: Context) = Config(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// color picker last used colors
|
||||||
|
var colorPickerRecentColors: LinkedList<Int>
|
||||||
|
get(): LinkedList<Int> {
|
||||||
|
val defaultList = arrayListOf(
|
||||||
|
Color.RED,
|
||||||
|
Color.BLUE,
|
||||||
|
Color.GREEN,
|
||||||
|
Color.YELLOW,
|
||||||
|
Color.BLACK
|
||||||
|
)
|
||||||
|
return LinkedList(prefs.getString(COLOR_PICKER_RECENT_COLORS, null)?.lines()?.map { it.toInt() } ?: defaultList)
|
||||||
|
}
|
||||||
|
set(recentColors) = prefs.edit().putString(COLOR_PICKER_RECENT_COLORS, recentColors.joinToString(separator = "\n")).apply()
|
||||||
|
|
||||||
|
|
||||||
|
var lastEditorCropAspectRatio: Int
|
||||||
|
get() = prefs.getInt(LAST_EDITOR_CROP_ASPECT_RATIO, ASPECT_RATIO_FREE)
|
||||||
|
set(lastEditorCropAspectRatio) = prefs.edit().putInt(LAST_EDITOR_CROP_ASPECT_RATIO, lastEditorCropAspectRatio).apply()
|
||||||
|
|
||||||
|
var lastEditorCropOtherAspectRatioX: Float
|
||||||
|
get() = prefs.getFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, 2f)
|
||||||
|
set(lastEditorCropOtherAspectRatioX) = prefs.edit().putFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X, lastEditorCropOtherAspectRatioX).apply()
|
||||||
|
|
||||||
|
var lastEditorCropOtherAspectRatioY: Float
|
||||||
|
get() = prefs.getFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y, 1f)
|
||||||
|
set(lastEditorCropOtherAspectRatioY) = prefs.edit().putFloat(LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y, lastEditorCropOtherAspectRatioY).apply()
|
||||||
|
|
||||||
|
var lastEditorDrawColor: Int
|
||||||
|
get() = prefs.getInt(LAST_EDITOR_DRAW_COLOR, context.getColor(R.color.editor_draw_default_color))
|
||||||
|
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()
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package eu.siacs.conversations.medialib.helpers
|
||||||
|
|
||||||
|
// shared preferences
|
||||||
|
const val LAST_EDITOR_CROP_ASPECT_RATIO = "last_editor_crop_aspect_ratio"
|
||||||
|
const val LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_X = "last_editor_crop_other_aspect_ratio_x_2"
|
||||||
|
const val LAST_EDITOR_CROP_OTHER_ASPECT_RATIO_Y = "last_editor_crop_other_aspect_ratio_y_2"
|
||||||
|
const val LAST_EDITOR_DRAW_COLOR = "last_editor_draw_color"
|
||||||
|
const val LAST_EDITOR_BRUSH_SIZE = "last_editor_brush_size"
|
||||||
|
|
||||||
|
const val REAL_FILE_PATH = "real_file_path_2"
|
||||||
|
|
||||||
|
const val COLOR_PICKER_RECENT_COLORS = "color_picker_recent_colors"
|
||||||
|
|
||||||
|
val DARK_GREY = 0xFF333333.toInt()
|
||||||
|
|
||||||
|
// aspect ratios used at the editor for cropping
|
||||||
|
const val ASPECT_RATIO_FREE = 0
|
||||||
|
const val ASPECT_RATIO_ONE_ONE = 1
|
||||||
|
const val ASPECT_RATIO_FOUR_THREE = 2
|
||||||
|
const val ASPECT_RATIO_SIXTEEN_NINE = 3
|
||||||
|
const val ASPECT_RATIO_OTHER = 4
|
|
@ -0,0 +1,27 @@
|
||||||
|
package eu.siacs.conversations.medialib.helpers
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import eu.siacs.conversations.medialib.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()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package eu.siacs.conversations.medialib.models
|
||||||
|
|
||||||
|
import eu.siacs.conversations.medialib.extensions.getParentPath
|
||||||
|
|
||||||
|
open class FileDirItem(
|
||||||
|
val path: String,
|
||||||
|
val name: String = "",
|
||||||
|
var isDirectory: Boolean = false,
|
||||||
|
var children: Int = 0,
|
||||||
|
var size: Long = 0L,
|
||||||
|
var modified: Long = 0L,
|
||||||
|
var mediaStoreId: Long = 0L
|
||||||
|
) {
|
||||||
|
override fun toString() =
|
||||||
|
"FileDirItem(path=$path, name=$name, isDirectory=$isDirectory, children=$children, size=$size, modified=$modified, mediaStoreId=$mediaStoreId)"
|
||||||
|
|
||||||
|
fun getParentPath() = path.getParentPath()
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
package eu.siacs.conversations.medialib.models
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import com.zomato.photofilters.imageprocessors.Filter
|
||||||
|
|
||||||
|
data class FilterItem(var bitmap: Bitmap, val filter: Filter)
|
|
@ -0,0 +1,5 @@
|
||||||
|
package eu.siacs.conversations.medialib.models
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
|
||||||
|
data class PaintOptions(var color: Int = Color.BLACK, var strokeWidth: Float = 5f)
|
|
@ -0,0 +1,33 @@
|
||||||
|
package eu.siacs.conversations.medialib.views
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.*
|
||||||
|
import android.graphics.Shader.TileMode
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.View
|
||||||
|
|
||||||
|
class ColorPickerSquare(context: Context, attrs: AttributeSet) : View(context, attrs) {
|
||||||
|
var paint: Paint? = null
|
||||||
|
var luar: Shader = LinearGradient(0f, 0f, 0f, measuredHeight.toFloat(), Color.WHITE, Color.BLACK, Shader.TileMode.CLAMP)
|
||||||
|
val color = floatArrayOf(1f, 1f, 1f)
|
||||||
|
|
||||||
|
@SuppressLint("DrawAllocation")
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
if (paint == null) {
|
||||||
|
paint = Paint()
|
||||||
|
luar = LinearGradient(0f, 0f, 0f, measuredHeight.toFloat(), Color.WHITE, Color.BLACK, TileMode.CLAMP)
|
||||||
|
}
|
||||||
|
val rgb = Color.HSVToColor(color)
|
||||||
|
val dalam = LinearGradient(0f, 0f, measuredWidth.toFloat(), 0f, Color.WHITE, rgb, TileMode.CLAMP)
|
||||||
|
val shader = ComposeShader(luar, dalam, PorterDuff.Mode.MULTIPLY)
|
||||||
|
paint!!.shader = shader
|
||||||
|
canvas.drawRect(0f, 0f, measuredWidth.toFloat(), measuredHeight.toFloat(), paint!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setHue(hue: Float) {
|
||||||
|
color[0] = hue
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,145 @@
|
||||||
|
package eu.siacs.conversations.medialib.views
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.*
|
||||||
|
import android.util.AttributeSet
|
||||||
|
import android.view.MotionEvent
|
||||||
|
import android.view.View
|
||||||
|
import eu.siacs.conversations.R
|
||||||
|
import eu.siacs.conversations.medialib.models.PaintOptions
|
||||||
|
|
||||||
|
class EditorDrawCanvas(context: Context, attrs: AttributeSet) : View(context, attrs) {
|
||||||
|
private var mCurX = 0f
|
||||||
|
private var mCurY = 0f
|
||||||
|
private var mStartX = 0f
|
||||||
|
private var mStartY = 0f
|
||||||
|
private var mColor = 0
|
||||||
|
private var mWasMultitouch = false
|
||||||
|
|
||||||
|
private var mPaths = LinkedHashMap<Path, PaintOptions>()
|
||||||
|
private var mPaint = Paint()
|
||||||
|
private var mPath = Path()
|
||||||
|
private var mPaintOptions = PaintOptions()
|
||||||
|
|
||||||
|
private var backgroundBitmap: Bitmap? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
mColor = context.getColor(R.color.editor_draw_default_color)
|
||||||
|
mPaint.apply {
|
||||||
|
color = mColor
|
||||||
|
style = Paint.Style.STROKE
|
||||||
|
strokeJoin = Paint.Join.ROUND
|
||||||
|
strokeCap = Paint.Cap.ROUND
|
||||||
|
strokeWidth = 40f
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas) {
|
||||||
|
super.onDraw(canvas)
|
||||||
|
canvas.save()
|
||||||
|
|
||||||
|
if (backgroundBitmap != null) {
|
||||||
|
canvas.drawBitmap(backgroundBitmap!!, 0f, 0f, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
for ((key, value) in mPaths) {
|
||||||
|
changePaint(value)
|
||||||
|
canvas.drawPath(key, mPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
changePaint(mPaintOptions)
|
||||||
|
canvas.drawPath(mPath, mPaint)
|
||||||
|
canvas.restore()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||||
|
val x = event.x
|
||||||
|
val y = event.y
|
||||||
|
|
||||||
|
when (event.action and MotionEvent.ACTION_MASK) {
|
||||||
|
MotionEvent.ACTION_DOWN -> {
|
||||||
|
mWasMultitouch = false
|
||||||
|
mStartX = x
|
||||||
|
mStartY = y
|
||||||
|
actionDown(x, y)
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
if (event.pointerCount == 1 && !mWasMultitouch) {
|
||||||
|
actionMove(x, y)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> actionUp()
|
||||||
|
MotionEvent.ACTION_POINTER_DOWN -> mWasMultitouch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidate()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun actionDown(x: Float, y: Float) {
|
||||||
|
mPath.reset()
|
||||||
|
mPath.moveTo(x, y)
|
||||||
|
mCurX = x
|
||||||
|
mCurY = y
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun actionMove(x: Float, y: Float) {
|
||||||
|
mPath.quadTo(mCurX, mCurY, (x + mCurX) / 2, (y + mCurY) / 2)
|
||||||
|
mCurX = x
|
||||||
|
mCurY = y
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun actionUp() {
|
||||||
|
if (!mWasMultitouch) {
|
||||||
|
mPath.lineTo(mCurX, mCurY)
|
||||||
|
|
||||||
|
// draw a dot on click
|
||||||
|
if (mStartX == mCurX && mStartY == mCurY) {
|
||||||
|
mPath.lineTo(mCurX, mCurY + 2)
|
||||||
|
mPath.lineTo(mCurX + 1, mCurY + 2)
|
||||||
|
mPath.lineTo(mCurX + 1, mCurY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mPaths[mPath] = mPaintOptions
|
||||||
|
mPath = Path()
|
||||||
|
mPaintOptions = PaintOptions(mPaintOptions.color, mPaintOptions.strokeWidth)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun changePaint(paintOptions: PaintOptions) {
|
||||||
|
mPaint.color = paintOptions.color
|
||||||
|
mPaint.strokeWidth = paintOptions.strokeWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateColor(newColor: Int) {
|
||||||
|
mPaintOptions.color = newColor
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBrushSize(newBrushSize: Int) {
|
||||||
|
mPaintOptions.strokeWidth = resources.getDimension(R.dimen.full_brush_size) * (newBrushSize / 100f)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBackgroundBitmap(bitmap: Bitmap) {
|
||||||
|
backgroundBitmap = bitmap
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getBitmap(): Bitmap {
|
||||||
|
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(bitmap)
|
||||||
|
canvas.drawColor(Color.WHITE)
|
||||||
|
draw(canvas)
|
||||||
|
return bitmap
|
||||||
|
}
|
||||||
|
|
||||||
|
fun undo() {
|
||||||
|
if (mPaths.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastKey = mPaths.keys.lastOrNull()
|
||||||
|
mPaths.remove(lastKey)
|
||||||
|
invalidate()
|
||||||
|
}
|
||||||
|
}
|
|
@ -98,6 +98,7 @@ import eu.siacs.conversations.entities.ReadByMarker;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
import eu.siacs.conversations.entities.TransferablePlaceholder;
|
||||||
import eu.siacs.conversations.http.HttpDownloadConnection;
|
import eu.siacs.conversations.http.HttpDownloadConnection;
|
||||||
|
import eu.siacs.conversations.medialib.activities.EditActivity;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.services.MessageArchiveService;
|
import eu.siacs.conversations.services.MessageArchiveService;
|
||||||
import eu.siacs.conversations.services.QuickConversationsService;
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
|
@ -163,6 +164,7 @@ public class ConversationFragment extends XmppFragment
|
||||||
public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
|
public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
|
||||||
public static final int ATTACHMENT_CHOICE_INVALID = 0x0306;
|
public static final int ATTACHMENT_CHOICE_INVALID = 0x0306;
|
||||||
public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307;
|
public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307;
|
||||||
|
public static final int ATTACHMENT_CHOICE_EDIT_PHOTO = 0x0308;
|
||||||
|
|
||||||
public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action";
|
public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action";
|
||||||
public static final String STATE_CONVERSATION_UUID =
|
public static final String STATE_CONVERSATION_UUID =
|
||||||
|
@ -1032,14 +1034,25 @@ public class ConversationFragment extends XmppFragment
|
||||||
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
|
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
|
||||||
final List<Attachment> imageUris =
|
final List<Attachment> imageUris =
|
||||||
Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE);
|
Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE);
|
||||||
mediaPreviewAdapter.addMediaPreviews(imageUris);
|
if (imageUris.size() == 1) {
|
||||||
toggleInputMethod();
|
editImage(imageUris.get(0).getUri());
|
||||||
|
} else {
|
||||||
|
mediaPreviewAdapter.addMediaPreviews(imageUris);
|
||||||
|
toggleInputMethod();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ATTACHMENT_CHOICE_TAKE_PHOTO:
|
case ATTACHMENT_CHOICE_TAKE_PHOTO:
|
||||||
final Uri takePhotoUri = pendingTakePhotoUri.pop();
|
final Uri takePhotoUri = pendingTakePhotoUri.pop();
|
||||||
if (takePhotoUri != null) {
|
if (takePhotoUri != null) {
|
||||||
mediaPreviewAdapter.addMediaPreviews(
|
editImage(takePhotoUri);
|
||||||
Attachment.of(getActivity(), takePhotoUri, Attachment.Type.IMAGE));
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "lost take photo uri. unable to to attach");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ATTACHMENT_CHOICE_EDIT_PHOTO:
|
||||||
|
final Uri editedUriPhoto = data.getParcelableExtra(EditActivity.KEY_EDITED_URI);
|
||||||
|
if (editedUriPhoto != null) {
|
||||||
|
mediaPreviewAdapter.replaceOrAddMediaPreview(data.getData(), editedUriPhoto, Attachment.Type.IMAGE);
|
||||||
toggleInputMethod();
|
toggleInputMethod();
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "lost take photo uri. unable to to attach");
|
Log.d(Config.LOGTAG, "lost take photo uri. unable to to attach");
|
||||||
|
@ -1085,6 +1098,13 @@ public class ConversationFragment extends XmppFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void editImage(Uri uri) {
|
||||||
|
Intent intent = new Intent(activity, EditActivity.class);
|
||||||
|
intent.setData(uri);
|
||||||
|
intent.putExtra(EditActivity.KEY_CHAT_NAME, conversation.getName());
|
||||||
|
startActivityForResult(intent, ATTACHMENT_CHOICE_EDIT_PHOTO);
|
||||||
|
}
|
||||||
|
|
||||||
private void commitAttachments() {
|
private void commitAttachments() {
|
||||||
final List<Attachment> attachments = mediaPreviewAdapter.getAttachments();
|
final List<Attachment> attachments = mediaPreviewAdapter.getAttachments();
|
||||||
if (anyNeedsExternalStoragePermission(attachments)
|
if (anyNeedsExternalStoragePermission(attachments)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
@ -22,7 +23,6 @@ import java.lang.ref.WeakReference;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.RejectedExecutionException;
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.MediaPreviewBinding;
|
import eu.siacs.conversations.databinding.MediaPreviewBinding;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
@ -65,7 +65,13 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
|
||||||
notifyItemRemoved(pos);
|
notifyItemRemoved(pos);
|
||||||
conversationFragment.toggleInputMethod();
|
conversationFragment.toggleInputMethod();
|
||||||
});
|
});
|
||||||
holder.binding.mediaPreview.setOnClickListener(v -> view(context, attachment));
|
holder.binding.mediaPreview.setOnClickListener(v -> {
|
||||||
|
if (attachment.getType() == Attachment.Type.IMAGE) {
|
||||||
|
conversationFragment.editImage(attachment.getUri());
|
||||||
|
} else {
|
||||||
|
view(context, attachment);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void view(final Context context, Attachment attachment) {
|
private static void view(final Context context, Attachment attachment) {
|
||||||
|
@ -82,6 +88,23 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void replaceOrAddMediaPreview(Uri originalUri, Uri editedUri, Attachment.Type type) {
|
||||||
|
boolean replaced = false;
|
||||||
|
for(int i = 0; i < mediaPreviews.size(); i++) {
|
||||||
|
Attachment current = mediaPreviews.get(i);
|
||||||
|
if (current.getUri().equals(originalUri)) {
|
||||||
|
replaced = true;
|
||||||
|
mediaPreviews.set(i, Attachment.of(conversationFragment.getActivity(), editedUri, current.getType()).get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replaced) {
|
||||||
|
mediaPreviews.addAll(Attachment.of(conversationFragment.getActivity(), editedUri, type));
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
|
||||||
public void addMediaPreviews(List<Attachment> attachments) {
|
public void addMediaPreviews(List<Attachment> attachments) {
|
||||||
this.mediaPreviews.addAll(attachments);
|
this.mediaPreviews.addAll(attachments);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
|
|
BIN
src/main/res/drawable-nodpi/img_color_picker_hue.png
Normal file
BIN
src/main/res/drawable-nodpi/img_color_picker_hue.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 768 B |
9
src/main/res/drawable/circle_background.xml
Normal file
9
src/main/res/drawable/circle_background.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<solid
|
||||||
|
android:color="?colorPrimary"/>
|
||||||
|
|
||||||
|
</shape>
|
10
src/main/res/drawable/circle_stroke_white.xml
Normal file
10
src/main/res/drawable/circle_stroke_white.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="oval">
|
||||||
|
|
||||||
|
<stroke
|
||||||
|
android:width="2dp"
|
||||||
|
android:color="@color/white"/>
|
||||||
|
|
||||||
|
</shape>
|
13
src/main/res/drawable/color_picker_circle.xml
Normal file
13
src/main/res/drawable/color_picker_circle.xml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="ring" android:thicknessRatio="1" android:useLevel="false">
|
||||||
|
<stroke android:width="4px" android:color="@android:color/white"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<shape android:shape="ring" android:thicknessRatio="1" android:useLevel="false">
|
||||||
|
<stroke android:width="2px" android:color="@android:color/black"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
7
src/main/res/drawable/gradient_background.xml
Normal file
7
src/main/res/drawable/gradient_background.xml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<gradient
|
||||||
|
android:angle="90"
|
||||||
|
android:endColor="@android:color/transparent"
|
||||||
|
android:startColor="#CC000000"/>
|
||||||
|
</shape>
|
3
src/main/res/drawable/ic_arrow_right_vector.xml
Normal file
3
src/main/res/drawable/ic_arrow_right_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<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="M5 13h11.17l-4.88 4.88c-0.39 0.39-0.39 1.03 0 1.42 0.39 0.39 1.02 0.39 1.41 0l6.59-6.59c0.39-0.39 0.39-1.02 0-1.41l-6.58-6.6c-0.39-0.39-1.02-0.39-1.41 0-0.39 0.39-0.39 1.02 0 1.41L16.17 11H5c-0.55 0-1 0.45-1 1s0.45 1 1 1z"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_aspect_ratio_vector.xml
Normal file
3
src/main/res/drawable/ic_aspect_ratio_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<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="M18 12c-0.55 0-1 0.45-1 1v2h-2c-0.55 0-1 0.45-1 1s0.45 1 1 1h3c0.55 0 1-0.45 1-1v-3c0-0.55-0.45-1-1-1zM7 9h2c0.55 0 1-0.45 1-1S9.55 7 9 7H6C5.45 7 5 7.45 5 8v3c0 0.55 0.45 1 1 1s1-0.45 1-1V9zm14-6H3C1.9 3 1 3.9 1 5v14c0 1.1 0.9 2 2 2h18c1.1 0 2-0.9 2-2V5c0-1.1-0.9-2-2-2zm-1 16.01H4c-0.55 0-1-0.45-1-1V5.99c0-0.55 0.45-1 1-1h16c0.55 0 1 0.45 1 1v12.02c0 0.55-0.45 1-1 1z"/>
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M6.102 1.09c-0.804 0.804-0.804 2.103 0 2.908L14.105 12l-8.003 8.002c-0.804 0.805-0.804 2.104 0 2.909 0.805 0.804 2.104 0.804 2.908 0l9.468-9.468c0.804-0.804 0.804-2.103 0-2.908L9.01 1.07C8.227 0.286 6.907 0.286 6.102 1.09z" android:strokeWidth="2.06257677" android:fillColor="#FFFFFFFF"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_crop_rotate_vector.xml
Normal file
3
src/main/res/drawable/ic_crop_rotate_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<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="M16 9v5h2V8c0-1.1-0.9-2-2-2h-6v2h5c0.55 0 1 0.45 1 1zm3 7H9c-0.55 0-1-0.45-1-1V5c0-0.55-0.45-1-1-1S6 4.45 6 5v1H5C4.45 6 4 6.45 4 7s0.45 1 1 1h1v8c0 1.1 0.9 2 2 2h8v1c0 0.55 0.45 1 1 1s1-0.45 1-1v-1h1c0.55 0 1-0.45 1-1s-0.45-1-1-1zM17.66 1.4c-1.67-0.89-3.83-1.51-6.27-1.36l3.81 3.81 1.33-1.33c3.09 1.46 5.34 4.37 5.89 7.86 0.06 0.41 0.44 0.69 0.86 0.62 0.41-0.06 0.69-0.45 0.62-0.86-0.6-3.8-2.96-7-6.24-8.74zM7.47 21.49c-3.09-1.46-5.34-4.37-5.89-7.86-0.06-0.41-0.44-0.69-0.86-0.62-0.41 0.06-0.69 0.45-0.62 0.86 0.6 3.81 2.96 7.01 6.24 8.75 1.67 0.89 3.83 1.51 6.27 1.36L8.8 20.16l-1.33 1.33z"/>
|
||||||
|
</vector>
|
9
src/main/res/drawable/ic_done_24dp.xml
Normal file
9
src/main/res/drawable/ic_done_24dp.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="960"
|
||||||
|
android:viewportHeight="960">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M382,727.65L146.35,492L211.24,427.11L382,597.87L748.76,231.11L813.65,296L382,727.65Z"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_draw_vector.xml
Normal file
3
src/main/res/drawable/ic_draw_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<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="M18.85 10.39l1.06-1.06c0.78-0.78 0.78-2.05 0-2.83L18.5 5.09c-0.78-0.78-2.05-0.78-2.83 0l-1.06 1.06 4.24 4.24zm-5.66-2.83l-9.05 9.05C4.05 16.7 4 16.83 4 16.96v3.54C4 20.78 4.22 21 4.5 21h3.54c0.13 0 0.26-0.05 0.35-0.15l9.05-9.05-4.25-4.24zM19 17.5c0 2.19-2.54 3.5-5 3.5-0.55 0-1-0.45-1-1s0.45-1 1-1c1.54 0 3-0.73 3-1.5 0-0.47-0.48-0.87-1.23-1.2l1.48-1.48C18.32 15.45 19 16.29 19 17.5zM4.58 13.35C3.61 12.79 3 12.06 3 11c0-1.8 1.89-2.63 3.56-3.36C7.59 7.18 9 6.56 9 6c0-0.41-0.78-1-2-1-1.26 0-1.8 0.61-1.83 0.64-0.35 0.41-0.98 0.46-1.4 0.12-0.41-0.34-0.49-0.95-0.15-1.38C3.73 4.24 4.76 3 7 3s4 1.32 4 3c0 1.87-1.93 2.72-3.64 3.47C6.42 9.88 5 10.5 5 11c0 0.31 0.43 0.6 1.07 0.86l-1.49 1.49z"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_flip_horizontally_vector.xml
Normal file
3
src/main/res/drawable/ic_flip_horizontally_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M15 21h2v-2h-2v2zm4-12h2V7h-2v2zM3 5v14c0 1.1 0.9 2 2 2h3c0.55 0 1-0.45 1-1s-0.45-1-1-1H6c-0.55 0-1-0.45-1-1V6c0-0.55 0.45-1 1-1h2c0.55 0 1-0.45 1-1S8.55 3 8 3H5C3.9 3 3 3.9 3 5zm16-2v2h2c0-1.1-0.9-2-2-2zm-7 20c0.55 0 1-0.45 1-1V2c0-0.55-0.45-1-1-1s-1 0.45-1 1v20c0 0.55 0.45 1 1 1zm7-6h2v-2h-2v2zM15 5h2V3h-2v2zm4 8h2v-2h-2v2zm0 8c1.1 0 2-0.9 2-2h-2v2z" android:fillColor="#FFFFFF"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_flip_vertically_vector.xml
Normal file
3
src/main/res/drawable/ic_flip_vertically_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M21 9V7h-2v2zM9 5V3H7v2zM5 21h14c1.1 0 2-0.9 2-2v-3c0-0.55-0.45-1-1-1s-1 0.45-1 1v2c0 0.55-0.45 1-1 1H6c-0.55 0-1-0.45-1-1v-2c0-0.55-0.45-1-1-1s-1 0.45-1 1v3c0 1.1 0.9 2 2 2zM3 5h2V3C3.9 3 3 3.9 3 5zm20 7c0-0.55-0.45-1-1-1H2c-0.55 0-1 0.45-1 1s0.45 1 1 1h20c0.55 0 1-0.45 1-1zm-6-7V3h-2v2zM5 9V7H3v2zm8-4V3h-2v2zm8 0c0-1.1-0.9-2-2-2v2z" android:fillColor="#FFFFFF"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_minimize_vector.xml
Normal file
3
src/main/res/drawable/ic_minimize_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M3 8c0 0.55 0.45 1 1 1h4c0.55 0 1-0.45 1-1V4c0-0.55-0.45-1-1-1S7 3.45 7 4v1.59L4.62 3.21c-0.39-0.39-1.02-0.39-1.41 0-0.39 0.39-0.39 1.02 0 1.41L5.59 7H4C3.45 7 3 7.45 3 8zm17-1h-1.59l2.38-2.38c0.39-0.39 0.39-1.02 0-1.41-0.39-0.39-1.02-0.39-1.41 0L17 5.59V4c0-0.55-0.45-1-1-1s-1 0.45-1 1v4c0 0.55 0.45 1 1 1h4c0.55 0 1-0.45 1-1s-0.45-1-1-1zM4 17h1.59l-2.38 2.38c-0.39 0.39-0.39 1.02 0 1.41 0.39 0.39 1.02 0.39 1.41 0L7 18.41V20c0 0.55 0.45 1 1 1s1-0.45 1-1v-4c0-0.55-0.45-1-1-1H4c-0.55 0-1 0.45-1 1s0.45 1 1 1zm17-1c0-0.55-0.45-1-1-1h-4c-0.55 0-1 0.45-1 1v4c0 0.55 0.45 1 1 1s1-0.45 1-1v-1.59l2.38 2.38c0.39 0.39 1.02 0.39 1.41 0 0.39-0.39 0.39-1.02 0-1.41L18.41 17H20c0.55 0 1-0.45 1-1z" android:fillColor="#FFFFFF"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_photo_filter_vector.xml
Normal file
3
src/main/res/drawable/ic_photo_filter_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<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="#FFFFFF" android:pathData="M19.02 10.99V18c0 0.55-0.45 1-1 1H6c-0.55 0-1-0.45-1-1V6c0-0.55 0.45-1 1-1h7c0.55 0 1-0.45 1-1s-0.45-1-1-1H5.02c-1.1 0-2 0.9-2 2v14c0 1.1 0.9 2 2 2H19c1.1 0 2-0.89 2-2v-8.01c0-0.55-0.44-0.99-0.99-0.99s-0.99 0.44-0.99 0.99zm-5.77-0.24L12.46 9c-0.18-0.39-0.73-0.39-0.91 0l-0.79 1.75L9 11.54c-0.39 0.18-0.39 0.73 0 0.91l1.75 0.79 0.79 1.76c0.18 0.39 0.73 0.39 0.91 0l0.79-1.75L15 12.46c0.39-0.18 0.39-0.73 0-0.91l-1.75-0.8zm4.69-4.69l-0.6-1.32c-0.13-0.29-0.55-0.29-0.69 0l-0.6 1.32-1.32 0.6c-0.29 0.13-0.29 0.55 0 0.69l1.32 0.6 0.6 1.32c0.13 0.29 0.55 0.29 0.69 0l0.6-1.32 1.32-0.6c0.29-0.13 0.29-0.55 0-0.69l-1.32-0.6z"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_rotate_right_vector.xml
Normal file
3
src/main/res/drawable/ic_rotate_right_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<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="#FFFFFF" android:pathData="M14.83 4.83L12.7 2.7C12.08 2.08 11 2.52 11 3.41v0.66C7.06 4.56 4 7.92 4 12c0 3.64 2.43 6.71 5.77 7.68 0.62 0.18 1.23-0.32 1.23-0.96v-0.03c0-0.43-0.27-0.82-0.68-0.94C7.82 17.03 6 14.73 6 12c0-2.97 2.16-5.43 5-5.91v1.53c0 0.89 1.07 1.33 1.7 0.71l2.13-2.08c0.4-0.38 0.4-1.02 0-1.42zm4.84 4.93c-0.16-0.55-0.38-1.08-0.66-1.59-0.31-0.57-1.1-0.66-1.56-0.2l-0.01 0.01c-0.31 0.31-0.38 0.78-0.17 1.16 0.2 0.37 0.36 0.76 0.48 1.16 0.12 0.42 0.51 0.7 0.94 0.7h0.02c0.65 0 1.15-0.62 0.96-1.24zM13 18.68v0.02c0 0.65 0.62 1.14 1.24 0.96 0.55-0.16 1.08-0.38 1.59-0.66 0.57-0.31 0.66-1.1 0.2-1.56l-0.02-0.02c-0.31-0.31-0.78-0.38-1.16-0.17-0.37 0.21-0.76 0.37-1.16 0.49-0.41 0.12-0.69 0.51-0.69 0.94zm4.44-2.65c0.46 0.46 1.25 0.37 1.56-0.2 0.28-0.51 0.5-1.04 0.67-1.59 0.18-0.62-0.31-1.24-0.96-1.24h-0.02c-0.44 0-0.82 0.28-0.94 0.7-0.12 0.4-0.28 0.79-0.48 1.17-0.21 0.38-0.13 0.86 0.17 1.16z"/>
|
||||||
|
</vector>
|
3
src/main/res/drawable/ic_undo_vector.xml
Normal file
3
src/main/res/drawable/ic_undo_vector.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="24" android:viewportHeight="24">
|
||||||
|
<path android:pathData="M12.5 8c-2.65 0-5.05 0.99-6.9 2.6L3.71 8.71C3.08 8.08 2 8.52 2 9.41V15c0 0.55 0.45 1 1 1h5.59c0.89 0 1.34-1.08 0.71-1.71l-1.91-1.91c1.39-1.16 3.16-1.88 5.12-1.88 3.16 0 5.89 1.84 7.19 4.5 0.27 0.56 0.91 0.84 1.5 0.64 0.71-0.23 1.07-1.04 0.75-1.72C20.23 10.42 16.65 8 12.5 8z" android:fillColor="#FFFFFFFF"/>
|
||||||
|
</vector>
|
46
src/main/res/drawable/strings.xml
Normal file
46
src/main/res/drawable/strings.xml
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Simple Gallery</string>
|
||||||
|
<string name="app_launcher_name">Gallery</string>
|
||||||
|
|
||||||
|
<string name="ok">OK</string>
|
||||||
|
<string name="cancel">Cancel</string>
|
||||||
|
<string name="custom">Custom</string>
|
||||||
|
|
||||||
|
<string name="resize_and_save">Resize selection and save</string>
|
||||||
|
<string name="width">Width</string>
|
||||||
|
<string name="height">Height</string>
|
||||||
|
<string name="keep_aspect_ratio">Keep aspect ratio</string>
|
||||||
|
<string name="invalid_values">Please enter a valid resolution</string>
|
||||||
|
|
||||||
|
<string name="editor">Editor</string>
|
||||||
|
<string name="basic_editor">Basic Editor</string>
|
||||||
|
<string name="rotate">Rotate</string>
|
||||||
|
<string name="invalid_image_path">Invalid image path</string>
|
||||||
|
<string name="image_editing_failed">Image editing failed</string>
|
||||||
|
<string name="unknown_file_location">Unknown file location</string>
|
||||||
|
<string name="transform">Transform</string>
|
||||||
|
<string name="crop">Crop</string>
|
||||||
|
<string name="draw">Draw</string>
|
||||||
|
<string name="flip_horizontally">Flip horizontally</string>
|
||||||
|
<string name="flip_vertically">Flip vertically</string>
|
||||||
|
<string name="free_aspect_ratio">Free</string>
|
||||||
|
<string name="other_aspect_ratio">Other</string>
|
||||||
|
|
||||||
|
<string name="thumbnails">Thumbnails</string>
|
||||||
|
<string name="saving">saving</string>
|
||||||
|
<string name="out_of_memory_error">out_of_memory_error</string>
|
||||||
|
<string name="none">none</string>
|
||||||
|
<string name="file_saved">file_saved</string>
|
||||||
|
<string name="error">error</string>
|
||||||
|
<string name="simple_commons">simple_commons</string>
|
||||||
|
<string name="value_copied_to_clipboard_show">value_copied_to_clipboard_show</string>
|
||||||
|
<string name="default_color">default_color</string>
|
||||||
|
<string name="unknown_error_occurred">unknown_error_occurred</string>
|
||||||
|
<string name="undo">undo</string>
|
||||||
|
<string name="change_color">change_color</string>
|
||||||
|
<string name="resize">resize</string>
|
||||||
|
<string name="filter">filter</string>
|
||||||
|
<string name="could_not_create_file">could_not_create_file</string>
|
||||||
|
|
||||||
|
</resources>
|
8
src/main/res/drawable/stroke_background.xml
Normal file
8
src/main/res/drawable/stroke_background.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item>
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<stroke android:width="@dimen/one_dp" android:color="#FFFFFFFF"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
89
src/main/res/layout/activity_edit.xml
Normal file
89
src/main/res/layout/activity_edit.xml
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<com.github.chrisbanes.photoview.PhotoView
|
||||||
|
android:id="@+id/default_image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="?attr/actionBarSize"
|
||||||
|
android:paddingBottom="@dimen/bottom_actions_height_bigger" />
|
||||||
|
|
||||||
|
<com.theartofdev.edmodo.cropper.CropImageView
|
||||||
|
android:id="@+id/crop_image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:layout_marginBottom="@dimen/bottom_actions_height_bigger"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:cropBackgroundColor="@color/crop_image_view_background"
|
||||||
|
app:cropInitialCropWindowPaddingRatio="0" />
|
||||||
|
|
||||||
|
<eu.siacs.conversations.medialib.views.EditorDrawCanvas
|
||||||
|
android:id="@+id/editor_draw_canvas"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerHorizontal="true"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:layout_marginBottom="@dimen/bottom_actions_height_double"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
|
<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: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_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>
|
||||||
|
|
||||||
|
<com.google.android.material.appbar.MaterialToolbar
|
||||||
|
android:id="@+id/editor_toolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="?attr/actionBarSize"
|
||||||
|
android:background="@color/black26"
|
||||||
|
android:elevation="0dp"
|
||||||
|
app:title="@string/editor"
|
||||||
|
app:navigationIcon="?attr/homeAsUpIndicator" />
|
||||||
|
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
</layout>
|
94
src/main/res/layout/bottom_actions_aspect_ratio.xml
Normal file
94
src/main/res/layout/bottom_actions_aspect_ratio.xml
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:background="@color/black26"
|
||||||
|
android:layout_height="@dimen/bottom_actions_height">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_aspect_ratio_free"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/large_margin"
|
||||||
|
android:text="@string/free_aspect_ratio"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="@dimen/big_text_size"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bottom_aspect_ratio_one_one"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_aspect_ratio_one_one"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/large_margin"
|
||||||
|
android:text="1:1"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="@dimen/big_text_size"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bottom_aspect_ratio_four_three"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bottom_aspect_ratio_free"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_aspect_ratio_four_three"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/large_margin"
|
||||||
|
android:text="4:3"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="@dimen/big_text_size"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bottom_aspect_ratio_sixteen_nine"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bottom_aspect_ratio_one_one"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_aspect_ratio_sixteen_nine"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/large_margin"
|
||||||
|
android:text="16:9"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="@dimen/big_text_size"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@+id/bottom_aspect_ratio_other"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bottom_aspect_ratio_four_three"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottom_aspect_ratio_other"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:padding="@dimen/large_margin"
|
||||||
|
android:text="@string/other_aspect_ratio"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
android:textSize="@dimen/big_text_size"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bottom_aspect_ratio_sixteen_nine"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.0"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
21
src/main/res/layout/bottom_editor_actions_filter.xml
Normal file
21
src/main/res/layout/bottom_editor_actions_filter.xml
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_filters_height"
|
||||||
|
android:background="@color/black26"
|
||||||
|
android:paddingTop="@dimen/medium_margin">
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/bottom_actions_filter_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_filters_height"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:overScrollMode="never"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
</layout>
|
82
src/main/res/layout/bottom_editor_crop_rotate_actions.xml
Normal file
82
src/main/res/layout/bottom_editor_crop_rotate_actions.xml
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_actions_small_height"
|
||||||
|
android:background="@color/black26"
|
||||||
|
android:layout_alignParentBottom="true">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bottom_rotate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/rotate"
|
||||||
|
android:padding="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_rotate_right_vector"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
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_vector"
|
||||||
|
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/crop"
|
||||||
|
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"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/flip_horizontally"
|
||||||
|
android:padding="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_flip_horizontally_vector"
|
||||||
|
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_aspect_ratio"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/bottom_flip_vertically"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/flip_vertically"
|
||||||
|
android:padding="@dimen/normal_margin"
|
||||||
|
android:src="@drawable/ic_flip_vertically_vector"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/bottom_flip_horizontally"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</layout>
|
64
src/main/res/layout/bottom_editor_draw_actions.xml
Normal file
64
src/main/res/layout/bottom_editor_draw_actions.xml
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_actions_height"
|
||||||
|
android:background="@color/black26"
|
||||||
|
android:layout_alignParentBottom="true">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatSeekBar
|
||||||
|
android:id="@+id/bottom_draw_width"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginLeft="@dimen/large_margin"
|
||||||
|
android:layout_marginRight="@dimen/large_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"
|
||||||
|
android:foreground="@drawable/circle_stroke_white"
|
||||||
|
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>
|
||||||
|
</layout>
|
52
src/main/res/layout/bottom_editor_primary_actions.xml
Normal file
52
src/main/res/layout/bottom_editor_primary_actions.xml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="@dimen/bottom_actions_height"
|
||||||
|
android:background="@color/black26"
|
||||||
|
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:contentDescription="@string/transform"
|
||||||
|
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:contentDescription="@string/draw"
|
||||||
|
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>
|
||||||
|
</layout>
|
||||||
|
|
166
src/main/res/layout/dialog_color_picker.xml
Normal file
166
src/main/res/layout/dialog_color_picker.xml
Normal file
|
@ -0,0 +1,166 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<ScrollView
|
||||||
|
android:id="@+id/color_picker_scrollview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="@dimen/large_margin"
|
||||||
|
android:layout_marginBottom="@dimen/large_margin">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/color_picker_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:id="@+id/recent_colors"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="@dimen/large_margin"
|
||||||
|
android:layout_marginTop="@dimen/large_margin">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.helper.widget.Flow
|
||||||
|
android:id="@+id/recent_colors_flow"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:flow_horizontalAlign="start"
|
||||||
|
app:flow_horizontalGap="@dimen/large_margin"
|
||||||
|
app:flow_horizontalStyle="packed"
|
||||||
|
app:flow_maxElementsWrap="5"
|
||||||
|
app:flow_verticalGap="@dimen/medium_margin"
|
||||||
|
app:flow_wrapMode="chain"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/color_picker_top_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="272dp"
|
||||||
|
android:layout_below="@+id/recent_colors"
|
||||||
|
android:gravity="center_horizontal">
|
||||||
|
|
||||||
|
<eu.siacs.conversations.medialib.views.ColorPickerSquare
|
||||||
|
android:id="@+id/color_picker_square"
|
||||||
|
android:layout_width="240dp"
|
||||||
|
android:layout_height="240dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layerType="software" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_hue"
|
||||||
|
android:layout_width="@dimen/colorpicker_hue_width"
|
||||||
|
android:layout_height="240dp"
|
||||||
|
android:layout_centerVertical="true"
|
||||||
|
android:layout_marginStart="@dimen/medium_margin"
|
||||||
|
android:layout_toEndOf="@id/color_picker_square"
|
||||||
|
android:scaleType="fitXY"
|
||||||
|
android:src="@drawable/img_color_picker_hue" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_hue_cursor"
|
||||||
|
android:layout_width="8dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_chevron_right_unpadded_vector" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_cursor"
|
||||||
|
android:layout_width="@dimen/large_margin"
|
||||||
|
android:layout_height="@dimen/large_margin"
|
||||||
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="fitCenter"
|
||||||
|
android:src="@drawable/color_picker_circle" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/color_picker_hex_codes_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/color_picker_top_holder">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/color_picker_old_hex"
|
||||||
|
android:layout_width="70dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_toStartOf="@+id/color_picker_hex_arrow"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="@dimen/normal_text_size" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_hex_arrow"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_alignTop="@+id/color_picker_new_hex"
|
||||||
|
android:layout_alignBottom="@+id/color_picker_new_hex"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginStart="@dimen/normal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/normal_margin"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_arrow_right_vector" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/color_picker_new_hex_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_alignTop="@+id/color_picker_new_hex"
|
||||||
|
android:layout_alignBottom="@+id/color_picker_new_hex"
|
||||||
|
android:layout_toEndOf="@+id/color_picker_hex_arrow"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="#"
|
||||||
|
android:textSize="@dimen/normal_text_size" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/color_picker_new_hex"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_toEndOf="@+id/color_picker_new_hex_label"
|
||||||
|
android:digits="0123456789ABCDEFabcdef"
|
||||||
|
android:ems="5"
|
||||||
|
android:gravity="start"
|
||||||
|
android:lines="1"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:textSize="@dimen/normal_text_size" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
android:id="@+id/color_picker_bottom_holder"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_below="@id/color_picker_hex_codes_holder"
|
||||||
|
android:layout_marginTop="@dimen/medium_margin"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_old_color"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_toStartOf="@+id/color_picker_arrow"
|
||||||
|
android:background="@android:color/white" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_arrow"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:layout_marginStart="@dimen/normal_margin"
|
||||||
|
android:layout_marginEnd="@dimen/normal_margin"
|
||||||
|
android:scaleType="centerInside"
|
||||||
|
android:src="@drawable/ic_arrow_right_vector" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/color_picker_new_color"
|
||||||
|
android:layout_width="30dp"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_toEndOf="@+id/color_picker_arrow"
|
||||||
|
android:background="@android:color/white" />
|
||||||
|
</RelativeLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</layout>
|
63
src/main/res/layout/dialog_custom_aspect_ratio.xml
Normal file
63
src/main/res/layout/dialog_custom_aspect_ratio.xml
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="@dimen/large_margin"
|
||||||
|
android:paddingTop="@dimen/large_margin"
|
||||||
|
android:paddingRight="@dimen/large_margin">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/aspect_ratio_width_hint"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/large_margin"
|
||||||
|
android:hint="@string/width">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/aspect_ratio_width"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/aspect_ratio_colon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/aspect_ratio_width_hint"
|
||||||
|
android:layout_alignBottom="@+id/aspect_ratio_width_hint"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_toEndOf="@+id/aspect_ratio_width_hint"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text=":"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/aspect_ratio_width_hint"
|
||||||
|
android:layout_alignBottom="@+id/aspect_ratio_width_hint"
|
||||||
|
android:layout_toEndOf="@+id/aspect_ratio_colon"
|
||||||
|
android:hint="@string/height">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/aspect_ratio_height"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</RelativeLayout>
|
||||||
|
</layout>
|
168
src/main/res/layout/dialog_other_aspect_ratio.xml
Normal file
168
src/main/res/layout/dialog_other_aspect_ratio.xml
Normal file
|
@ -0,0 +1,168 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<ScrollView
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingLeft="@dimen/large_margin"
|
||||||
|
android:paddingTop="@dimen/large_margin"
|
||||||
|
android:paddingRight="@dimen/large_margin"
|
||||||
|
tools:ignore="HardcodedText">
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/other_aspect_ratio_dialog_radio_1"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_2_1"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="2:1"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_3_2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="3:2"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_4_3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="4:3"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_5_3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="5:3"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_16_9"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="16:9"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_19_9"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="19:9"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_custom"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="@string/custom"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</RadioGroup>
|
||||||
|
|
||||||
|
<RadioGroup
|
||||||
|
android:id="@+id/other_aspect_ratio_dialog_radio_2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/medium_margin"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_1_2"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="1:2"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_2_3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="2:3"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_3_4"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="3:4"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_3_5"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="3:5"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_9_16"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="9:16"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatRadioButton
|
||||||
|
android:id="@+id/other_aspect_ratio_9_19"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="@dimen/small_margin"
|
||||||
|
android:paddingTop="@dimen/normal_margin"
|
||||||
|
android:paddingBottom="@dimen/normal_margin"
|
||||||
|
android:text="9:19"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</RadioGroup>
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</layout>
|
||||||
|
|
74
src/main/res/layout/dialog_resize_image.xml
Normal file
74
src/main/res/layout/dialog_resize_image.xml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<RelativeLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingLeft="@dimen/large_margin"
|
||||||
|
android:paddingTop="@dimen/large_margin"
|
||||||
|
android:paddingRight="@dimen/large_margin">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/resize_image_width_hint"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="@dimen/large_margin"
|
||||||
|
android:hint="@string/width">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/resize_image_width"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/resize_image_colon"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/resize_image_width_hint"
|
||||||
|
android:layout_alignBottom="@+id/resize_image_width_hint"
|
||||||
|
android:layout_marginStart="20dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:layout_toEndOf="@+id/resize_image_width_hint"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text=":"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignTop="@+id/resize_image_width_hint"
|
||||||
|
android:layout_alignBottom="@+id/resize_image_width_hint"
|
||||||
|
android:layout_toEndOf="@+id/resize_image_colon"
|
||||||
|
android:hint="@string/height">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/resize_image_height"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="6"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textCursorDrawable="@null"
|
||||||
|
android:textSize="@dimen/bigger_text_size" />
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatCheckBox
|
||||||
|
android:id="@+id/keep_aspect_ratio"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@+id/resize_image_width_hint"
|
||||||
|
android:checked="true"
|
||||||
|
android:paddingTop="@dimen/large_margin"
|
||||||
|
android:paddingBottom="@dimen/large_margin"
|
||||||
|
android:text="@string/keep_aspect_ratio" />
|
||||||
|
|
||||||
|
</RelativeLayout>
|
||||||
|
</layout>
|
31
src/main/res/layout/editor_filter_item.xml
Normal file
31
src/main/res/layout/editor_filter_item.xml
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<RelativeLayout
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
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>
|
||||||
|
|
||||||
|
</layout>
|
9
src/main/res/menu/menu_done.xml
Normal file
9
src/main/res/menu/menu_done.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item android:id="@+id/action_done"
|
||||||
|
app:showAsAction="ifRoom"
|
||||||
|
android:showAsAction="ifRoom"
|
||||||
|
android:title="@string/done"
|
||||||
|
android:icon="@drawable/ic_done_24dp"/>
|
||||||
|
</menu>
|
|
@ -46,4 +46,7 @@
|
||||||
<color name="blue500">#ff2196f3</color>
|
<color name="blue500">#ff2196f3</color>
|
||||||
|
|
||||||
<color name="blue_alpha">#aa82B1FF</color>
|
<color name="blue_alpha">#aa82B1FF</color>
|
||||||
|
|
||||||
|
<color name="crop_image_view_background">#BB000000</color>
|
||||||
|
<color name="editor_draw_default_color">#000000</color>
|
||||||
</resources>
|
</resources>
|
|
@ -44,4 +44,34 @@
|
||||||
<dimen name="local_video_preview_height">128dp</dimen>
|
<dimen name="local_video_preview_height">128dp</dimen>
|
||||||
<dimen name="local_video_preview_width">96dp</dimen>
|
<dimen name="local_video_preview_width">96dp</dimen>
|
||||||
<dimen name="rtp_session_duration_top_margin">24dp</dimen>
|
<dimen name="rtp_session_duration_top_margin">24dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="selection_check_size">26dp</dimen>
|
||||||
|
<dimen name="bottom_actions_height">64dp</dimen>
|
||||||
|
<dimen name="bottom_actions_small_height">48dp</dimen>
|
||||||
|
<dimen name="bottom_actions_height_double">120dp</dimen>
|
||||||
|
<dimen name="bottom_actions_height_bigger">172dp</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="full_brush_size">40dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="one_dp">1dp</dimen>
|
||||||
|
<dimen name="tiny_margin">2dp</dimen>
|
||||||
|
<dimen name="small_margin">4dp</dimen>
|
||||||
|
<dimen name="smaller_margin">6dp</dimen>
|
||||||
|
<dimen name="medium_margin">8dp</dimen>
|
||||||
|
<dimen name="normal_margin">12dp</dimen>
|
||||||
|
<dimen name="large_margin">16dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="tiny_text_size">8sp</dimen>
|
||||||
|
<dimen name="small_text_size">10sp</dimen>
|
||||||
|
<dimen name="smaller_text_size">12sp</dimen>
|
||||||
|
<dimen name="normal_text_size">14sp</dimen>
|
||||||
|
<dimen name="medium_text_size">15sp</dimen>
|
||||||
|
<dimen name="bigger_text_size">16sp</dimen>
|
||||||
|
<dimen name="big_text_size">18sp</dimen>
|
||||||
|
|
||||||
|
<dimen name="colorpicker_hue_width">30dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -1011,4 +1011,35 @@
|
||||||
<string name="delete_from_server">Remove account from server</string>
|
<string name="delete_from_server">Remove account from server</string>
|
||||||
<string name="could_not_delete_account_from_server">Could not delete account from server</string>
|
<string name="could_not_delete_account_from_server">Could not delete account from server</string>
|
||||||
|
|
||||||
|
<string name="custom">Custom</string>
|
||||||
|
<string name="resize_and_save">Resize selection and save</string>
|
||||||
|
<string name="width">Width</string>
|
||||||
|
<string name="height">Height</string>
|
||||||
|
<string name="keep_aspect_ratio">Keep aspect ratio</string>
|
||||||
|
<string name="invalid_values">Please enter a valid resolution</string>
|
||||||
|
<string name="editor">Editor</string>
|
||||||
|
<string name="basic_editor">Basic Editor</string>
|
||||||
|
<string name="rotate">Rotate</string>
|
||||||
|
<string name="invalid_image_path">Invalid image path</string>
|
||||||
|
<string name="image_editing_failed">Image editing failed</string>
|
||||||
|
<string name="unknown_file_location">Unknown file location</string>
|
||||||
|
<string name="transform">Transform</string>
|
||||||
|
<string name="crop">Crop</string>
|
||||||
|
<string name="draw">Draw</string>
|
||||||
|
<string name="flip_horizontally">Flip horizontally</string>
|
||||||
|
<string name="flip_vertically">Flip vertically</string>
|
||||||
|
<string name="free_aspect_ratio">Free</string>
|
||||||
|
<string name="other_aspect_ratio">Other</string>
|
||||||
|
<string name="thumbnails">Thumbnails</string>
|
||||||
|
<string name="saving">saving</string>
|
||||||
|
<string name="out_of_memory_error">out_of_memory_error</string>
|
||||||
|
<string name="file_saved">file_saved</string>
|
||||||
|
<string name="simple_commons">simple_commons</string>
|
||||||
|
<string name="value_copied_to_clipboard_show">value_copied_to_clipboard_show</string>
|
||||||
|
<string name="default_color">default_color</string>
|
||||||
|
<string name="unknown_error_occurred">unknown_error_occurred</string>
|
||||||
|
<string name="change_color">change_color</string>
|
||||||
|
<string name="resize">resize</string>
|
||||||
|
<string name="filter">filter</string>
|
||||||
|
<string name="could_not_create_file">could_not_create_file</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -376,9 +376,10 @@
|
||||||
<style name="ConversationsTheme.FullScreen" parent="@style/Theme.AppCompat.Light">
|
<style name="ConversationsTheme.FullScreen" parent="@style/Theme.AppCompat.Light">
|
||||||
<item name="colorPrimary">@color/green600</item>
|
<item name="colorPrimary">@color/green600</item>
|
||||||
<item name="colorPrimaryDark">@color/green700</item>
|
<item name="colorPrimaryDark">@color/green700</item>
|
||||||
|
<item name="colorAccent">@color/green600</item>
|
||||||
|
<item name="colorSurface">@color/white</item>
|
||||||
<item name="android:windowNoTitle">true</item>
|
<item name="android:windowNoTitle">true</item>
|
||||||
<item name="android:windowActionBar">false</item>
|
<item name="android:windowActionBar">false</item>
|
||||||
<item name="android:windowFullscreen">true</item>
|
|
||||||
<item name="android:windowContentOverlay">@null</item>
|
<item name="android:windowContentOverlay">@null</item>
|
||||||
<item name="android:windowBackground">@android:color/black</item>
|
<item name="android:windowBackground">@android:color/black</item>
|
||||||
<item name="android:navigationBarColor" tools:targetApi="21">@color/black</item>
|
<item name="android:navigationBarColor" tools:targetApi="21">@color/black</item>
|
||||||
|
|
Loading…
Reference in a new issue