diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt index 19722fa3..a33d9732 100644 --- a/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/camera/activities/MainActivity.kt @@ -13,7 +13,6 @@ import android.os.Looper import android.provider.MediaStore import android.view.* import android.widget.LinearLayout -import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.core.view.* import androidx.transition.* @@ -29,6 +28,7 @@ import com.simplemobiletools.camera.R import com.simplemobiletools.camera.extensions.config import com.simplemobiletools.camera.extensions.fadeIn import com.simplemobiletools.camera.extensions.fadeOut +import com.simplemobiletools.camera.extensions.setShadowIcon import com.simplemobiletools.camera.extensions.toFlashModeId import com.simplemobiletools.camera.helpers.* import com.simplemobiletools.camera.implementations.CameraXInitializer @@ -366,10 +366,19 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera last_photo_video_preview.setOnClickListener { showLastMediaPreview() } toggle_flash.setOnClickListener { toggleFlash() } shutter.setOnClickListener { shutterPressed() } + + settings.setShadowIcon(R.drawable.ic_settings_vector) settings.setOnClickListener { launchSettings() } + change_resolution.setOnClickListener { mPreview?.showChangeResolution() } + + flash_on.setShadowIcon(R.drawable.ic_flash_on_vector) flash_on.setOnClickListener { selectFlashMode(FLASH_ON) } + + flash_off.setShadowIcon(R.drawable.ic_flash_off_vector) flash_off.setOnClickListener { selectFlashMode(FLASH_OFF) } + + flash_auto.setShadowIcon(R.drawable.ic_flash_auto_vector) flash_auto.setOnClickListener { selectFlashMode(FLASH_AUTO) } } @@ -394,7 +403,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private fun toggleFlash() { if (checkCameraAvailable()) { if (mIsInPhotoMode) { - showFlashOptions(mIsInPhotoMode) + showFlashOptions(true) } else { mPreview?.toggleFlashlight() } @@ -408,7 +417,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera FLASH_ON -> R.drawable.ic_flash_on_vector else -> R.drawable.ic_flash_auto_vector } - toggle_flash.icon = AppCompatResources.getDrawable(this, flashDrawable) + toggle_flash.setShadowIcon(flashDrawable) toggle_flash.transitionName = "${getString(R.string.toggle_flash)}$state" } @@ -551,7 +560,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera private fun hasPhotoModePermissions(): Boolean { return if (isTiramisuPlus()) { - hasPermission(PERMISSION_READ_MEDIA_IMAGES) && hasPermission(PERMISSION_READ_MEDIA_VIDEO) && hasPermission(PERMISSION_CAMERA) + hasPermission(PERMISSION_READ_MEDIA_IMAGES) && hasPermission(PERMISSION_READ_MEDIA_VIDEO) && hasPermission(PERMISSION_CAMERA) } else { hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA) } @@ -622,7 +631,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera toggle_flash.beVisible() } else { toggle_flash.beInvisible() - toggle_flash.icon = AppCompatResources.getDrawable(this, R.drawable.ic_flash_off_vector) + toggle_flash.setShadowIcon(R.drawable.ic_flash_off_vector) mPreview?.setFlashlightState(FLASH_OFF) } } @@ -741,7 +750,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera override fun displaySelectedResolution(resolutionOption: ResolutionOption) { val imageRes = resolutionOption.imageDrawableResId - change_resolution.icon = AppCompatResources.getDrawable(this, imageRes) + change_resolution.setShadowIcon(imageRes) change_resolution.transitionName = "${resolutionOption.buttonViewId}" } @@ -765,9 +774,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera onSelect.invoke(index, selectedResolution.buttonViewId != clickedViewId) } - resolutions.map { - createButton(it, onItemClick) - }.forEach { button -> + resolutions.forEach { + val button = createButton(it, onItemClick) mediaSizeToggleGroup.addView(button) } @@ -787,7 +795,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera } return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply { layoutParams = params - icon = AppCompatResources.getDrawable(context, resolutionOption.imageDrawableResId) + setShadowIcon(resolutionOption.imageDrawableResId) id = resolutionOption.buttonViewId transitionName = "${resolutionOption.buttonViewId}" setOnClickListener { @@ -811,7 +819,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera flash_toggle_group.check(config.flashlightState.toFlashModeId()) flash_toggle_group.beVisible() - flash_toggle_group.children.map { it as MaterialButton }.forEach(::setButtonColors) + flash_toggle_group.children.forEach { setButtonColors(it as MaterialButton) } } private fun setButtonColors(button: MaterialButton) { diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/extensions/MaterialButton.kt b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/MaterialButton.kt new file mode 100644 index 00000000..f44fd81b --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/camera/extensions/MaterialButton.kt @@ -0,0 +1,10 @@ +package com.simplemobiletools.camera.extensions + +import androidx.annotation.DrawableRes +import com.google.android.material.button.MaterialButton +import com.simplemobiletools.camera.R +import com.simplemobiletools.camera.views.ShadowDrawable + +fun MaterialButton.setShadowIcon(@DrawableRes drawableResId: Int) { + icon = ShadowDrawable(context, drawableResId, R.style.TopIconShadow) +} diff --git a/app/src/main/kotlin/com/simplemobiletools/camera/views/ShadowDrawable.kt b/app/src/main/kotlin/com/simplemobiletools/camera/views/ShadowDrawable.kt new file mode 100644 index 00000000..e10f93fa --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/camera/views/ShadowDrawable.kt @@ -0,0 +1,228 @@ +package com.simplemobiletools.camera.views + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.* +import android.graphics.drawable.Drawable +import androidx.annotation.DrawableRes +import androidx.annotation.StyleRes +import androidx.core.content.ContextCompat +import com.simplemobiletools.camera.R +import kotlin.math.ceil +import kotlin.math.roundToInt + +class ShadowDrawable(context: Context, private val drawable: Drawable, @StyleRes styleResId: Int) : Drawable() { + + companion object { + private const val SHARED_BITMAP_BUFFER_SIZE = 640 + private val sharedDrawableBitmapBuffer = ThreadLocal() + private val sharedDrawableCanvasBuffer = ThreadLocal() + } + + private val bitmapPaint = Paint() + private val contentBounds = Rect() + private val destRect = Rect() + private val noRadiusShadowBounds = Rect() + private val shadowBounds = Rect() + private val shadowPaint = Paint() + private val srcRect = Rect() + private val unionBounds = Rect() + + private var outputBuffer: Bitmap? = null + private var outputBufferCanvas: Canvas? = null + private var paddingBottom = 0 + private var paddingEnd = 0 + private var paddingStart = 0 + private var paddingTop = 0 + private var shadowColor = 0 + private var shadowDx = 0 + private var shadowDy = 0 + private var shadowRadiusCeiling = 0 + + constructor(context: Context, @DrawableRes drawableRes: Int, @StyleRes styleResId: Int) : this( + context, + ContextCompat.getDrawable( + context, + drawableRes, + )!!, + styleResId + ) + + init { + drawable.callback = object : Callback { + override fun unscheduleDrawable(drawable2: Drawable, runnable: Runnable) { + unscheduleSelf(runnable) + } + + override fun scheduleDrawable(drawable2: Drawable, runnable: Runnable, j: Long) { + scheduleSelf(runnable, j) + } + + override fun invalidateDrawable(drawable2: Drawable) { + invalidateSelf() + } + } + if (styleResId != 0) { + val obtainStyledAttributes = context.obtainStyledAttributes(styleResId, R.styleable.ShadowDrawable) + shadowColor = + obtainStyledAttributes.getColor(R.styleable.ShadowDrawable_android_shadowColor, ContextCompat.getColor(context, R.color.md_grey_400_dark)) + shadowDx = obtainStyledAttributes.getFloat(R.styleable.ShadowDrawable_android_shadowDx, 0f).toInt() + shadowDy = obtainStyledAttributes.getFloat(R.styleable.ShadowDrawable_android_shadowDy, 0f).toInt() + val shadowRadius = obtainStyledAttributes.getFloat(R.styleable.ShadowDrawable_android_shadowRadius, 0.0f).coerceAtLeast(0.0f) + shadowRadiusCeiling = ceil(shadowRadius).toInt() + val alpha = Color.alpha(shadowColor) + if (shadowRadius > 0.0f && alpha > 0) { + val blurMaskFilter = BlurMaskFilter(shadowRadius, BlurMaskFilter.Blur.NORMAL) + shadowPaint.alpha = alpha + shadowPaint.maskFilter = blurMaskFilter + } else { + shadowPaint.alpha = 0 + } + obtainStyledAttributes.recycle() + } + shadowPaint.isAntiAlias = true + shadowPaint.isFilterBitmap = true + shadowPaint.isAntiAlias = true + shadowPaint.isFilterBitmap = true + } + + override fun draw(canvas: Canvas) { + val createBitmap: Bitmap + val canvas2: Canvas + val bounds = bounds + contentBounds[bounds.left + paddingStart, bounds.top + paddingTop, bounds.right - paddingEnd] = + bounds.bottom - paddingBottom + if (contentBounds.width() <= 0 || contentBounds.height() <= 0) { + return + } + if (shadowPaint.alpha == 0) { + drawable.bounds = contentBounds + drawable.draw(canvas) + return + } + if (contentBounds.width() <= SHARED_BITMAP_BUFFER_SIZE && contentBounds.height() <= SHARED_BITMAP_BUFFER_SIZE) { + var sharedBitmap = sharedDrawableBitmapBuffer.get() + if (sharedBitmap != null) { + sharedBitmap.eraseColor(0) + } else { + sharedBitmap = Bitmap.createBitmap(SHARED_BITMAP_BUFFER_SIZE, SHARED_BITMAP_BUFFER_SIZE, Bitmap.Config.ARGB_8888) + sharedDrawableBitmapBuffer.set(sharedBitmap) + sharedDrawableCanvasBuffer.set(Canvas(sharedBitmap!!)) + } + createBitmap = sharedBitmap + canvas2 = sharedDrawableCanvasBuffer.get()!! + } else { + createBitmap = Bitmap.createBitmap( + contentBounds.width(), + contentBounds.height(), + Bitmap.Config.ARGB_8888 + ) + canvas2 = Canvas(createBitmap) + } + drawable.setBounds(0, 0, contentBounds.width(), contentBounds.height()) + drawable.draw(canvas2) + shadowBounds.set(contentBounds) + shadowBounds.offset(shadowDx, shadowDy) + noRadiusShadowBounds.set(shadowBounds) + val i = shadowRadiusCeiling + shadowBounds.inset(-i, -i) + unionBounds.set(bounds) + unionBounds.union(contentBounds) + unionBounds.union(shadowBounds) + + var outputBuffer = this.outputBuffer + if (outputBuffer == null || unionBounds.width() > outputBuffer.width || unionBounds.height() > outputBuffer.height) { + outputBuffer = Bitmap.createBitmap( + unionBounds.width(), + unionBounds.height(), + Bitmap.Config.ARGB_8888 + ) + this.outputBuffer = outputBuffer + this.outputBufferCanvas = Canvas(outputBuffer) + } else { + outputBuffer.eraseColor(0) + } + + srcRect[0, 0, contentBounds.width()] = contentBounds.height() + destRect.set(noRadiusShadowBounds) + destRect.offset(-unionBounds.left, -unionBounds.top) + outputBufferCanvas!!.drawBitmap(createBitmap.extractAlpha(), srcRect, destRect, shadowPaint) + destRect.set(contentBounds) + destRect.offset(-unionBounds.left, -unionBounds.top) + outputBufferCanvas!!.drawBitmap(createBitmap, srcRect, destRect, bitmapPaint) + srcRect[0, 0, unionBounds.width()] = unionBounds.height() + destRect.set(unionBounds) + canvas.drawBitmap(outputBuffer!!, srcRect, destRect, bitmapPaint) + } + + @Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat")) + override fun getOpacity(): Int { + return drawable.opacity + } + + override fun setTint(tintColor: Int) { + drawable.setTint(tintColor) + } + + override fun setTintList(tint: ColorStateList?) { + drawable.setTintList(tint) + } + + override fun setTintMode(tintMode: PorterDuff.Mode?) { + drawable.setTintMode(tintMode) + } + + override fun getColorFilter(): ColorFilter? { + return drawable.colorFilter + } + + + override fun getState(): IntArray { + return drawable.state + } + + override fun getIntrinsicHeight(): Int { + return drawable.intrinsicHeight + paddingTop + paddingBottom + } + + override fun getIntrinsicWidth(): Int { + return drawable.intrinsicWidth + paddingStart + paddingEnd + } + + override fun isStateful(): Boolean { + return drawable.isStateful + } + + override fun onLevelChange(i: Int): Boolean { + return drawable.setLevel(i) + } + + override fun getAlpha(): Int { + return drawable.alpha + } + + override fun setAlpha(i: Int) { + drawable.alpha = i + shadowPaint.alpha = (Color.alpha(shadowColor) / 255.0f * i).roundToInt() + } + + override fun setAutoMirrored(z: Boolean) { + drawable.isAutoMirrored = z + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + drawable.colorFilter = colorFilter + } + + fun setPaddings(start: Int, top: Int, end: Int, bottom: Int) { + paddingStart = start + paddingTop = top + paddingEnd = end + paddingBottom = bottom + invalidateSelf() + } + + override fun setState(iArr: IntArray): Boolean { + return drawable.setState(iArr) + } +} diff --git a/app/src/main/res/layout/layout_flash.xml b/app/src/main/res/layout/layout_flash.xml index 0a982cd0..2f2c05b0 100644 --- a/app/src/main/res/layout/layout_flash.xml +++ b/app/src/main/res/layout/layout_flash.xml @@ -11,7 +11,7 @@ tools:visibility="visible"> -