diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8316dd0..0cfc92f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -43,6 +43,11 @@ android { buildFeatures { viewBinding = true buildConfig = true + compose = true + } + + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() } buildTypes { @@ -80,6 +85,13 @@ android { tasks.withType { kotlinOptions.jvmTarget = project.libs.versions.app.build.kotlinJVMTarget.get() + kotlinOptions.freeCompilerArgs = listOf( + "-opt-in=kotlin.RequiresOptIn", + "-opt-in=androidx.compose.material3.ExperimentalMaterial3Api", + "-opt-in=androidx.compose.material.ExperimentalMaterialApi", + "-opt-in=androidx.compose.foundation.ExperimentalFoundationApi", + "-Xcontext-receivers" + ) } namespace = libs.versions.app.version.appId.get() @@ -95,4 +107,7 @@ dependencies { implementation(libs.androidx.constraintlayout) implementation(libs.eventbus) + implementation(libs.bundles.lifecycle) + implementation(libs.bundles.compose) + debugImplementation(libs.bundles.compose.preview) } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/SettingsActivity.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/SettingsActivity.kt index 3ae7603..e4d8574 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/SettingsActivity.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/activities/SettingsActivity.kt @@ -2,145 +2,64 @@ package com.simplemobiletools.flashlight.activities import android.content.Intent import android.os.Bundle -import com.simplemobiletools.commons.extensions.* +import androidx.activity.compose.setContent +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.simplemobiletools.commons.compose.extensions.enableEdgeToEdgeSimple +import com.simplemobiletools.commons.compose.theme.AppThemeSurface import com.simplemobiletools.commons.helpers.IS_CUSTOMIZING_COLORS -import com.simplemobiletools.commons.helpers.NavigationIcon -import com.simplemobiletools.commons.helpers.isTiramisuPlus -import com.simplemobiletools.flashlight.databinding.ActivitySettingsBinding import com.simplemobiletools.flashlight.extensions.config +import com.simplemobiletools.flashlight.screens.SettingsScreen import java.util.Locale -import kotlin.system.exitProcess class SettingsActivity : SimpleActivity() { - private val binding by viewBinding(ActivitySettingsBinding::inflate) + private val preferences by lazy { config } override fun onCreate(savedInstanceState: Bundle?) { - isMaterialActivity = true super.onCreate(savedInstanceState) - setContentView(binding.root) + enableEdgeToEdgeSimple() + setContent { + AppThemeSurface { + val displayLanguage = remember { Locale.getDefault().displayLanguage } + val turnFlashlightOnStartupFlow by preferences.turnFlashlightOnFlow.collectAsStateWithLifecycle(preferences.turnFlashlightOn) + val forcePortraitModeFlow by preferences.forcePortraitModeFlow.collectAsStateWithLifecycle(preferences.forcePortraitMode) + val showBrightDisplayButtonFlow by preferences.brightDisplayFlow.collectAsStateWithLifecycle(preferences.brightDisplay) + val showSosButtonFlow by preferences.sosFlow.collectAsStateWithLifecycle(preferences.sos) + val showStroboscopeButtonFlow by preferences.stroboscopeFlow.collectAsStateWithLifecycle(preferences.stroboscope) - binding.apply { - updateMaterialActivityViews(settingsCoordinator, settingsHolder, useTransparentNavigation = true, useTopSearchMenu = false) - setupMaterialScrollListener(settingsNestedScrollview, settingsToolbar) - } - } - - override fun onResume() { - super.onResume() - setupToolbar(binding.settingsToolbar, NavigationIcon.Arrow) - - setupPurchaseThankYou() - setupCustomizeColors() - setupCustomizeWidgetColors() - setupUseEnglish() - setupLanguage() - setupTurnFlashlightOn() - setupBrightDisplay() - setupStroboscope() - setupSOS() - setupForcePortrait() - updateTextColors(binding.settingsHolder) - - arrayOf(binding.settingsColorCustomizationSectionLabel, binding.settingsGeneralSettingsLabel).forEach { - it.setTextColor(getProperPrimaryColor()) - } - } - - private fun setupPurchaseThankYou() { - binding.settingsPurchaseThankYouHolder.apply { - beGoneIf(isOrWasThankYouInstalled()) - setOnClickListener { - launchPurchaseThankYouIntent() - } - } - } - - private fun setupCustomizeColors() { - binding.apply { - settingsColorCustomizationLabel.text = getCustomizeColorsString() - settingsColorCustomizationHolder.setOnClickListener { - handleCustomizeColorsClick() - } - } - } - - private fun setupCustomizeWidgetColors() { - binding.settingsWidgetColorCustomizationHolder.setOnClickListener { - Intent(this, WidgetTorchConfigureActivity::class.java).apply { - putExtra(IS_CUSTOMIZING_COLORS, true) - startActivity(this) - } - } - } - - private fun setupUseEnglish() { - binding.apply { - settingsUseEnglishHolder.beVisibleIf((config.wasUseEnglishToggled || Locale.getDefault().language != "en") && !isTiramisuPlus()) - settingsUseEnglish.isChecked = config.useEnglish - settingsUseEnglishHolder.setOnClickListener { - settingsUseEnglish.toggle() - config.useEnglish = settingsUseEnglish.isChecked - exitProcess(0) - } - } - } - - private fun setupLanguage() { - binding.apply { - settingsLanguage.text = Locale.getDefault().displayLanguage - settingsLanguageHolder.beVisibleIf(isTiramisuPlus()) - settingsLanguageHolder.setOnClickListener { - launchChangeAppLanguageIntent() - } - } - } - - private fun setupTurnFlashlightOn() { - binding.apply { - settingsTurnFlashlightOn.isChecked = config.turnFlashlightOn - settingsTurnFlashlightOnHolder.setOnClickListener { - settingsTurnFlashlightOn.toggle() - config.turnFlashlightOn = settingsTurnFlashlightOn.isChecked - } - } - } - - private fun setupBrightDisplay() { - binding.apply { - settingsBrightDisplay.isChecked = config.brightDisplay - settingsBrightDisplayHolder.setOnClickListener { - settingsBrightDisplay.toggle() - config.brightDisplay = settingsBrightDisplay.isChecked - } - } - } - - private fun setupStroboscope() { - binding.apply { - settingsStroboscope.isChecked = config.stroboscope - settingsStroboscopeHolder.setOnClickListener { - settingsStroboscope.toggle() - config.stroboscope = settingsStroboscope.isChecked - } - } - } - - private fun setupSOS() { - binding.apply { - settingsSos.isChecked = config.sos - settingsSosHolder.setOnClickListener { - settingsSos.toggle() - config.sos = settingsSos.isChecked - } - } - } - - private fun setupForcePortrait() { - binding.apply { - settingsForcePortrait.isChecked = config.forcePortraitMode - settingsForcePortraitHolder.setOnClickListener { - settingsForcePortrait.toggle() - config.forcePortraitMode = settingsForcePortrait.isChecked + SettingsScreen( + displayLanguage = displayLanguage, + onSetupLanguagePress = ::launchChangeAppLanguageIntent, + customizeColors = ::startCustomizationActivity, + turnFlashlightOnStartupChecked = turnFlashlightOnStartupFlow, + forcePortraitModeChecked = forcePortraitModeFlow, + showBrightDisplayButtonChecked = showBrightDisplayButtonFlow, + showSosButtonChecked = showSosButtonFlow, + showStroboscopeButtonChecked = showStroboscopeButtonFlow, + customizeWidgetColors = { + Intent(this, WidgetTorchConfigureActivity::class.java).apply { + putExtra(IS_CUSTOMIZING_COLORS, true) + startActivity(this) + } + }, + onTurnFlashlightOnStartupPress = { + preferences.turnFlashlightOn = it + }, + onForcePortraitModePress = { + preferences.forcePortraitMode = it + }, + onShowBrightDisplayButtonPress = { + preferences.brightDisplay = it + }, + onShowSosButtonPress = { + preferences.sos = it + }, + onShowStroboscopeButtonPress = { + preferences.stroboscope = it + }, + goBack = ::finish + ) } } } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt index d6292b6..16301a2 100644 --- a/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/helpers/Config.kt @@ -2,7 +2,11 @@ package com.simplemobiletools.flashlight.helpers import android.content.Context import android.graphics.Color +import com.simplemobiletools.commons.extensions.sharedPreferencesCallback import com.simplemobiletools.commons.helpers.BaseConfig +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterNotNull +import kotlin.reflect.KProperty0 class Config(context: Context) : BaseConfig(context) { companion object { @@ -13,18 +17,26 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getBoolean(BRIGHT_DISPLAY, true) set(brightDisplay) = prefs.edit().putBoolean(BRIGHT_DISPLAY, brightDisplay).apply() + val brightDisplayFlow = ::brightDisplay.asFlow() + var stroboscope: Boolean get() = prefs.getBoolean(STROBOSCOPE, true) set(stroboscope) = prefs.edit().putBoolean(STROBOSCOPE, stroboscope).apply() + val stroboscopeFlow = ::stroboscope.asFlow() + var sos: Boolean get() = prefs.getBoolean(SOS, true) set(sos) = prefs.edit().putBoolean(SOS, sos).apply() + val sosFlow = ::sos.asFlow() + var turnFlashlightOn: Boolean get() = prefs.getBoolean(TURN_FLASHLIGHT_ON, false) set(turnFlashlightOn) = prefs.edit().putBoolean(TURN_FLASHLIGHT_ON, turnFlashlightOn).apply() + val turnFlashlightOnFlow = ::turnFlashlightOn.asFlow() + var stroboscopeProgress: Int get() = prefs.getInt(STROBOSCOPE_PROGRESS, 1000) set(stroboscopeProgress) = prefs.edit().putInt(STROBOSCOPE_PROGRESS, stroboscopeProgress).apply() @@ -41,6 +53,8 @@ class Config(context: Context) : BaseConfig(context) { get() = prefs.getBoolean(FORCE_PORTRAIT_MODE, true) set(forcePortraitMode) = prefs.edit().putBoolean(FORCE_PORTRAIT_MODE, forcePortraitMode).apply() + val forcePortraitModeFlow = ::forcePortraitMode.asFlow() + var brightnessLevel: Int get() = prefs.getInt(BRIGHTNESS_LEVEL, DEFAULT_BRIGHTNESS_LEVEL) set(brightnessLevel) = prefs.edit().putInt(BRIGHTNESS_LEVEL, brightnessLevel).apply() @@ -52,4 +66,6 @@ class Config(context: Context) : BaseConfig(context) { var sleepInTS: Long get() = prefs.getLong(SLEEP_IN_TS, 0) set(sleepInTS) = prefs.edit().putLong(SLEEP_IN_TS, sleepInTS).apply() + + private fun KProperty0.asFlow(): Flow = prefs.run { sharedPreferencesCallback { this@asFlow.get() } }.filterNotNull() } diff --git a/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/SettingsScreen.kt b/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/SettingsScreen.kt new file mode 100644 index 0000000..68b03e5 --- /dev/null +++ b/app/src/main/kotlin/com/simplemobiletools/flashlight/screens/SettingsScreen.kt @@ -0,0 +1,114 @@ +package com.simplemobiletools.flashlight.screens + +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.simplemobiletools.commons.compose.extensions.MyDevices +import com.simplemobiletools.commons.compose.settings.SettingsCheckBoxComponent +import com.simplemobiletools.commons.compose.settings.SettingsGroup +import com.simplemobiletools.commons.compose.settings.SettingsPreferenceComponent +import com.simplemobiletools.commons.compose.settings.SettingsTitleTextComponent +import com.simplemobiletools.commons.compose.settings.scaffold.SettingsScaffold +import com.simplemobiletools.commons.compose.theme.AppThemeSurface +import com.simplemobiletools.commons.compose.theme.divider_grey +import com.simplemobiletools.commons.helpers.isTiramisuPlus +import com.simplemobiletools.flashlight.R + +@Composable +internal fun SettingsScreen( + displayLanguage: String, + turnFlashlightOnStartupChecked: Boolean, + forcePortraitModeChecked: Boolean, + showBrightDisplayButtonChecked: Boolean, + showSosButtonChecked: Boolean, + showStroboscopeButtonChecked: Boolean, + onSetupLanguagePress: () -> Unit, + customizeColors: () -> Unit, + customizeWidgetColors: () -> Unit, + onTurnFlashlightOnStartupPress: (Boolean) -> Unit, + onForcePortraitModePress: (Boolean) -> Unit, + onShowBrightDisplayButtonPress: (Boolean) -> Unit, + onShowSosButtonPress: (Boolean) -> Unit, + onShowStroboscopeButtonPress: (Boolean) -> Unit, + goBack: () -> Unit, +) { + SettingsScaffold(title = stringResource(id = R.string.settings), goBack = goBack) { + SettingsGroup(title = { + SettingsTitleTextComponent(text = stringResource(id = R.string.color_customization)) + }) { + SettingsPreferenceComponent( + preferenceTitle = stringResource(id = R.string.customize_colors), + doOnPreferenceClick = customizeColors, + ) + SettingsPreferenceComponent( + preferenceTitle = stringResource(id = R.string.customize_widget_colors), + doOnPreferenceClick = customizeWidgetColors + ) + } + HorizontalDivider(color = divider_grey) + SettingsGroup(title = { + SettingsTitleTextComponent(text = stringResource(id = R.string.general_settings)) + }) { + + if (isTiramisuPlus()) { + SettingsPreferenceComponent( + preferenceTitle = stringResource(id = R.string.language), + preferenceSummary = displayLanguage, + doOnPreferenceClick = onSetupLanguagePress, + preferenceSummaryColor = MaterialTheme.colorScheme.onSurface, + ) + } + SettingsCheckBoxComponent( + title = stringResource(id = R.string.turn_flashlight_on), + initialValue = turnFlashlightOnStartupChecked, + onChange = onTurnFlashlightOnStartupPress + ) + SettingsCheckBoxComponent( + title = stringResource(id = R.string.force_portrait_mode), + initialValue = forcePortraitModeChecked, + onChange = onForcePortraitModePress + ) + SettingsCheckBoxComponent( + title = stringResource(id = R.string.show_bright_display), + initialValue = showBrightDisplayButtonChecked, + onChange = onShowBrightDisplayButtonPress + ) + SettingsCheckBoxComponent( + title = stringResource(id = R.string.show_sos), + initialValue = showSosButtonChecked, + onChange = onShowSosButtonPress + ) + SettingsCheckBoxComponent( + title = stringResource(id = R.string.show_stroboscope), + initialValue = showStroboscopeButtonChecked, + onChange = onShowStroboscopeButtonPress + ) + } + } +} + +@Composable +@MyDevices +private fun SettingsScreenPreview() { + AppThemeSurface { + SettingsScreen( + displayLanguage = "English", + turnFlashlightOnStartupChecked = false, + forcePortraitModeChecked = true, + showBrightDisplayButtonChecked = true, + showSosButtonChecked = true, + showStroboscopeButtonChecked = true, + onSetupLanguagePress = {}, + customizeColors = {}, + customizeWidgetColors = {}, + onTurnFlashlightOnStartupPress = {}, + onForcePortraitModePress = {}, + onShowBrightDisplayButtonPress = {}, + onShowSosButtonPress = {}, + onShowStroboscopeButtonPress = {}, + goBack = {}, + ) + } +} + diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml deleted file mode 100644 index 7a764b9..0000000 --- a/app/src/main/res/layout/activity_settings.xml +++ /dev/null @@ -1,212 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c564a84..c1c3ef6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,12 +1,20 @@ [versions] #jetbrains -kotlin = "1.9.0" +kotlin = "1.9.10" #AndroidX androidx-constraintlayout = "2.1.4" +androidx-customView = "1.2.0-alpha02" +androidx-customViewPooling = "1.0.0" +androidx-lifecycle = "2.7.0-alpha02" #EventBus eventbusVersion = "3.3.1" #Simple tools -simple-commons = "de113ad025" +simple-commons = "f87e960171" +#Compose +composeActivity = "1.8.0-rc01" +compose = "1.6.0-alpha06" +composeCompiler = "1.5.3" +composeMaterial3 = "1.2.0-alpha08" #Gradle gradlePlugins-agp = "8.1.1" #build @@ -20,10 +28,54 @@ app-version-appId = "com.simplemobiletools.flashlight" app-version-versionCode = "65" app-version-versionName = "5.10.0" [libraries] -#Simple Mobile Tools +#AndroidX androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintlayout" } +androidx-customView = { module = "androidx.customview:customview", version.ref = "androidx-customView" } +androidx-customViewPooling = { module = "androidx.customview:customview-poolingcontainer", version.ref = "androidx-customViewPooling" } +#Android X lifecycle +androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewModel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } +androidx-lifecycle-viewModel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-compose = { module = "androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } +#EventBus eventbus = { module = "org.greenrobot:eventbus", version.ref = "eventbusVersion" } +#Simple Mobile Tools simple-tools-commons = { module = "com.github.SimpleMobileTools:Simple-Commons", version.ref = "simple-commons" } +#Compose +compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "composeCompiler" } +compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } +compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "composeMaterial3" } +compose-material2 = { module = "androidx.compose.material:material", version.ref = "compose" } +compose-material-icons = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } +compose-animation = { module = "androidx.compose.animation:animation", version.ref = "compose" } +compose-activity = { module = "androidx.activity:activity-compose", version.ref = "composeActivity" } +compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } +compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } +compose-uiTooling-debug = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } +compose-uiTooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } +[bundles] +compose = [ + "compose-activity", + "compose-animation", + "compose-compiler", + "compose-foundation", + "compose-material-icons", + "compose-material3", + "compose-runtime", + "compose-ui", + "compose-uiTooling-preview", +] +compose-preview = [ + "androidx-customView", + "androidx-customViewPooling", + "compose-uiTooling-debug", +] +lifecycle = [ + "androidx-lifecycle-compose", + "androidx-lifecycle-runtime", + "androidx-lifecycle-viewModel", + "androidx-lifecycle-viewModel-compose", +] [plugins] android = { id = "com.android.application", version.ref = "gradlePlugins-agp" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }