mirror of
https://github.com/SimpleMobileTools/Simple-Camera.git
synced 2025-04-13 15:02:07 +02:00
954 lines
35 KiB
Kotlin
954 lines
35 KiB
Kotlin
package com.simplemobiletools.camera.activities
|
|
|
|
import android.annotation.SuppressLint
|
|
import android.app.Activity
|
|
import android.content.Intent
|
|
import android.content.res.ColorStateList
|
|
import android.graphics.Bitmap
|
|
import android.hardware.SensorManager
|
|
import android.net.Uri
|
|
import android.os.Bundle
|
|
import android.os.CountDownTimer
|
|
import android.provider.MediaStore
|
|
import android.view.*
|
|
import android.widget.LinearLayout
|
|
import androidx.constraintlayout.widget.ConstraintSet
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.view.*
|
|
import androidx.transition.*
|
|
import com.bumptech.glide.Glide
|
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
import com.bumptech.glide.request.RequestOptions
|
|
import com.google.android.material.button.MaterialButton
|
|
import com.google.android.material.button.MaterialButtonToggleGroup
|
|
import com.google.android.material.tabs.TabLayout
|
|
import com.simplemobiletools.camera.BuildConfig
|
|
import com.simplemobiletools.camera.R
|
|
import com.simplemobiletools.camera.databinding.ActivityMainBinding
|
|
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
|
|
import com.simplemobiletools.camera.implementations.CameraXPreviewListener
|
|
import com.simplemobiletools.camera.interfaces.MyPreview
|
|
import com.simplemobiletools.camera.models.ResolutionOption
|
|
import com.simplemobiletools.camera.models.TimerMode
|
|
import com.simplemobiletools.camera.views.FocusCircleView
|
|
import com.simplemobiletools.commons.extensions.*
|
|
import com.simplemobiletools.commons.helpers.*
|
|
import com.simplemobiletools.commons.models.Release
|
|
import java.util.concurrent.TimeUnit
|
|
import kotlin.math.abs
|
|
|
|
class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, CameraXPreviewListener {
|
|
private companion object {
|
|
const val CAPTURE_ANIMATION_DURATION = 500L
|
|
const val PHOTO_MODE_INDEX = 1
|
|
const val VIDEO_MODE_INDEX = 0
|
|
private const val MIN_SWIPE_DISTANCE_X = 100
|
|
private const val TIMER_2_SECONDS = 2001
|
|
}
|
|
|
|
private lateinit var defaultScene: Scene
|
|
private lateinit var flashModeScene: Scene
|
|
private lateinit var timerScene: Scene
|
|
private lateinit var mOrientationEventListener: OrientationEventListener
|
|
private lateinit var mFocusCircleView: FocusCircleView
|
|
private lateinit var mediaSoundHelper: MediaSoundHelper
|
|
private lateinit var binding: ActivityMainBinding
|
|
private var mPreview: MyPreview? = null
|
|
private var mediaSizeToggleGroup: MaterialButtonToggleGroup? = null
|
|
private var mPreviewUri: Uri? = null
|
|
private var mIsHardwareShutterHandled = false
|
|
private var mLastHandledOrientation = 0
|
|
private var countDownTimer: CountDownTimer? = null
|
|
|
|
private val tabSelectedListener = object : TabSelectedListener {
|
|
override fun onTabSelected(tab: TabLayout.Tab) {
|
|
handlePermission(PERMISSION_RECORD_AUDIO) {
|
|
if (it) {
|
|
when (tab.position) {
|
|
VIDEO_MODE_INDEX -> mPreview?.initVideoMode()
|
|
PHOTO_MODE_INDEX -> mPreview?.initPhotoMode()
|
|
else -> throw IllegalStateException("Unsupported tab position ${tab.position}")
|
|
}
|
|
} else {
|
|
toast(com.simplemobiletools.commons.R.string.no_audio_permissions)
|
|
selectPhotoTab()
|
|
if (isVideoCaptureIntent()) {
|
|
finish()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
useDynamicTheme = false
|
|
super.onCreate(savedInstanceState)
|
|
binding = ActivityMainBinding.inflate(layoutInflater)
|
|
appLaunched(BuildConfig.APPLICATION_ID)
|
|
requestWindowFeature(Window.FEATURE_NO_TITLE)
|
|
initVariables()
|
|
tryInitCamera()
|
|
supportActionBar?.hide()
|
|
checkWhatsNewDialog()
|
|
setupOrientationEventListener()
|
|
|
|
val windowInsetsController = ViewCompat.getWindowInsetsController(window.decorView)
|
|
windowInsetsController?.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
|
windowInsetsController?.hide(WindowInsetsCompat.Type.statusBars())
|
|
|
|
if (isOreoMr1Plus()) {
|
|
setShowWhenLocked(true)
|
|
setTurnScreenOn(true)
|
|
} else {
|
|
window.addFlags(
|
|
WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD or
|
|
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED or
|
|
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON or
|
|
WindowManager.LayoutParams.FLAG_FULLSCREEN
|
|
)
|
|
}
|
|
}
|
|
|
|
override fun onResume() {
|
|
super.onResume()
|
|
if (hasStorageAndCameraPermissions()) {
|
|
val isInPhotoMode = isInPhotoMode()
|
|
setupPreviewImage(isInPhotoMode)
|
|
mFocusCircleView.setStrokeColor(getProperPrimaryColor())
|
|
toggleActionButtons(enabled = true)
|
|
mOrientationEventListener.enable()
|
|
}
|
|
|
|
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
ensureTransparentNavigationBar()
|
|
|
|
if (ViewCompat.getWindowInsetsController(window.decorView) == null) {
|
|
window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
}
|
|
}
|
|
|
|
override fun onPause() {
|
|
super.onPause()
|
|
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
|
if (!isAskingPermissions) {
|
|
cancelTimer()
|
|
}
|
|
|
|
if (!hasStorageAndCameraPermissions() || isAskingPermissions) {
|
|
return
|
|
}
|
|
|
|
mOrientationEventListener.disable()
|
|
}
|
|
|
|
override fun onDestroy() {
|
|
super.onDestroy()
|
|
mPreview = null
|
|
mediaSoundHelper.release()
|
|
}
|
|
|
|
override fun onBackPressed() {
|
|
if (!closeOptions()) {
|
|
super.onBackPressed()
|
|
}
|
|
}
|
|
|
|
private fun selectPhotoTab(triggerListener: Boolean = false) {
|
|
if (!triggerListener) {
|
|
removeTabListener()
|
|
}
|
|
|
|
binding.cameraModeTab.getTabAt(PHOTO_MODE_INDEX)?.select()
|
|
setTabListener()
|
|
}
|
|
|
|
private fun selectVideoTab(triggerListener: Boolean = false) {
|
|
if (!triggerListener) {
|
|
removeTabListener()
|
|
}
|
|
binding.cameraModeTab.getTabAt(VIDEO_MODE_INDEX)?.select()
|
|
setTabListener()
|
|
}
|
|
|
|
private fun setTabListener() {
|
|
binding.cameraModeTab.addOnTabSelectedListener(tabSelectedListener)
|
|
}
|
|
|
|
private fun removeTabListener() {
|
|
binding.cameraModeTab.removeOnTabSelectedListener(tabSelectedListener)
|
|
}
|
|
|
|
private fun ensureTransparentNavigationBar() {
|
|
window.navigationBarColor = ContextCompat.getColor(this, android.R.color.transparent)
|
|
}
|
|
|
|
private fun initVariables() {
|
|
mIsHardwareShutterHandled = false
|
|
mediaSoundHelper = MediaSoundHelper(this)
|
|
mediaSoundHelper.loadSounds()
|
|
}
|
|
|
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
|
return if (keyCode == KeyEvent.KEYCODE_CAMERA && !mIsHardwareShutterHandled) {
|
|
mIsHardwareShutterHandled = true
|
|
shutterPressed()
|
|
true
|
|
} else if (!mIsHardwareShutterHandled && config.volumeButtonsAsShutter && (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)) {
|
|
mIsHardwareShutterHandled = true
|
|
shutterPressed()
|
|
true
|
|
} else {
|
|
super.onKeyDown(keyCode, event)
|
|
}
|
|
}
|
|
|
|
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
|
|
if (keyCode == KeyEvent.KEYCODE_CAMERA || keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
|
mIsHardwareShutterHandled = false
|
|
}
|
|
return super.onKeyUp(keyCode, event)
|
|
}
|
|
|
|
private fun hideIntentButtons() = binding.apply {
|
|
cameraModeHolder.beGone()
|
|
layoutTop.settings.beGone()
|
|
lastPhotoVideoPreview.beInvisible()
|
|
}
|
|
|
|
private fun tryInitCamera() {
|
|
handlePermission(PERMISSION_CAMERA) { grantedCameraPermission ->
|
|
if (grantedCameraPermission) {
|
|
handleStoragePermission { grantedStoragePermission ->
|
|
if (grantedStoragePermission) {
|
|
val isInPhotoMode = isInPhotoMode()
|
|
if (isInPhotoMode) {
|
|
initializeCamera(true)
|
|
} else {
|
|
handlePermission(PERMISSION_RECORD_AUDIO) { grantedRecordAudioPermission ->
|
|
if (grantedRecordAudioPermission) {
|
|
initializeCamera(false)
|
|
} else {
|
|
toast(com.simplemobiletools.commons.R.string.no_audio_permissions)
|
|
if (isThirdPartyIntent()) {
|
|
finish()
|
|
} else {
|
|
// re-initialize in photo mode
|
|
config.initPhotoMode = true
|
|
tryInitCamera()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
toast(com.simplemobiletools.commons.R.string.no_storage_permissions)
|
|
finish()
|
|
}
|
|
}
|
|
} else {
|
|
toast(com.simplemobiletools.commons.R.string.no_camera_permissions)
|
|
finish()
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun isInPhotoMode(): Boolean {
|
|
return mPreview?.isInPhotoMode() ?: if (isVideoCaptureIntent()) {
|
|
false
|
|
} else if (isImageCaptureIntent()) {
|
|
true
|
|
} else {
|
|
config.initPhotoMode
|
|
}
|
|
}
|
|
|
|
private fun handleStoragePermission(callback: (granted: Boolean) -> Unit) {
|
|
if (isTiramisuPlus()) {
|
|
handlePermission(PERMISSION_READ_MEDIA_IMAGES) { grantedReadImages ->
|
|
if (grantedReadImages) {
|
|
handlePermission(PERMISSION_READ_MEDIA_VIDEO, callback)
|
|
} else {
|
|
callback.invoke(false)
|
|
}
|
|
}
|
|
} else {
|
|
handlePermission(PERMISSION_WRITE_STORAGE, callback)
|
|
}
|
|
}
|
|
|
|
private fun isThirdPartyIntent() = isVideoCaptureIntent() || isImageCaptureIntent()
|
|
|
|
private fun isImageCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_IMAGE_CAPTURE || intent?.action == MediaStore.ACTION_IMAGE_CAPTURE_SECURE
|
|
|
|
private fun isVideoCaptureIntent(): Boolean = intent?.action == MediaStore.ACTION_VIDEO_CAPTURE
|
|
|
|
private fun createToggleGroup(): MaterialButtonToggleGroup {
|
|
return MaterialButtonToggleGroup(this).apply {
|
|
isSingleSelection = true
|
|
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
|
}
|
|
}
|
|
|
|
private fun initializeCamera(isInPhotoMode: Boolean) {
|
|
setContentView(binding.root)
|
|
initButtons()
|
|
initModeSwitcher()
|
|
binding.apply {
|
|
defaultScene = Scene(topOptions, layoutTop.defaultIcons)
|
|
flashModeScene = Scene(topOptions, layoutFlash.flashToggleGroup)
|
|
timerScene = Scene(topOptions, layoutTimer.timerToggleGroup)
|
|
}
|
|
|
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
ViewCompat.setOnApplyWindowInsetsListener(binding.viewHolder) { _, windowInsets ->
|
|
val safeInsetBottom = windowInsets.displayCutout?.safeInsetBottom ?: 0
|
|
val safeInsetTop = windowInsets.displayCutout?.safeInsetTop ?: 0
|
|
|
|
binding.topOptions.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
topMargin = safeInsetTop
|
|
}
|
|
|
|
val marginBottom = safeInsetBottom + navigationBarHeight + resources.getDimensionPixelSize(com.simplemobiletools.commons.R.dimen.bigger_margin)
|
|
|
|
binding.shutter.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
bottomMargin = marginBottom
|
|
}
|
|
|
|
WindowInsetsCompat.CONSUMED
|
|
}
|
|
|
|
if (isInPhotoMode) {
|
|
selectPhotoTab()
|
|
} else {
|
|
selectVideoTab()
|
|
}
|
|
|
|
val outputUri = intent.extras?.get(MediaStore.EXTRA_OUTPUT) as? Uri
|
|
val isThirdPartyIntent = isThirdPartyIntent()
|
|
mPreview = CameraXInitializer(this).createCameraXPreview(
|
|
binding.previewView,
|
|
listener = this,
|
|
mediaSoundHelper = mediaSoundHelper,
|
|
outputUri = outputUri,
|
|
isThirdPartyIntent = isThirdPartyIntent,
|
|
initInPhotoMode = isInPhotoMode,
|
|
)
|
|
|
|
mFocusCircleView = FocusCircleView(this).apply {
|
|
id = View.generateViewId()
|
|
}
|
|
binding.viewHolder.addView(mFocusCircleView)
|
|
|
|
setupPreviewImage(true)
|
|
initFlashModeTransitionNames()
|
|
initTimerModeTransitionNames()
|
|
|
|
if (isThirdPartyIntent) {
|
|
hideIntentButtons()
|
|
}
|
|
}
|
|
|
|
private fun initFlashModeTransitionNames() = binding.layoutFlash.apply {
|
|
val baseName = getString(R.string.toggle_flash)
|
|
flashAuto.transitionName = "$baseName$FLASH_AUTO"
|
|
flashOff.transitionName = "$baseName$FLASH_OFF"
|
|
flashOn.transitionName = "$baseName$FLASH_ON"
|
|
flashAlwaysOn.transitionName = "$baseName$FLASH_ALWAYS_ON"
|
|
}
|
|
|
|
private fun initTimerModeTransitionNames() = binding.layoutTimer.apply {
|
|
val baseName = getString(R.string.toggle_timer)
|
|
timerOff.transitionName = "$baseName${TimerMode.OFF.name}"
|
|
timer3s.transitionName = "$baseName${TimerMode.TIMER_3.name}"
|
|
timer5s.transitionName = "$baseName${TimerMode.TIMER_5.name}"
|
|
timer10S.transitionName = "$baseName${TimerMode.TIMER_10.name}"
|
|
}
|
|
|
|
private fun initButtons() = binding.apply {
|
|
timerText.setFactory { layoutInflater.inflate(R.layout.timer_text, null) }
|
|
toggleCamera.setOnClickListener { mPreview!!.toggleFrontBackCamera() }
|
|
lastPhotoVideoPreview.setOnClickListener { showLastMediaPreview() }
|
|
|
|
layoutTop.apply {
|
|
toggleFlash.setOnClickListener { mPreview!!.handleFlashlightClick() }
|
|
toggleTimer.setOnClickListener {
|
|
val transitionSet = createTransition()
|
|
TransitionManager.go(timerScene, transitionSet)
|
|
layoutTimer.timerToggleGroup.beVisible()
|
|
layoutTimer.timerToggleGroup.check(config.timerMode.getTimerModeResId())
|
|
layoutTimer.timerToggleGroup.children.forEach { setButtonColors(it as MaterialButton) }
|
|
}
|
|
|
|
settings.setShadowIcon(R.drawable.ic_settings_vector)
|
|
settings.setOnClickListener { launchSettings() }
|
|
changeResolution.setOnClickListener { mPreview?.showChangeResolution() }
|
|
}
|
|
|
|
shutter.setOnClickListener { shutterPressed() }
|
|
|
|
layoutFlash.apply {
|
|
flashOn.setShadowIcon(R.drawable.ic_flash_on_vector)
|
|
flashOn.setOnClickListener { selectFlashMode(FLASH_ON) }
|
|
|
|
flashOff.setShadowIcon(R.drawable.ic_flash_off_vector)
|
|
flashOff.setOnClickListener { selectFlashMode(FLASH_OFF) }
|
|
|
|
flashAuto.setShadowIcon(R.drawable.ic_flash_auto_vector)
|
|
flashAuto.setOnClickListener { selectFlashMode(FLASH_AUTO) }
|
|
|
|
flashAlwaysOn.setShadowIcon(R.drawable.ic_flashlight_vector)
|
|
flashAlwaysOn.setOnClickListener { selectFlashMode(FLASH_ALWAYS_ON) }
|
|
}
|
|
|
|
layoutTimer.apply {
|
|
timerOff.setShadowIcon(R.drawable.ic_timer_off_vector)
|
|
timerOff.setOnClickListener { selectTimerMode(TimerMode.OFF) }
|
|
|
|
timer3s.setShadowIcon(R.drawable.ic_timer_3_vector)
|
|
timer3s.setOnClickListener { selectTimerMode(TimerMode.TIMER_3) }
|
|
|
|
timer5s.setShadowIcon(R.drawable.ic_timer_5_vector)
|
|
timer5s.setOnClickListener { selectTimerMode(TimerMode.TIMER_5) }
|
|
|
|
timer10S.setShadowIcon(R.drawable.ic_timer_10_vector)
|
|
timer10S.setOnClickListener { selectTimerMode(TimerMode.TIMER_10) }
|
|
}
|
|
|
|
setTimerModeIcon(config.timerMode)
|
|
}
|
|
|
|
private fun selectTimerMode(timerMode: TimerMode) {
|
|
config.timerMode = timerMode
|
|
setTimerModeIcon(timerMode)
|
|
closeOptions()
|
|
}
|
|
|
|
private fun setTimerModeIcon(timerMode: TimerMode) = binding.layoutTop.toggleTimer.apply {
|
|
setShadowIcon(timerMode.getTimerModeDrawableRes())
|
|
transitionName = "${getString(R.string.toggle_timer)}${timerMode.name}"
|
|
}
|
|
|
|
@SuppressLint("ClickableViewAccessibility")
|
|
private fun initModeSwitcher() {
|
|
val gestureDetector = GestureDetectorCompat(this, object : GestureDetectorListener() {
|
|
override fun onDown(e: MotionEvent): Boolean {
|
|
// we have to return true here so ACTION_UP (and onFling) can be dispatched
|
|
return true
|
|
}
|
|
|
|
override fun onFling(event1: MotionEvent?, event2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean {
|
|
if (event1 == null || event2 == null) {
|
|
return true
|
|
}
|
|
|
|
val deltaX = event1.x - event2.x
|
|
val deltaXAbs = abs(deltaX)
|
|
|
|
if (deltaXAbs >= MIN_SWIPE_DISTANCE_X) {
|
|
if (deltaX > 0) {
|
|
onSwipeLeft()
|
|
} else {
|
|
onSwipeRight()
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
})
|
|
|
|
binding.cameraModeTab.setOnTouchListener { _, event ->
|
|
gestureDetector.onTouchEvent(event)
|
|
}
|
|
}
|
|
|
|
private fun onSwipeLeft() {
|
|
if (!isThirdPartyIntent() && binding.cameraModeHolder.isVisible()) {
|
|
selectPhotoTab(triggerListener = true)
|
|
}
|
|
}
|
|
|
|
private fun onSwipeRight() {
|
|
if (!isThirdPartyIntent() && binding.cameraModeHolder.isVisible()) {
|
|
selectVideoTab(triggerListener = true)
|
|
}
|
|
}
|
|
|
|
private fun selectFlashMode(flashMode: Int) {
|
|
closeOptions()
|
|
mPreview?.setFlashlightState(flashMode)
|
|
}
|
|
|
|
|
|
private fun showLastMediaPreview() {
|
|
if (mPreviewUri != null) {
|
|
val path = applicationContext.getRealPathFromURI(mPreviewUri!!) ?: mPreviewUri!!.toString()
|
|
openPathIntent(path, false, BuildConfig.APPLICATION_ID)
|
|
}
|
|
}
|
|
|
|
private fun shutterPressed() {
|
|
if (countDownTimer != null) {
|
|
cancelTimer()
|
|
} else if (isInPhotoMode()) {
|
|
val timerMode = config.timerMode
|
|
if (timerMode == TimerMode.OFF) {
|
|
mPreview?.tryTakePicture()
|
|
} else {
|
|
scheduleTimer(timerMode)
|
|
}
|
|
} else {
|
|
mPreview?.toggleRecording()
|
|
}
|
|
}
|
|
|
|
private fun cancelTimer() {
|
|
mediaSoundHelper.stopTimerCountdown2SecondsSound()
|
|
countDownTimer?.cancel()
|
|
countDownTimer = null
|
|
resetViewsOnTimerFinish()
|
|
}
|
|
|
|
private fun launchSettings() {
|
|
val intent = Intent(applicationContext, SettingsActivity::class.java)
|
|
startActivity(intent)
|
|
}
|
|
|
|
override fun onInitPhotoMode() {
|
|
binding.apply {
|
|
shutter.setImageResource(R.drawable.ic_shutter_animated)
|
|
layoutTop.toggleTimer.beVisible()
|
|
layoutTop.toggleTimer.fadeIn()
|
|
}
|
|
setupPreviewImage(true)
|
|
selectPhotoTab()
|
|
}
|
|
|
|
override fun onInitVideoMode() {
|
|
binding.apply {
|
|
shutter.setImageResource(R.drawable.ic_video_rec_animated)
|
|
layoutTop.toggleTimer.fadeOut()
|
|
layoutTop.toggleTimer.beGone()
|
|
}
|
|
setupPreviewImage(false)
|
|
selectVideoTab()
|
|
}
|
|
|
|
private fun setupPreviewImage(isPhoto: Boolean) {
|
|
val uri = if (isPhoto) MediaStore.Images.Media.EXTERNAL_CONTENT_URI else MediaStore.Video.Media.EXTERNAL_CONTENT_URI
|
|
val lastMediaId = getLatestMediaId(uri)
|
|
if (lastMediaId == 0L) {
|
|
return
|
|
}
|
|
|
|
mPreviewUri = Uri.withAppendedPath(uri, lastMediaId.toString())
|
|
|
|
loadLastTakenMedia(mPreviewUri)
|
|
}
|
|
|
|
private fun loadLastTakenMedia(uri: Uri?) {
|
|
mPreviewUri = uri
|
|
runOnUiThread {
|
|
if (!isDestroyed) {
|
|
val options = RequestOptions()
|
|
.centerCrop()
|
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
|
|
Glide.with(this)
|
|
.load(uri)
|
|
.apply(options)
|
|
.transition(DrawableTransitionOptions.withCrossFade())
|
|
.into(binding.lastPhotoVideoPreview)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun hasStorageAndCameraPermissions(): Boolean {
|
|
return if (isInPhotoMode()) hasPhotoModePermissions() else hasVideoModePermissions()
|
|
}
|
|
|
|
private fun hasPhotoModePermissions(): Boolean {
|
|
return if (isTiramisuPlus()) {
|
|
hasPermission(PERMISSION_READ_MEDIA_IMAGES) && hasPermission(PERMISSION_READ_MEDIA_VIDEO) && hasPermission(PERMISSION_CAMERA)
|
|
} else {
|
|
hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
|
|
}
|
|
}
|
|
|
|
private fun hasVideoModePermissions(): Boolean {
|
|
return if (isTiramisuPlus()) {
|
|
hasPermission(PERMISSION_READ_MEDIA_VIDEO) && hasPermission(PERMISSION_CAMERA) && hasPermission(PERMISSION_RECORD_AUDIO)
|
|
} else {
|
|
hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA) && hasPermission(PERMISSION_RECORD_AUDIO)
|
|
}
|
|
}
|
|
|
|
private fun setupOrientationEventListener() {
|
|
mOrientationEventListener = object : OrientationEventListener(this, SensorManager.SENSOR_DELAY_NORMAL) {
|
|
override fun onOrientationChanged(orientation: Int) {
|
|
if (isDestroyed) {
|
|
mOrientationEventListener.disable()
|
|
return
|
|
}
|
|
|
|
val currOrient = when (orientation) {
|
|
in 75..134 -> ORIENT_LANDSCAPE_RIGHT
|
|
in 225..289 -> ORIENT_LANDSCAPE_LEFT
|
|
else -> ORIENT_PORTRAIT
|
|
}
|
|
|
|
if (currOrient != mLastHandledOrientation) {
|
|
val degrees = when (currOrient) {
|
|
ORIENT_LANDSCAPE_LEFT -> 90
|
|
ORIENT_LANDSCAPE_RIGHT -> -90
|
|
else -> 0
|
|
}
|
|
|
|
animateViews(degrees)
|
|
mLastHandledOrientation = currOrient
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun animateViews(degrees: Int) = binding.apply {
|
|
val views = arrayOf(
|
|
toggleCamera,
|
|
layoutTop.toggleFlash,
|
|
layoutTop.changeResolution,
|
|
shutter,
|
|
layoutTop.settings,
|
|
lastPhotoVideoPreview
|
|
)
|
|
for (view in views) {
|
|
rotate(view, degrees)
|
|
}
|
|
}
|
|
|
|
private fun rotate(view: View, degrees: Int) = view.animate().rotation(degrees.toFloat()).start()
|
|
|
|
override fun setHasFrontAndBackCamera(hasFrontAndBack: Boolean) {
|
|
binding.toggleCamera?.beVisibleIf(hasFrontAndBack)
|
|
}
|
|
|
|
override fun setFlashAvailable(available: Boolean) {
|
|
if (available) {
|
|
binding.layoutTop.toggleFlash.beVisible()
|
|
} else {
|
|
binding.layoutTop.toggleFlash.beGone()
|
|
mPreview?.setFlashlightState(FLASH_OFF)
|
|
}
|
|
}
|
|
|
|
override fun onChangeCamera(frontCamera: Boolean) {
|
|
binding.toggleCamera.setImageResource(if (frontCamera) R.drawable.ic_camera_rear_vector else R.drawable.ic_camera_front_vector)
|
|
}
|
|
|
|
override fun onPhotoCaptureStart() {
|
|
toggleActionButtons(enabled = false)
|
|
}
|
|
|
|
override fun onPhotoCaptureEnd() {
|
|
toggleActionButtons(enabled = true)
|
|
}
|
|
|
|
private fun toggleActionButtons(enabled: Boolean) = binding.apply {
|
|
runOnUiThread {
|
|
shutter.isClickable = enabled
|
|
previewView.isEnabled = enabled
|
|
layoutTop.changeResolution.isEnabled = enabled
|
|
toggleCamera.isClickable = enabled
|
|
layoutTop.toggleFlash.isClickable = enabled
|
|
}
|
|
}
|
|
|
|
override fun shutterAnimation() {
|
|
binding.shutterAnimation.alpha = 1.0f
|
|
binding.shutterAnimation.animate().alpha(0f).setDuration(CAPTURE_ANIMATION_DURATION).start()
|
|
}
|
|
|
|
override fun onMediaSaved(uri: Uri) {
|
|
binding.layoutTop.changeResolution.isEnabled = true
|
|
loadLastTakenMedia(uri)
|
|
if (isImageCaptureIntent()) {
|
|
Intent().apply {
|
|
data = uri
|
|
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
setResult(Activity.RESULT_OK, this)
|
|
}
|
|
finish()
|
|
} else if (isVideoCaptureIntent()) {
|
|
Intent().apply {
|
|
data = uri
|
|
flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
setResult(Activity.RESULT_OK, this)
|
|
}
|
|
finish()
|
|
}
|
|
}
|
|
|
|
override fun onImageCaptured(bitmap: Bitmap) {
|
|
if (isImageCaptureIntent()) {
|
|
Intent().apply {
|
|
putExtra("data", bitmap)
|
|
setResult(Activity.RESULT_OK, this)
|
|
}
|
|
finish()
|
|
}
|
|
}
|
|
|
|
override fun onChangeFlashMode(flashMode: Int) {
|
|
binding.layoutTop.apply {
|
|
val flashDrawable = when (flashMode) {
|
|
FLASH_OFF -> R.drawable.ic_flash_off_vector
|
|
FLASH_ON -> R.drawable.ic_flash_on_vector
|
|
FLASH_AUTO -> R.drawable.ic_flash_auto_vector
|
|
else -> R.drawable.ic_flashlight_vector
|
|
}
|
|
toggleFlash.setShadowIcon(flashDrawable)
|
|
toggleFlash.transitionName = "${getString(R.string.toggle_flash)}$flashMode"
|
|
}
|
|
}
|
|
|
|
override fun onVideoRecordingStarted() {
|
|
binding.apply {
|
|
cameraModeHolder.beInvisible()
|
|
videoRecCurrTimer.beVisible()
|
|
|
|
toggleCamera.fadeOut()
|
|
lastPhotoVideoPreview.fadeOut()
|
|
|
|
layoutTop.changeResolution.isEnabled = false
|
|
layoutTop.settings.isEnabled = false
|
|
shutter.post {
|
|
if (!isDestroyed) {
|
|
shutter.isSelected = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onVideoRecordingStopped() {
|
|
binding.apply {
|
|
cameraModeHolder.beVisible()
|
|
|
|
toggleCamera.fadeIn()
|
|
lastPhotoVideoPreview.fadeIn()
|
|
|
|
videoRecCurrTimer.text = 0.getFormattedDuration()
|
|
videoRecCurrTimer.beGone()
|
|
|
|
shutter.isSelected = false
|
|
layoutTop.changeResolution.isEnabled = true
|
|
layoutTop.settings.isEnabled = true
|
|
}
|
|
}
|
|
|
|
override fun onVideoDurationChanged(durationNanos: Long) {
|
|
val seconds = TimeUnit.NANOSECONDS.toSeconds(durationNanos).toInt()
|
|
binding.videoRecCurrTimer.text = seconds.getFormattedDuration()
|
|
}
|
|
|
|
override fun onFocusCamera(xPos: Float, yPos: Float) {
|
|
mFocusCircleView.drawFocusCircle(xPos, yPos)
|
|
}
|
|
|
|
override fun onTouchPreview() {
|
|
closeOptions()
|
|
}
|
|
|
|
private fun closeOptions(): Boolean {
|
|
binding.apply {
|
|
if (mediaSizeToggleGroup?.isVisible() == true ||
|
|
layoutFlash.flashToggleGroup.isVisible() || layoutTimer.timerToggleGroup.isVisible()
|
|
) {
|
|
val transitionSet = createTransition()
|
|
TransitionManager.go(defaultScene, transitionSet)
|
|
mediaSizeToggleGroup?.beGone()
|
|
layoutFlash.flashToggleGroup.beGone()
|
|
layoutTimer.timerToggleGroup.beGone()
|
|
layoutTop.defaultIcons.beVisible()
|
|
return true
|
|
}
|
|
|
|
return false
|
|
}
|
|
}
|
|
|
|
override fun displaySelectedResolution(resolutionOption: ResolutionOption) {
|
|
val imageRes = resolutionOption.imageDrawableResId
|
|
binding.layoutTop.changeResolution.setShadowIcon(imageRes)
|
|
binding.layoutTop.changeResolution.transitionName = "${resolutionOption.buttonViewId}"
|
|
}
|
|
|
|
override fun showImageSizes(
|
|
selectedResolution: ResolutionOption,
|
|
resolutions: List<ResolutionOption>,
|
|
isPhotoCapture: Boolean,
|
|
isFrontCamera: Boolean,
|
|
onSelect: (index: Int, changed: Boolean) -> Unit
|
|
) {
|
|
binding.topOptions.removeView(mediaSizeToggleGroup)
|
|
val mediaSizeToggleGroup = createToggleGroup().apply {
|
|
mediaSizeToggleGroup = this
|
|
}
|
|
|
|
binding.topOptions.addView(mediaSizeToggleGroup)
|
|
|
|
val onItemClick = { clickedViewId: Int ->
|
|
closeOptions()
|
|
val index = resolutions.indexOfFirst { it.buttonViewId == clickedViewId }
|
|
onSelect.invoke(index, selectedResolution.buttonViewId != clickedViewId)
|
|
}
|
|
|
|
resolutions.forEach {
|
|
val button = createButton(it, onItemClick)
|
|
mediaSizeToggleGroup.addView(button)
|
|
}
|
|
|
|
mediaSizeToggleGroup.check(selectedResolution.buttonViewId)
|
|
|
|
val transitionSet = createTransition()
|
|
val mediaSizeScene = Scene(binding.topOptions, mediaSizeToggleGroup)
|
|
TransitionManager.go(mediaSizeScene, transitionSet)
|
|
binding.layoutTop.defaultIcons.beGone()
|
|
mediaSizeToggleGroup.beVisible()
|
|
mediaSizeToggleGroup.children.map { it as MaterialButton }.forEach(::setButtonColors)
|
|
}
|
|
|
|
private fun createButton(resolutionOption: ResolutionOption, onClick: (clickedViewId: Int) -> Unit): MaterialButton {
|
|
val params = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT).apply {
|
|
weight = 1f
|
|
}
|
|
|
|
return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply {
|
|
layoutParams = params
|
|
setShadowIcon(resolutionOption.imageDrawableResId)
|
|
id = resolutionOption.buttonViewId
|
|
transitionName = "${resolutionOption.buttonViewId}"
|
|
setOnClickListener {
|
|
onClick.invoke(id)
|
|
}
|
|
}
|
|
}
|
|
|
|
private fun createTransition(): Transition {
|
|
val fadeTransition = Fade()
|
|
return TransitionSet().apply {
|
|
addTransition(fadeTransition)
|
|
this.duration = resources.getInteger(R.integer.icon_anim_duration).toLong()
|
|
}
|
|
}
|
|
|
|
override fun showFlashOptions(photoCapture: Boolean) {
|
|
binding.layoutFlash.apply {
|
|
val transitionSet = createTransition()
|
|
TransitionManager.go(flashModeScene, transitionSet)
|
|
flashAuto.beVisibleIf(photoCapture)
|
|
flashAlwaysOn.beVisibleIf(photoCapture)
|
|
flashToggleGroup.check(config.flashlightState.toFlashModeId())
|
|
|
|
flashToggleGroup.beVisible()
|
|
flashToggleGroup.children.forEach { setButtonColors(it as MaterialButton) }
|
|
}
|
|
}
|
|
|
|
private fun setButtonColors(button: MaterialButton) {
|
|
val primaryColor = getProperPrimaryColor()
|
|
val states = arrayOf(intArrayOf(-android.R.attr.state_checked), intArrayOf(android.R.attr.state_checked))
|
|
val iconColors = intArrayOf(ContextCompat.getColor(this, com.simplemobiletools.commons.R.color.md_grey_white), primaryColor)
|
|
button.iconTint = ColorStateList(states, iconColors)
|
|
}
|
|
|
|
override fun adjustPreviewView(requiresCentering: Boolean) {
|
|
binding.apply {
|
|
val constraintSet = ConstraintSet()
|
|
constraintSet.clone(viewHolder)
|
|
if (requiresCentering) {
|
|
constraintSet.connect(previewView.id, ConstraintSet.TOP, topOptions.id, ConstraintSet.BOTTOM)
|
|
constraintSet.connect(previewView.id, ConstraintSet.BOTTOM, cameraModeHolder.id, ConstraintSet.TOP)
|
|
} else {
|
|
constraintSet.connect(previewView.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
|
|
constraintSet.connect(previewView.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
|
|
}
|
|
constraintSet.applyTo(viewHolder)
|
|
}
|
|
}
|
|
|
|
override fun mediaSaved(path: String) {
|
|
rescanPaths(arrayListOf(path)) {
|
|
setupPreviewImage(true)
|
|
Intent(BROADCAST_REFRESH_MEDIA).apply {
|
|
putExtra(REFRESH_PATH, path)
|
|
`package` = "com.simplemobiletools.gallery"
|
|
sendBroadcast(this)
|
|
}
|
|
}
|
|
|
|
if (isImageCaptureIntent()) {
|
|
setResult(Activity.RESULT_OK)
|
|
finish()
|
|
}
|
|
}
|
|
|
|
private fun scheduleTimer(timerMode: TimerMode) {
|
|
hideViewsOnTimerStart()
|
|
binding.shutter.setImageState(intArrayOf(R.attr.state_timer_cancel), true)
|
|
binding.timerText.beVisible()
|
|
var playSound = true
|
|
countDownTimer = object : CountDownTimer(timerMode.millisInFuture, 1000) {
|
|
override fun onTick(millisUntilFinished: Long) {
|
|
val seconds = (TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished) + 1).toString()
|
|
binding.timerText.setText(seconds)
|
|
if (playSound && config.isSoundEnabled) {
|
|
if (millisUntilFinished <= TIMER_2_SECONDS) {
|
|
mediaSoundHelper.playTimerCountdown2SecondsSound()
|
|
playSound = false
|
|
} else {
|
|
mediaSoundHelper.playTimerCountdownSound()
|
|
}
|
|
}
|
|
}
|
|
|
|
override fun onFinish() {
|
|
cancelTimer()
|
|
mPreview!!.tryTakePicture()
|
|
}
|
|
}.start()
|
|
}
|
|
|
|
private fun hideViewsOnTimerStart() = binding.apply {
|
|
arrayOf(topOptions, toggleCamera, lastPhotoVideoPreview, cameraModeHolder).forEach {
|
|
it.fadeOut()
|
|
it.beInvisible()
|
|
}
|
|
}
|
|
|
|
private fun resetViewsOnTimerFinish() = binding.apply {
|
|
arrayOf(topOptions, toggleCamera, lastPhotoVideoPreview, cameraModeHolder).forEach {
|
|
it?.fadeIn()
|
|
it?.beVisible()
|
|
}
|
|
|
|
timerText.beGone()
|
|
shutter.setImageState(intArrayOf(-R.attr.state_timer_cancel), true)
|
|
}
|
|
|
|
private fun checkWhatsNewDialog() {
|
|
arrayListOf<Release>().apply {
|
|
add(Release(33, R.string.release_33))
|
|
add(Release(35, R.string.release_35))
|
|
add(Release(39, R.string.release_39))
|
|
add(Release(44, R.string.release_44))
|
|
add(Release(46, R.string.release_46))
|
|
add(Release(52, R.string.release_52))
|
|
checkWhatsNew(this, BuildConfig.VERSION_CODE)
|
|
}
|
|
}
|
|
}
|