Initial migration to new Compose settings activity and DataStore
This commit is contained in:
parent
912982aba8
commit
89db713e24
@ -21,6 +21,7 @@ import de.mm20.launcher2.websites.websitesModule
|
|||||||
import de.mm20.launcher2.widgets.widgetsModule
|
import de.mm20.launcher2.widgets.widgetsModule
|
||||||
import de.mm20.launcher2.wikipedia.wikipediaModule
|
import de.mm20.launcher2.wikipedia.wikipediaModule
|
||||||
import de.mm20.launcher2.database.databaseModule
|
import de.mm20.launcher2.database.databaseModule
|
||||||
|
import de.mm20.launcher2.preferences.preferencesModule
|
||||||
import de.mm20.launcher2.weather.weatherModule
|
import de.mm20.launcher2.weather.weatherModule
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
@ -66,6 +67,7 @@ class LauncherApplication : Application(), CoroutineScope {
|
|||||||
hiddenItemsModule,
|
hiddenItemsModule,
|
||||||
iconsModule,
|
iconsModule,
|
||||||
musicModule,
|
musicModule,
|
||||||
|
preferencesModule,
|
||||||
searchModule,
|
searchModule,
|
||||||
unitConverterModule,
|
unitConverterModule,
|
||||||
weatherModule,
|
weatherModule,
|
||||||
|
|||||||
@ -11,7 +11,7 @@ object AppLicense {
|
|||||||
copyrightNote = "Copyright (C) 2021 MM2-0",
|
copyrightNote = "Copyright (C) 2021 MM2-0",
|
||||||
licenseName = R.string.gpl3_name,
|
licenseName = R.string.gpl3_name,
|
||||||
licenseText = R.raw.license_gpl_3,
|
licenseText = R.raw.license_gpl_3,
|
||||||
url = "https://github.com/Kvaesitso"
|
url = "https://github.com/MM2-0/Kvaesitso"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,6 +29,12 @@
|
|||||||
<item name="android:windowLightStatusBar">true</item>
|
<item name="android:windowLightStatusBar">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="SettingsTheme.NoActionBar" parent="BaseTheme">
|
||||||
|
<item name="windowActionBar">false</item>
|
||||||
|
<item name="windowNoTitle">true</item>
|
||||||
|
<item name="android:statusBarColor">?colorSurface</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="LauncherTheme" parent="BaseTheme">
|
<style name="LauncherTheme" parent="BaseTheme">
|
||||||
<item name="android:windowBackground">@android:color/transparent</item>
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
<item name="android:windowShowWallpaper">true</item>
|
<item name="android:windowShowWallpaper">true</item>
|
||||||
|
|||||||
@ -63,6 +63,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.preference)
|
implementation(libs.androidx.preference)
|
||||||
api(libs.androidx.datastore)
|
api(libs.androidx.datastore)
|
||||||
api(libs.protobuf.javalite)
|
api(libs.protobuf.javalite)
|
||||||
|
implementation(libs.koin.android)
|
||||||
|
|
||||||
implementation(project(":ktx"))
|
implementation(project(":ktx"))
|
||||||
implementation(project(":i18n"))
|
implementation(project(":i18n"))
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import android.content.Context
|
|||||||
import androidx.datastore.core.DataStore
|
import androidx.datastore.core.DataStore
|
||||||
import androidx.datastore.dataStore
|
import androidx.datastore.dataStore
|
||||||
|
|
||||||
val Context.dataStore: DataStore<Settings> by dataStore(
|
typealias LauncherDataStore = DataStore<Settings>
|
||||||
|
|
||||||
|
val Context.dataStore: LauncherDataStore by dataStore(
|
||||||
fileName = "settings.pb",
|
fileName = "settings.pb",
|
||||||
serializer = SettingsSerializer
|
serializer = SettingsSerializer
|
||||||
)
|
)
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package de.mm20.launcher2.preferences
|
||||||
|
|
||||||
|
import org.koin.android.ext.koin.androidContext
|
||||||
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
val preferencesModule = module {
|
||||||
|
single { androidContext().dataStore }
|
||||||
|
}
|
||||||
@ -40,6 +40,24 @@
|
|||||||
android:windowSoftInputMode="stateHidden">
|
android:windowSoftInputMode="stateHidden">
|
||||||
|
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".settings.SettingsActivity"
|
||||||
|
android:label="@string/title_activity_settings"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:exported="true"
|
||||||
|
android:parentActivityName=".legacy.activity.LauncherActivity"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
|
android:taskAffinity="de.mm20.launcher2.settings"
|
||||||
|
android:theme="@style/SettingsTheme.NoActionBar">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.APPLICATION_PREFERENCES" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="de.mm20.launcher2.ui.legacy.activity.LauncherActivity" />
|
||||||
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@ -1,12 +1,17 @@
|
|||||||
package de.mm20.launcher2.ui.component.preferences
|
package de.mm20.launcher2.ui.component.preferences
|
||||||
|
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.Card
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.RadioButton
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.*
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -36,8 +41,10 @@ fun <T> ListPreference(
|
|||||||
)
|
)
|
||||||
if (showDialog) {
|
if (showDialog) {
|
||||||
Dialog(onDismissRequest = { showDialog = false }) {
|
Dialog(onDismissRequest = { showDialog = false }) {
|
||||||
Card(
|
Surface(
|
||||||
elevation = 16.dp,
|
tonalElevation = 16.dp,
|
||||||
|
shadowElevation = 16.dp,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp),
|
.padding(vertical = 16.dp),
|
||||||
@ -47,13 +54,15 @@ fun <T> ListPreference(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = title,
|
text = title,
|
||||||
style = MaterialTheme.typography.headlineMedium,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
modifier = Modifier.padding(
|
modifier = Modifier.padding(
|
||||||
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
|
start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp)
|
||||||
) {
|
) {
|
||||||
items(items) {
|
items(items) {
|
||||||
Row(
|
Row(
|
||||||
@ -64,7 +73,7 @@ fun <T> ListPreference(
|
|||||||
onValueChanged(it.value)
|
onValueChanged(it.value)
|
||||||
showDialog = false
|
showDialog = false
|
||||||
}
|
}
|
||||||
.padding(horizontal = 24.dp, vertical = 4.dp)
|
.padding(start = 16.dp, top = 4.dp, bottom = 4.dp, end = 24.dp)
|
||||||
) {
|
) {
|
||||||
RadioButton(selected = it.value == value, onClick = {
|
RadioButton(selected = it.value == value, onClick = {
|
||||||
onValueChanged(it.value)
|
onValueChanged(it.value)
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
package de.mm20.launcher2.ui.component.preferences
|
package de.mm20.launcher2.ui.component.preferences
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@ -14,26 +14,25 @@ fun PreferenceCategory(
|
|||||||
title: String? = null,
|
title: String? = null,
|
||||||
content: @Composable ColumnScope.() -> Unit
|
content: @Composable ColumnScope.() -> Unit
|
||||||
) {
|
) {
|
||||||
Surface(
|
Column {
|
||||||
shadowElevation = 2.dp,
|
if (title != null) {
|
||||||
tonalElevation = 2.dp,
|
Row(
|
||||||
modifier = Modifier.padding(bottom = 4.dp).fillMaxWidth()
|
modifier = Modifier
|
||||||
) {
|
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
||||||
Column {
|
) {
|
||||||
if (title != null) {
|
Text(
|
||||||
Row(
|
modifier = Modifier.padding(start = 56.dp),
|
||||||
modifier = Modifier
|
text = title,
|
||||||
.padding(start = 16.dp, top = 16.dp, end = 16.dp)
|
style = MaterialTheme.typography.titleSmall,
|
||||||
) {
|
color = MaterialTheme.colorScheme.primary
|
||||||
Text(
|
)
|
||||||
modifier = Modifier.padding(start = 56.dp),
|
|
||||||
text = title,
|
|
||||||
style = MaterialTheme.typography.titleSmall,
|
|
||||||
color = MaterialTheme.colorScheme.primary
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
content()
|
|
||||||
}
|
}
|
||||||
|
content()
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(0.5.dp).background(
|
||||||
|
MaterialTheme.colorScheme.onSurface.copy(alpha = 0.12f)
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package de.mm20.launcher2.ui.component.preferences
|
package de.mm20.launcher2.ui.component.preferences
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
@ -11,6 +12,7 @@ import androidx.compose.material3.*
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import com.google.accompanist.insets.systemBarsPadding
|
import com.google.accompanist.insets.systemBarsPadding
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
||||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
@ -26,18 +28,22 @@ fun PreferenceScreen(
|
|||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
systemUiController.setStatusBarColor(MaterialTheme.colorScheme.surface)
|
systemUiController.setStatusBarColor(MaterialTheme.colorScheme.surface)
|
||||||
systemUiController.setNavigationBarColor(Color.Black)
|
systemUiController.setNavigationBarColor(Color.Black)
|
||||||
|
|
||||||
|
val activity = LocalContext.current as? AppCompatActivity
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.systemBarsPadding()
|
modifier = Modifier.systemBarsPadding()
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
SmallTopAppBar(
|
CenterAlignedTopAppBar(
|
||||||
title = {
|
title = {
|
||||||
Text(title)
|
Text(title)
|
||||||
},
|
},
|
||||||
navigationIcon = {
|
navigationIcon = {
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
navController?.navigateUp()
|
if (navController?.navigateUp() != true) {
|
||||||
|
activity?.onBackPressed()
|
||||||
|
}
|
||||||
}) {
|
}) {
|
||||||
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -59,6 +59,7 @@ import de.mm20.launcher2.ui.launcher.modals.HiddenItemsView
|
|||||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||||
import de.mm20.launcher2.ui.legacy.component.WidgetView
|
import de.mm20.launcher2.ui.legacy.component.WidgetView
|
||||||
import de.mm20.launcher2.ui.legacy.helper.ThemeHelper
|
import de.mm20.launcher2.ui.legacy.helper.ThemeHelper
|
||||||
|
import de.mm20.launcher2.ui.settings.SettingsActivity
|
||||||
import de.mm20.launcher2.weather.WeatherViewModel
|
import de.mm20.launcher2.weather.WeatherViewModel
|
||||||
import de.mm20.launcher2.widgets.Widget
|
import de.mm20.launcher2.widgets.Widget
|
||||||
import de.mm20.launcher2.widgets.WidgetType
|
import de.mm20.launcher2.widgets.WidgetType
|
||||||
@ -295,14 +296,7 @@ class LauncherActivity : AppCompatActivity() {
|
|||||||
menu.setOnMenuItemClickListener { item ->
|
menu.setOnMenuItemClickListener { item ->
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.menu_item_settings -> {
|
R.id.menu_item_settings -> {
|
||||||
finish()
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
startActivity(Intent().also {
|
|
||||||
it.component = ComponentName(
|
|
||||||
packageName,
|
|
||||||
"de.mm20.launcher2.activity.SettingsActivity"
|
|
||||||
)
|
|
||||||
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
R.id.menu_item_wallpaper -> {
|
R.id.menu_item_wallpaper -> {
|
||||||
startActivity(
|
startActivity(
|
||||||
@ -335,6 +329,16 @@ class LauncherActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
R.id.menu_item_settings_old -> {
|
||||||
|
finish()
|
||||||
|
startActivity(Intent().also {
|
||||||
|
it.component = ComponentName(
|
||||||
|
packageName,
|
||||||
|
"de.mm20.launcher2.activity.SettingsActivity"
|
||||||
|
)
|
||||||
|
it.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,109 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||||
|
import com.google.accompanist.navigation.animation.composable
|
||||||
|
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||||
|
import de.mm20.launcher2.licenses.AppLicense
|
||||||
|
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
||||||
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.legacy.helper.ThemeHelper
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
import de.mm20.launcher2.ui.screens.settings.SettingsLicenseScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.about.AboutScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.appearance.AppearanceScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.license.LicenseScreen
|
||||||
|
import de.mm20.launcher2.ui.settings.main.MainScreen
|
||||||
|
|
||||||
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
private val viewModel: SettingsActivityVM by viewModels()
|
||||||
|
|
||||||
|
@OptIn(ExperimentalAnimationApi::class)
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
val theme = viewModel.getTheme()
|
||||||
|
when (theme) {
|
||||||
|
Settings.AppearanceSettings.Theme.Light -> AppCompatDelegate.setDefaultNightMode(
|
||||||
|
AppCompatDelegate.MODE_NIGHT_NO
|
||||||
|
)
|
||||||
|
Settings.AppearanceSettings.Theme.Dark -> AppCompatDelegate.setDefaultNightMode(
|
||||||
|
AppCompatDelegate.MODE_NIGHT_YES
|
||||||
|
)
|
||||||
|
else -> AppCompatDelegate.setDefaultNightMode(
|
||||||
|
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModel.theme.observe(this) {
|
||||||
|
if (it != theme && it != null) recreate()
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorScheme = viewModel.getColorScheme()
|
||||||
|
val colorSchemeThemeId = when(colorScheme) {
|
||||||
|
Settings.AppearanceSettings.ColorScheme.BlackAndWhite -> R.style.BlackWhiteColors
|
||||||
|
else -> R.style.DefaultColors
|
||||||
|
}
|
||||||
|
this.theme.applyStyle(colorSchemeThemeId, true)
|
||||||
|
|
||||||
|
viewModel.colorScheme.observe(this) {
|
||||||
|
if (it != colorScheme && it != null) recreate()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setContent {
|
||||||
|
val navController = rememberAnimatedNavController()
|
||||||
|
CompositionLocalProvider(LocalNavController provides navController) {
|
||||||
|
LegacyLauncherTheme {
|
||||||
|
AnimatedNavHost(
|
||||||
|
navController = navController,
|
||||||
|
startDestination = "settings",
|
||||||
|
exitTransition = { fadeOut(tween(300, 300)) },
|
||||||
|
enterTransition = { fadeIn(tween(200)) },
|
||||||
|
popEnterTransition = { fadeIn(tween(0)) },
|
||||||
|
popExitTransition = { fadeOut(tween(200)) },
|
||||||
|
) {
|
||||||
|
composable("settings") {
|
||||||
|
MainScreen()
|
||||||
|
}
|
||||||
|
composable("settings/appearance") {
|
||||||
|
AppearanceScreen()
|
||||||
|
}
|
||||||
|
composable("settings/about") {
|
||||||
|
AboutScreen()
|
||||||
|
}
|
||||||
|
composable(
|
||||||
|
"settings/license?library={libraryName}",
|
||||||
|
arguments = listOf(navArgument("libraryName") {
|
||||||
|
nullable = true
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
val libraryName = it.arguments?.getString("libraryName")
|
||||||
|
val library = remember(libraryName) {
|
||||||
|
if (libraryName == null) {
|
||||||
|
AppLicense.get(this@SettingsActivity)
|
||||||
|
} else {
|
||||||
|
OpenSourceLicenses.first { it.name == libraryName }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LicenseScreen(library)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,28 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
import kotlinx.coroutines.flow.first
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class SettingsActivityVM: ViewModel(), KoinComponent {
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
|
val theme = dataStore.data.map { it.appearance.theme }.asLiveData()
|
||||||
|
|
||||||
|
fun getTheme(): Settings.AppearanceSettings.Theme = runBlocking {
|
||||||
|
dataStore.data.map { it.appearance.theme }.first()
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorScheme = dataStore.data.map { it.appearance.colorScheme }.asLiveData()
|
||||||
|
|
||||||
|
fun getColorScheme(): Settings.AppearanceSettings.ColorScheme = runBlocking {
|
||||||
|
dataStore.data.map { it.appearance.colorScheme }.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,12 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings
|
||||||
|
|
||||||
|
enum class SettingsScreen {
|
||||||
|
Main,
|
||||||
|
Appearance,
|
||||||
|
Search,
|
||||||
|
Badges,
|
||||||
|
Weather,
|
||||||
|
Calendar,
|
||||||
|
Services,
|
||||||
|
About,
|
||||||
|
}
|
||||||
@ -0,0 +1,107 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.about
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.Info
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.licenses.OpenSourceLicenses
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.icons.Fdroid
|
||||||
|
import de.mm20.launcher2.ui.icons.GitHub
|
||||||
|
import de.mm20.launcher2.ui.icons.Telegram
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AboutScreen() {
|
||||||
|
val viewModel: AboutScreenVM = viewModel()
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
PreferenceScreen(title = stringResource(R.string.preference_screen_about)) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
var appVersion by remember { mutableStateOf<String?>(null) }
|
||||||
|
LaunchedEffect(null) {
|
||||||
|
appVersion = withContext(Dispatchers.IO) {
|
||||||
|
context.packageManager.getPackageInfo(
|
||||||
|
context.packageName,
|
||||||
|
0
|
||||||
|
).versionName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Preference(
|
||||||
|
title = stringResource(R.string.preference_version),
|
||||||
|
summary = appVersion
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory(title = stringResource(id = R.string.preference_category_license)) {
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Info,
|
||||||
|
title = stringResource(id = R.string.preference_about_license),
|
||||||
|
summary = stringResource(id = R.string.preference_about_license_summary),
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/license")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory(title = stringResource(id = R.string.preference_category_links)) {
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.GitHub,
|
||||||
|
title = "GitHub",
|
||||||
|
summary = "github.com/MM2-0/Kvaesitso",
|
||||||
|
onClick = {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
data = Uri.parse("https://github.com/MM2-0/Kvaesitso")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Telegram,
|
||||||
|
title = stringResource(id = R.string.preference_about_telegram),
|
||||||
|
summary = "t.me/Kvaesitso",
|
||||||
|
onClick = {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
data = Uri.parse("https://t.me/Kvaesitso")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Fdroid,
|
||||||
|
title = stringResource(id = R.string.preference_about_fdroid),
|
||||||
|
summary = "github.com/MM2-0/fdroid",
|
||||||
|
onClick = {
|
||||||
|
context.startActivity(Intent(Intent.ACTION_VIEW).apply {
|
||||||
|
data =
|
||||||
|
Uri.parse("https://raw.githubusercontent.com/MM2-0/fdroid/master/fdroid/repo")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory(title = stringResource(id = R.string.preference_category_licenses)) {
|
||||||
|
for (library in OpenSourceLicenses.sortedBy { it.name.lowercase() }) {
|
||||||
|
Preference(
|
||||||
|
title = library.name,
|
||||||
|
summary = library.description,
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/license?library=${library.name}")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,5 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.about
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
|
||||||
|
class AboutScreenVM : ViewModel()
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.preferences.Settings
|
||||||
|
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
|
||||||
|
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.ListPreference
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AppearanceScreen() {
|
||||||
|
val viewModel: AppearanceScreenVM = viewModel()
|
||||||
|
PreferenceScreen(title = stringResource(id = R.string.preference_screen_appearance)) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
val theme by viewModel.theme.observeAsState()
|
||||||
|
ListPreference(
|
||||||
|
title = stringResource(id = R.string.preference_theme),
|
||||||
|
items = listOf(
|
||||||
|
stringResource(id = R.string.preference_theme_system) to Theme.System,
|
||||||
|
stringResource(id = R.string.preference_theme_light) to Theme.Light,
|
||||||
|
stringResource(id = R.string.preference_theme_dark) to Theme.Dark,
|
||||||
|
),
|
||||||
|
value = theme,
|
||||||
|
onValueChanged = { newValue ->
|
||||||
|
if (newValue == null) return@ListPreference
|
||||||
|
viewModel.setTheme(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
val colorScheme by viewModel.colorScheme.observeAsState()
|
||||||
|
ListPreference(
|
||||||
|
title = stringResource(id = R.string.preference_screen_colors),
|
||||||
|
items = listOf(
|
||||||
|
stringResource(id = R.string.preference_colors_default) to ColorScheme.Default,
|
||||||
|
stringResource(id = R.string.preference_colors_bw) to ColorScheme.BlackAndWhite,
|
||||||
|
),
|
||||||
|
value = colorScheme,
|
||||||
|
onValueChanged = { newValue ->
|
||||||
|
if (newValue == null) return@ListPreference
|
||||||
|
viewModel.setColorScheme(newValue)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,38 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.appearance
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.asLiveData
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||||
|
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
|
||||||
|
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
|
||||||
|
class AppearanceScreenVM : ViewModel(), KoinComponent {
|
||||||
|
private val dataStore: LauncherDataStore by inject()
|
||||||
|
|
||||||
|
val theme = dataStore.data.map { it.appearance.theme }.asLiveData()
|
||||||
|
fun setTheme(theme: Theme) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setAppearance(it.appearance.toBuilder().setTheme(theme))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val colorScheme = dataStore.data.map { it.appearance.colorScheme }.asLiveData()
|
||||||
|
fun setColorScheme(colorScheme: ColorScheme) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
dataStore.updateData {
|
||||||
|
it.toBuilder()
|
||||||
|
.setAppearance(it.appearance.toBuilder().setColorScheme(colorScheme))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.license
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.browser.customtabs.CustomTabColorSchemeParams
|
||||||
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
|
import androidx.compose.animation.rememberSplineBasedDecay
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.ArrowBack
|
||||||
|
import androidx.compose.material.icons.rounded.OpenInBrowser
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.toArgb
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import de.mm20.launcher2.licenses.OpenSourceLibrary
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun LicenseScreen(library: OpenSourceLibrary) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val viewModel: LicenseScreenVM = viewModel()
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
/*PreferenceScreen(title = stringResource(id = R.string.preference_category_license)) {
|
||||||
|
|
||||||
|
}*/
|
||||||
|
|
||||||
|
val scrollBehavior =
|
||||||
|
TopAppBarDefaults.exitUntilCollapsedScrollBehavior(rememberSplineBasedDecay())
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
LargeTopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(library.name)
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
navController?.navigateUp()
|
||||||
|
}) {
|
||||||
|
Icon(imageVector = Icons.Rounded.ArrowBack, contentDescription = "Back")
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
actions = {
|
||||||
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
|
IconButton(onClick = {
|
||||||
|
CustomTabsIntent.Builder()
|
||||||
|
.setDefaultColorSchemeParams(CustomTabColorSchemeParams.Builder()
|
||||||
|
.setToolbarColor(colorScheme.primaryContainer.toArgb())
|
||||||
|
.build())
|
||||||
|
.build()
|
||||||
|
.launchUrl(context, Uri.parse(library.url))
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Rounded.OpenInBrowser,
|
||||||
|
contentDescription = stringResource(
|
||||||
|
R.string.open_webpage
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
) {
|
||||||
|
library.description?.let {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(text = it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(id = library.licenseName),
|
||||||
|
style = MaterialTheme.typography.titleMedium
|
||||||
|
)
|
||||||
|
library.copyrightNote?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
modifier = Modifier.padding(vertical = 4.dp),
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val licenseText by viewModel.getLicenseText(library).observeAsState()
|
||||||
|
licenseText?.let {
|
||||||
|
Text(
|
||||||
|
text = it,
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.license
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import androidx.lifecycle.liveData
|
||||||
|
import de.mm20.launcher2.licenses.OpenSourceLibrary
|
||||||
|
|
||||||
|
class LicenseScreenVM(private val context: Application) : AndroidViewModel(context) {
|
||||||
|
fun getLicenseText(library: OpenSourceLibrary) = liveData<String?> {
|
||||||
|
val text = context.resources.openRawResource(library.licenseText).reader()
|
||||||
|
.readText()
|
||||||
|
emit(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,76 @@
|
|||||||
|
package de.mm20.launcher2.ui.settings.main
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.*
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import de.mm20.launcher2.ui.R
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.Preference
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceCategory
|
||||||
|
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||||
|
import de.mm20.launcher2.ui.icons.NotificationBadge
|
||||||
|
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MainScreen() {
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
PreferenceScreen(
|
||||||
|
title = stringResource(R.string.settings),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
PreferenceCategory {
|
||||||
|
PreferenceCategory {
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Palette,
|
||||||
|
title = stringResource(id = R.string.preference_screen_appearance),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_appearance_summary),
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/appearance")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Search,
|
||||||
|
title = stringResource(id = R.string.preference_screen_search),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_search_summary)
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.NotificationBadge,
|
||||||
|
title = stringResource(id = R.string.preference_screen_badges),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_badges_summary),
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/badges")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.LightMode,
|
||||||
|
title = stringResource(id = R.string.preference_screen_weather),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_weather_summary)
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Today,
|
||||||
|
title = stringResource(id = R.string.preference_screen_calendar),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_calendar_summary)
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.AccountBox,
|
||||||
|
title = stringResource(id = R.string.preference_screen_services),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_services_summary),
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/accounts")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
Preference(
|
||||||
|
icon = Icons.Rounded.Info,
|
||||||
|
title = stringResource(id = R.string.preference_screen_about),
|
||||||
|
summary = stringResource(id = R.string.preference_screen_about_summary),
|
||||||
|
onClick = {
|
||||||
|
navController?.navigate("settings/about")
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -12,4 +12,7 @@
|
|||||||
<item
|
<item
|
||||||
android:id="@+id/menu_item_settings"
|
android:id="@+id/menu_item_settings"
|
||||||
android:title="@string/settings" />
|
android:title="@string/settings" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/menu_item_settings_old"
|
||||||
|
android:title="@string/settings" />
|
||||||
</menu>
|
</menu>
|
||||||
Loading…
x
Reference in New Issue
Block a user