From 7937a71b0d4e68fca3fc9acd9bdd86d19a68d733 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ensar=20Saraj=C4=8Di=C4=87?= Date: Mon, 2 Oct 2023 09:06:39 +0200 Subject: [PATCH] Migrate MainActivity to compose --- .../activities/BrightDisplayActivity.kt | 2 +- .../flashlight/activities/MainActivity.kt | 511 +++++++++--------- .../flashlight/screens/BrightDisplayScreen.kt | 18 +- .../flashlight/screens/MainScreen.kt | 220 ++++++++ app/src/main/res/values/dimens.xml | 1 + 5 files changed, 493 insertions(+), 259 deletions(-) create mode 100644 app/src/main/kotlin/com/simplemobiletools/flashlight/screens/MainScreen.kt diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/BrightDisplayActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/BrightDisplayActivity.kt index bfee919..38c879b 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/BrightDisplayActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/BrightDisplayActivity.kt @@ -60,7 +60,7 @@ class BrightDisplayActivity : SimpleActivity() { }, timerVisible = timerVisible, timerText = timerText, - onTimerCloseClick = { + onTimerClosePress = { stopSleepTimer() } ) diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt index 7f3170d..8a8a841 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/MainActivity.kt @@ -2,16 +2,22 @@ package com.simplemobiletools.flashlight.activities import android.annotation.SuppressLint import android.app.AlarmManager +import android.app.Application import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.ShortcutInfo -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.Icon import android.graphics.drawable.LayerDrawable import android.os.Bundle -import android.view.WindowManager -import android.widget.ImageView +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.runtime.getValue +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.google.android.material.math.MathUtils +import com.simplemobiletools.commons.compose.extensions.onEventValue +import com.simplemobiletools.commons.compose.theme.AppThemeSurface import com.simplemobiletools.commons.dialogs.PermissionRequiredDialog import com.simplemobiletools.commons.dialogs.RadioGroupDialog import com.simplemobiletools.commons.extensions.* @@ -20,186 +26,142 @@ import com.simplemobiletools.commons.models.FAQItem import com.simplemobiletools.commons.models.RadioItem import com.simplemobiletools.flashlight.BuildConfig import com.simplemobiletools.flashlight.R -import com.simplemobiletools.flashlight.databinding.ActivityMainBinding import com.simplemobiletools.flashlight.dialogs.SleepTimerCustomDialog import com.simplemobiletools.flashlight.extensions.config import com.simplemobiletools.flashlight.helpers.* import com.simplemobiletools.flashlight.models.Events +import com.simplemobiletools.flashlight.screens.MainScreen +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.map import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode import java.util.* +import kotlin.system.exitProcess class MainActivity : SimpleActivity() { companion object { private const val MAX_STROBO_DELAY = 2000L private const val MIN_STROBO_DELAY = 10L - private const val FLASHLIGHT_STATE = "flashlight_state" - private const val STROBOSCOPE_STATE = "stroboscope_state" } - private val binding by viewBinding(ActivityMainBinding::inflate) - - private var mBus: EventBus? = null - private var mCameraImpl: MyCameraImpl? = null - private var mIsFlashlightOn = false - private var reTurnFlashlightOn = true + private val viewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { isMaterialActivity = true super.onCreate(savedInstanceState) - setContentView(binding.root) appLaunched(BuildConfig.APPLICATION_ID) - setupOptionsMenu() - refreshMenuItems() - mBus = EventBus.getDefault() + setContent { + AppThemeSurface { + val showMoreApps = onEventValue { !resources.getBoolean(R.bool.hide_google_relations) } + val timerVisible by viewModel.timerVisible.collectAsStateWithLifecycle(false) + val timerText by viewModel.timerText.collectAsStateWithLifecycle() + val flashlightActive by viewModel.flashlightOn.collectAsStateWithLifecycle() + val showBrightDisplayButton by config.brightDisplayFlow.collectAsStateWithLifecycle(config.brightDisplay) + val showSosButton by config.sosFlow.collectAsStateWithLifecycle(config.sos) + val sosActive by viewModel.sosActive.collectAsStateWithLifecycle() + val showStroboscopeButton by config.stroboscopeFlow.collectAsStateWithLifecycle(config.stroboscope) + val stroboscopeActive by viewModel.stroboscopeActive.collectAsStateWithLifecycle() + val brightnessBarVisible by viewModel.brightnessBarVisible.collectAsStateWithLifecycle(false) + val brightnessBarValue by viewModel.brightnessBarValue.collectAsStateWithLifecycle() + val stroboscopeBarVisible by viewModel.stroboscopeBarVisible.collectAsStateWithLifecycle(false) + val stroboscopeBarValue by viewModel.stroboscopeBarValue.collectAsStateWithLifecycle() - binding.apply { - updateMaterialActivityViews(mainCoordinator, mainHolder, useTransparentNavigation = true, useTopSearchMenu = false) - setupMaterialScrollListener(mainNestedScrollview, mainToolbar) - - changeIconColor(getContrastColor(), stroboscopeBtn) - - brightDisplayBtn.setOnClickListener { - reTurnFlashlightOn = false - startActivity(Intent(applicationContext, BrightDisplayActivity::class.java)) + MainScreen( + timerText = timerText, + timerVisible = timerVisible, + onTimerClosePress = { stopSleepTimer() }, + onFlashlightPress = { viewModel.toggleFlashlight() }, + flashlightActive = flashlightActive, + showBrightDisplayButton = showBrightDisplayButton, + onBrightDisplayPress = { + startActivity(Intent(applicationContext, BrightDisplayActivity::class.java)) + }, + showSosButton = showSosButton, + sosActive = sosActive, + onSosButtonPress = { + toggleStroboscope(true) + }, + showStroboscopeButton = showStroboscopeButton, + stroboscopeActive = stroboscopeActive, + onStroboscopeButtonPress = { + toggleStroboscope(false) + }, + showBrightnessBar = brightnessBarVisible, + brightnessBarValue = brightnessBarValue, + onBrightnessBarValueChange = { + viewModel.updateBrightnessBarValue(it) + }, + showStroboscopeBar = stroboscopeBarVisible, + stroboscopeBarValue = stroboscopeBarValue, + onStroboscopeBarValueChange = { + viewModel.updateStroboscopeBarValue(it) + }, + showMoreApps = showMoreApps, + openSettings = ::launchSettings, + openAbout = ::launchAbout, + openSleepTimer = ::showSleepTimer, + moreAppsFromUs = ::launchMoreAppsFromUsIntent + ) } - - flashlightBtn.setOnClickListener { - mCameraImpl!!.toggleFlashlight() - } - - sosBtn.setOnClickListener { - toggleStroboscope(true) - } - - stroboscopeBtn.setOnClickListener { - toggleStroboscope(false) - } - - sleepTimerStop.setOnClickListener { stopSleepTimer() } } - setupStroboscope() checkAppOnSDCard() } override fun onResume() { super.onResume() - setupToolbar(binding.mainToolbar) - mCameraImpl!!.handleCameraSetup() - checkState(MyCameraImpl.isFlashlightOn) + viewModel.onResume() +// val contrastColor = getContrastColor() - val contrastColor = getContrastColor() - - binding.apply { - changeIconColor(contrastColor, brightDisplayBtn) - brightDisplayBtn.beVisibleIf(config.brightDisplay) - sosBtn.beVisibleIf(config.sos) - - if (sosBtn.currentTextColor != getProperPrimaryColor()) { - sosBtn.setTextColor(contrastColor) - } - - stroboscopeBtn.beVisibleIf(config.stroboscope) - - if (!config.stroboscope) { - mCameraImpl!!.stopStroboscope() - stroboscopeBar.beInvisible() - } - - updateTextColors(mainHolder) - if (stroboscopeBar.isInvisible()) { - changeIconColor(contrastColor, stroboscopeBtn) - } - } - - binding.sleepTimerHolder.background = ColorDrawable(getProperBackgroundColor()) - binding.sleepTimerStop.applyColorFilter(getProperTextColor()) +// binding.apply { +// changeIconColor(contrastColor, brightDisplayBtn) +// brightDisplayBtn.beVisibleIf(config.brightDisplay) +// sosBtn.beVisibleIf(config.sos) +// +// if (sosBtn.currentTextColor != getProperPrimaryColor()) { +// sosBtn.setTextColor(contrastColor) +// } +// +// stroboscopeBtn.beVisibleIf(config.stroboscope) +// +// if (!config.stroboscope) { +// mCameraImpl!!.stopStroboscope() +// stroboscopeBar.beInvisible() +// } +// +// updateTextColors(mainHolder) +// if (stroboscopeBar.isInvisible()) { +// changeIconColor(contrastColor, stroboscopeBtn) +// } +// } +// +// binding.sleepTimerHolder.background = ColorDrawable(getProperBackgroundColor()) +// binding.sleepTimerStop.applyColorFilter(getProperTextColor()) requestedOrientation = if (config.forcePortraitMode) ActivityInfo.SCREEN_ORIENTATION_PORTRAIT else ActivityInfo.SCREEN_ORIENTATION_SENSOR invalidateOptionsMenu() - if (config.turnFlashlightOn && reTurnFlashlightOn) { - mCameraImpl!!.enableFlashlight() - } - - reTurnFlashlightOn = true - checkShortcuts() } override fun onStart() { super.onStart() - mBus!!.register(this) if (config.sleepInTS == 0L) { - binding.sleepTimerHolder.beGone() + viewModel.hideTimer() (getSystemService(Context.ALARM_SERVICE) as AlarmManager).cancel(getShutDownPendingIntent()) } - - if (mCameraImpl == null) { - setupCameraImpl() - } - } - - override fun onStop() { - super.onStop() - mBus!!.unregister(this) - } - - override fun onDestroy() { - super.onDestroy() - releaseCamera() - } - - private fun setupOptionsMenu() { - binding.mainToolbar.setOnMenuItemClickListener { menuItem -> - when (menuItem.itemId) { - R.id.more_apps_from_us -> launchMoreAppsFromUsIntent() - R.id.settings -> launchSettings() - R.id.sleep_timer -> showSleepTimer() - R.id.about -> launchAbout() - else -> return@setOnMenuItemClickListener false - } - return@setOnMenuItemClickListener true - } - } - - private fun refreshMenuItems() { - binding.mainToolbar.menu.apply { - findItem(R.id.more_apps_from_us).isVisible = !resources.getBoolean(R.bool.hide_google_relations) - } - } - - override fun onSaveInstanceState(outState: Bundle) { - outState.putBoolean(FLASHLIGHT_STATE, mIsFlashlightOn) - outState.putBoolean(STROBOSCOPE_STATE, binding.stroboscopeBar.isVisible()) - super.onSaveInstanceState(outState) - } - - override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - val isFlashlightOn = savedInstanceState.getBoolean(FLASHLIGHT_STATE, false) - if (isFlashlightOn) { - mCameraImpl!!.toggleFlashlight() - } - - val isStroboscopeOn = savedInstanceState.getBoolean(STROBOSCOPE_STATE, false) - if (isStroboscopeOn) { - toggleStroboscope(false) - } } private fun launchSettings() { hideKeyboard() - reTurnFlashlightOn = false startActivity(Intent(applicationContext, SettingsActivity::class.java)) } private fun launchAbout() { - reTurnFlashlightOn = false val licenses = LICENSE_EVENT_BUS val faqItems = arrayListOf( @@ -215,38 +177,6 @@ class MainActivity : SimpleActivity() { startAboutActivity(R.string.app_name, licenses, BuildConfig.VERSION_NAME, faqItems, true) } - private fun setupCameraImpl() { - mCameraImpl = MyCameraImpl.newInstance(this, object : CameraTorchListener { - override fun onTorchEnabled(isEnabled: Boolean) { - mCameraImpl!!.onTorchEnabled(isEnabled) - if (mCameraImpl!!.supportsBrightnessControl()) { - binding.brightnessBar.beVisibleIf(isEnabled) - } - } - - override fun onTorchUnavailable() { - mCameraImpl!!.onCameraNotAvailable() - } - }) - if (config.turnFlashlightOn) { - mCameraImpl!!.enableFlashlight() - } - setupBrightness() - } - - private fun setupStroboscope() { - binding.stroboscopeBar.apply { - max = (MAX_STROBO_DELAY - MIN_STROBO_DELAY).toInt() - progress = config.stroboscopeProgress - onSeekBarChangeListener { progress -> - val frequency = max - progress + MIN_STROBO_DELAY - mCameraImpl?.stroboFrequency = frequency - config.stroboscopeFrequency = frequency - config.stroboscopeProgress = progress - } - } - } - private fun toggleStroboscope(isSOS: Boolean) { // use the old Camera API for stroboscope, the new Camera Manager is way too slow if (isNougatPlus()) { @@ -262,37 +192,14 @@ class MainActivity : SimpleActivity() { } } - private fun setupBrightness() { - binding.brightnessBar.apply { - max = mCameraImpl?.getMaximumBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL - progress = mCameraImpl?.getCurrentBrightnessLevel() ?: MIN_BRIGHTNESS_LEVEL - onSeekBarChangeListener { level -> - val newLevel = level.coerceAtLeast(MIN_BRIGHTNESS_LEVEL) - mCameraImpl?.updateBrightnessLevel(newLevel) - config.brightnessLevel = newLevel - } - } - } - private fun cameraPermissionGranted(isSOS: Boolean) { if (isSOS) { - val isSOSRunning = mCameraImpl!!.toggleSOS() - binding.sosBtn.setTextColor(if (isSOSRunning) getProperPrimaryColor() else getContrastColor()) - } else if (mCameraImpl!!.toggleStroboscope()) { - binding.apply { - stroboscopeBar.beInvisibleIf(stroboscopeBar.isVisible()) - changeIconColor(if (stroboscopeBar.isVisible()) getProperPrimaryColor() else getContrastColor(), stroboscopeBtn) - } + viewModel.enableSos() + } else { + viewModel.enableStroboscope() } } - private fun getContrastColor() = getProperBackgroundColor().getContrastColor() - - private fun releaseCamera() { - mCameraImpl?.releaseCamera() - mCameraImpl = null - } - private fun showSleepTimer(force: Boolean = false) { val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager if (isSPlus() && !alarmManager.canScheduleExactAlarms() && !force) { @@ -353,72 +260,15 @@ class MainActivity : SimpleActivity() { } private fun startSleepTimer() { - binding.sleepTimerHolder.fadeIn() + viewModel.showTimer() startSleepTimerCountDown() } private fun stopSleepTimer() { - binding.sleepTimerHolder.fadeOut() + viewModel.hideTimer() stopSleepTimerCountDown() } - @Subscribe(threadMode = ThreadMode.MAIN) - fun sleepTimerChanged(event: Events.SleepTimerChanged) { - binding.sleepTimerValue.text = event.seconds.getFormattedDuration() - binding.sleepTimerHolder.beVisible() - - if (event.seconds == 0) { - finish() - } - } - - @Subscribe - fun stateChangedEvent(event: Events.StateChanged) { - checkState(event.isEnabled) - } - - @Subscribe - fun stopStroboscope(event: Events.StopStroboscope) { - binding.stroboscopeBar.beInvisible() - changeIconColor(getContrastColor(), binding.stroboscopeBtn) - } - - @Subscribe - fun stopSOS(event: Events.StopSOS) { - binding.sosBtn.setTextColor(getContrastColor()) - } - - private fun checkState(isEnabled: Boolean) { - if (isEnabled) { - enableFlashlight() - } else { - disableFlashlight() - } - } - - private fun enableFlashlight() { - changeIconColor(getProperPrimaryColor(), binding.flashlightBtn) - window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - mIsFlashlightOn = true - - binding.apply { - sosBtn.setTextColor(getContrastColor()) - - changeIconColor(getContrastColor(), stroboscopeBtn) - stroboscopeBar.beInvisible() - } - } - - private fun disableFlashlight() { - changeIconColor(getContrastColor(), binding.flashlightBtn) - window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) - mIsFlashlightOn = false - } - - private fun changeIconColor(color: Int, imageView: ImageView?) { - imageView!!.background.applyColorFilter(color) - } - @SuppressLint("NewApi") private fun checkShortcuts() { val appIconColor = config.appIconColor @@ -450,9 +300,166 @@ class MainActivity : SimpleActivity() { .build() } - @Subscribe - fun cameraUnavailable(event: Events.CameraUnavailable) { - toast(R.string.camera_error) - disableFlashlight() + internal class MainViewModel( + application: Application + ) : AndroidViewModel(application) { + + + private val _timerText: MutableStateFlow = MutableStateFlow("00:00") + val timerText = _timerText.asStateFlow() + + private val _timerVisible: MutableStateFlow = MutableStateFlow(false) + val timerVisible = _timerVisible.asStateFlow() + + private val _flashlightOn: MutableStateFlow = MutableStateFlow(false) + val flashlightOn = _flashlightOn.asStateFlow() + + val brightnessBarVisible = flashlightOn.map { + it && camera.supportsBrightnessControl() + } + + private val _sosActive: MutableStateFlow = MutableStateFlow(false) + val sosActive = _sosActive.asStateFlow() + + private val _brightnessBarValue: MutableStateFlow = MutableStateFlow(0f) + val brightnessBarValue = _brightnessBarValue.asStateFlow() + + private val _stroboscopeActive: MutableStateFlow = MutableStateFlow(false) + val stroboscopeActive = _stroboscopeActive.asStateFlow() + + val stroboscopeBarVisible = stroboscopeActive.map { it } + + private val _stroboscopeBarValue: MutableStateFlow = MutableStateFlow(0f) + val stroboscopeBarValue = _stroboscopeBarValue.asStateFlow() + + private lateinit var camera: MyCameraImpl + + init { + EventBus.getDefault().register(this) + + camera = MyCameraImpl.newInstance(application, object : CameraTorchListener { + override fun onTorchEnabled(isEnabled: Boolean) { + camera.onTorchEnabled(isEnabled) + if (isEnabled && camera.supportsBrightnessControl()) { + _brightnessBarValue.value = camera.getCurrentBrightnessLevel().toFloat() / camera.getMaximumBrightnessLevel() + } + } + + override fun onTorchUnavailable() { + camera.onCameraNotAvailable() + } + }) + if (application.config.turnFlashlightOn) { + camera.enableFlashlight() + } + + _stroboscopeBarValue.value = application.config.stroboscopeProgress.toFloat() / 100 + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun sleepTimerChanged(event: Events.SleepTimerChanged) { + _timerText.value = event.seconds.getFormattedDuration() + _timerVisible.value = true + + if (event.seconds == 0) { + exitProcess(0) + } + } + + fun hideTimer() { + _timerVisible.value = false + } + + fun showTimer() { + _timerVisible.value = true + } + + fun updateBrightnessBarValue(newValue: Float) { + _brightnessBarValue.value = newValue + val max = camera.getMaximumBrightnessLevel() + val min = MIN_BRIGHTNESS_LEVEL + val newLevel = MathUtils.lerp(min.toFloat(), max.toFloat(), newValue) + camera.updateBrightnessLevel(newLevel.toInt()) + getApplication().config.brightnessLevel = newLevel.toInt() + } + + fun updateStroboscopeBarValue(newValue: Float) { + _stroboscopeBarValue.value = newValue + val max = MAX_STROBO_DELAY + val min = MIN_STROBO_DELAY + val newLevel = MathUtils.lerp(min.toFloat(), max.toFloat(), 1 - newValue) + camera.stroboFrequency = newLevel.toLong() + getApplication().config.stroboscopeFrequency = newLevel.toLong() + getApplication().config.stroboscopeProgress = ((1 - newLevel) * 100).toInt() + } + + fun onResume() { + checkState(MyCameraImpl.isFlashlightOn) + camera.handleCameraSetup() + + if (getApplication().config.turnFlashlightOn) { + camera.enableFlashlight() + } + } + + private fun checkState(isEnabled: Boolean) { + if (isEnabled) { + enableFlashlight() + } else { + disableFlashlight() + } + } + + + fun enableFlashlight() { + _flashlightOn.value = true + } + + private fun disableFlashlight() { + _flashlightOn.value = false + } + + @Subscribe + fun stateChangedEvent(event: Events.StateChanged) { + checkState(event.isEnabled) + } + + @Subscribe + fun stopStroboscope(event: Events.StopStroboscope) { + _stroboscopeActive.value = false + } + + @Subscribe + fun stopSOS(event: Events.StopSOS) { + _sosActive.value = false + } + + @Subscribe + fun cameraUnavailable(event: Events.CameraUnavailable) { + getApplication().toast(R.string.camera_error) + disableFlashlight() + } + + override fun onCleared() { + super.onCleared() + EventBus.getDefault().unregister(this) + releaseCamera() + } + + fun toggleFlashlight() { + camera.toggleFlashlight() + } + + private fun releaseCamera() { + camera.releaseCamera() + } + + fun enableSos() { + _sosActive.value = camera.toggleSOS() + } + + fun enableStroboscope() { + _stroboscopeActive.value = camera.toggleStroboscope() + } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/BrightDisplayScreen.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/BrightDisplayScreen.kt index ad309aa..4b70cde 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/BrightDisplayScreen.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/BrightDisplayScreen.kt @@ -1,9 +1,14 @@ package com.simplemobiletools.flashlight.screens -import androidx.compose.animation.* +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.foundation.background import androidx.compose.foundation.border -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -26,7 +31,7 @@ internal fun BrightDisplayScreen( timerText: String?, timerVisible: Boolean, onChangeColorPress: () -> Unit, - onTimerCloseClick: () -> Unit + onTimerClosePress: () -> Unit ) { Box( modifier = Modifier @@ -51,7 +56,8 @@ internal fun BrightDisplayScreen( } AnimatedVisibility( - modifier = Modifier.align(Alignment.BottomEnd) + modifier = Modifier + .align(Alignment.BottomEnd) .navigationBarsPadding(), visible = timerVisible, enter = fadeIn(), @@ -59,7 +65,7 @@ internal fun BrightDisplayScreen( ) { SleepTimer( timerText = timerText ?: "", - onCloseClick = onTimerCloseClick + onCloseClick = onTimerClosePress ) } } @@ -74,7 +80,7 @@ private fun BrightDisplayScreenPreview() { timerText = "00:00", timerVisible = true, onChangeColorPress = {}, - onTimerCloseClick = {} + onTimerClosePress = {} ) } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/MainScreen.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/MainScreen.kt new file mode 100644 index 0000000..43d520a --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/MainScreen.kt @@ -0,0 +1,220 @@ +package com.simplemobiletools.flashlight.screens + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.outlined.Info +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.dimensionResource +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.TextUnit +import androidx.compose.ui.unit.TextUnitType +import com.simplemobiletools.commons.compose.extensions.AdjustNavigationBarColors +import com.simplemobiletools.commons.compose.extensions.MyDevices +import com.simplemobiletools.commons.compose.menus.ActionItem +import com.simplemobiletools.commons.compose.menus.ActionMenu +import com.simplemobiletools.commons.compose.menus.OverflowMode +import com.simplemobiletools.commons.compose.settings.scaffold.SettingsLazyScaffold +import com.simplemobiletools.commons.compose.settings.scaffold.topAppBarColors +import com.simplemobiletools.commons.compose.settings.scaffold.topAppBarInsets +import com.simplemobiletools.commons.compose.settings.scaffold.topAppBarPaddings +import com.simplemobiletools.commons.compose.theme.AppThemeSurface +import com.simplemobiletools.flashlight.R +import com.simplemobiletools.flashlight.views.SleepTimer +import kotlinx.collections.immutable.toImmutableList + +@Composable +internal fun MainScreen( + timerText: String?, + timerVisible: Boolean, + onTimerClosePress: () -> Unit, + flashlightActive: Boolean, + onFlashlightPress: () -> Unit, + showBrightDisplayButton: Boolean, + onBrightDisplayPress: () -> Unit, + showSosButton: Boolean, + sosActive: Boolean, + onSosButtonPress: () -> Unit, + showStroboscopeButton: Boolean, + stroboscopeActive: Boolean, + onStroboscopeButtonPress: () -> Unit, + showBrightnessBar: Boolean, + brightnessBarValue: Float, + onBrightnessBarValueChange: (Float) -> Unit, + showStroboscopeBar: Boolean, + stroboscopeBarValue: Float, + onStroboscopeBarValueChange: (Float) -> Unit, + showMoreApps: Boolean, + openSettings: () -> Unit, + openAbout: () -> Unit, + openSleepTimer: () -> Unit, + moreAppsFromUs: () -> Unit, +) { + AdjustNavigationBarColors() + SettingsLazyScaffold( + customTopBar = { scrolledColor: Color, _: MutableInteractionSource, scrollBehavior: TopAppBarScrollBehavior, statusBarColor: Int, colorTransitionFraction: Float, contrastColor: Color -> + TopAppBar( + title = {}, + actions = { + val actionMenus = remember { + val settings = + ActionItem(R.string.settings, icon = Icons.Filled.Settings, doAction = openSettings, overflowMode = OverflowMode.NEVER_OVERFLOW) + val about = ActionItem(R.string.about, icon = Icons.Outlined.Info, doAction = openAbout, overflowMode = OverflowMode.NEVER_OVERFLOW) + val sleepTimer = ActionItem(R.string.sleep_timer, doAction = openSleepTimer, overflowMode = OverflowMode.ALWAYS_OVERFLOW) + val list = mutableListOf(settings, about, sleepTimer) + if (showMoreApps) { + list += ActionItem(R.string.more_apps_from_us, doAction = moreAppsFromUs, overflowMode = OverflowMode.ALWAYS_OVERFLOW) + } + list.toImmutableList() + } + var isMenuVisible by remember { mutableStateOf(false) } + ActionMenu( + items = actionMenus, + numIcons = 2, + isMenuVisible = isMenuVisible, + onMenuToggle = { isMenuVisible = it }, + iconsColor = scrolledColor + ) + }, + scrollBehavior = scrollBehavior, + colors = topAppBarColors(statusBarColor, colorTransitionFraction, contrastColor), + modifier = Modifier.topAppBarPaddings(), + windowInsets = topAppBarInsets() + ) + }) { + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.SpaceEvenly + ) { + Icon( + modifier = Modifier + .size(dimensionResource(id = R.dimen.main_button_size)) + .padding(vertical = dimensionResource(id = R.dimen.normal_margin)) + .clickable(onClick = onFlashlightPress), + painter = painterResource(id = R.drawable.ic_flashlight_vector), + contentDescription = stringResource(id = R.string.flashlight_short), + tint = if (flashlightActive) MaterialTheme.colorScheme.primary else Color.Unspecified + ) + + if (showBrightDisplayButton) { + Icon( + modifier = Modifier + .size(dimensionResource(id = R.dimen.smaller_button_size)) + .padding(vertical = dimensionResource(id = R.dimen.normal_margin)) + .clickable(onClick = onBrightDisplayPress), + painter = painterResource(id = R.drawable.ic_bright_display_vector), + contentDescription = stringResource(id = R.string.bright_display) + ) + } + + if (showSosButton) { + Text( + modifier = Modifier + .padding(vertical = dimensionResource(id = R.dimen.normal_margin)) + .clickable(onClick = onSosButtonPress), + text = "SOS", + fontSize = TextUnit(dimensionResource(id = R.dimen.sos_text_size).value, TextUnitType.Sp), + fontWeight = FontWeight.Bold, + color = if (sosActive) MaterialTheme.colorScheme.primary else Color.Unspecified + ) + } + + if (showStroboscopeButton) { + Icon( + modifier = Modifier + .size(dimensionResource(id = R.dimen.smaller_button_size)) + .padding(vertical = dimensionResource(id = R.dimen.normal_margin)) + .clickable(onClick = onStroboscopeButtonPress), + painter = painterResource(id = R.drawable.ic_stroboscope_vector), + contentDescription = "", + tint = if (stroboscopeActive) MaterialTheme.colorScheme.primary else Color.Unspecified + ) + } + + val sliderModifier = Modifier + .padding(dimensionResource(id = R.dimen.activity_margin)) + .padding(vertical = dimensionResource(R.dimen.medium_margin)) + .padding(bottom = dimensionResource(id = R.dimen.activity_margin)) + .size(width = dimensionResource(id = R.dimen.seekbar_width), height = dimensionResource(id = R.dimen.seekbar_height)) + + if (showBrightnessBar) { + Slider( + modifier = sliderModifier, + value = brightnessBarValue, + onValueChange = onBrightnessBarValueChange + ) + } + + if (showStroboscopeBar) { + Slider( + modifier = sliderModifier, + value = stroboscopeBarValue, + onValueChange = onStroboscopeBarValueChange + ) + } + + if (!showBrightnessBar && !showStroboscopeBar) { + Spacer( + modifier = sliderModifier, + ) + } + } + + AnimatedVisibility( + modifier = Modifier.align(Alignment.BottomEnd), + visible = timerVisible, + enter = fadeIn(), + exit = fadeOut(), + ) { + SleepTimer( + timerText = timerText ?: "", + onCloseClick = onTimerClosePress + ) + } + } +} + +@Composable +@MyDevices +internal fun MainScreenPreview() { + AppThemeSurface { + MainScreen( + timerText = "00:00", + timerVisible = true, + onTimerClosePress = {}, + onFlashlightPress = {}, + flashlightActive = true, + showBrightDisplayButton = true, + onBrightDisplayPress = {}, + showSosButton = true, + sosActive = false, + onSosButtonPress = {}, + showStroboscopeButton = true, + stroboscopeActive = false, + onStroboscopeButtonPress = {}, + showBrightnessBar = false, + brightnessBarValue = 0f, + onBrightnessBarValueChange = {}, + showStroboscopeBar = false, + stroboscopeBarValue = 0f, + onStroboscopeBarValueChange = {}, + showMoreApps = true, + openSettings = {}, + openAbout = {}, + moreAppsFromUs = {}, + openSleepTimer = {} + ) + } +} diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1c6dbeb..cc368b4 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -1,5 +1,6 @@ 250dp + 20dp 180dp 60dp 60dp