mirror of
https://github.com/openflocon/Flocon.git
synced 2026-05-01 00:09:32 +00:00
feat: First appearance version (#281)
Co-authored-by: TEYSSANDIER Raphael <rteyssandier@sephora.fr>
This commit is contained in:
parent
c2887551d2
commit
5c8c4e0d95
15 changed files with 272 additions and 59 deletions
|
|
@ -8,12 +8,15 @@ import androidx.compose.foundation.layout.Box
|
|||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.safeContentPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.flocon.data.remote.dataRemoteModule
|
||||
import io.github.openflocon.data.core.dataCoreModule
|
||||
import io.github.openflocon.data.local.dataLocalModule
|
||||
import io.github.openflocon.domain.adb.repository.AdbRepository
|
||||
import io.github.openflocon.domain.domainModule
|
||||
import io.github.openflocon.domain.settings.usecase.ObserveFontSizeMultiplierUseCase
|
||||
import io.github.openflocon.flocondesktop.adb.AdbRepositoryImpl
|
||||
import io.github.openflocon.flocondesktop.app.AppViewModel
|
||||
import io.github.openflocon.flocondesktop.app.di.appModule
|
||||
|
|
@ -26,6 +29,7 @@ import io.github.openflocon.flocondesktop.main.ui.MainScreen
|
|||
import io.github.openflocon.library.designsystem.FloconTheme
|
||||
import io.github.openflocon.library.designsystem.components.FloconSurface
|
||||
import org.koin.compose.KoinApplication
|
||||
import org.koin.compose.koinInject
|
||||
import org.koin.compose.viewmodel.koinViewModel
|
||||
import org.koin.core.module.dsl.singleOf
|
||||
import org.koin.dsl.bind
|
||||
|
|
@ -54,7 +58,12 @@ fun App() {
|
|||
)
|
||||
},
|
||||
) {
|
||||
FloconTheme {
|
||||
val fontSizeMultiplier by koinInject<ObserveFontSizeMultiplierUseCase>()()
|
||||
.collectAsStateWithLifecycle()
|
||||
|
||||
FloconTheme(
|
||||
fontSizeMultiplier = fontSizeMultiplier
|
||||
) {
|
||||
val appViewModel: AppViewModel = koinViewModel()
|
||||
|
||||
FloconSurface(
|
||||
|
|
|
|||
|
|
@ -3,15 +3,23 @@ package io.github.openflocon.flocondesktop.core.data.settings
|
|||
import io.github.openflocon.domain.settings.repository.SettingsRepository
|
||||
import io.github.openflocon.flocondesktop.core.data.settings.datasource.local.SettingsDataSource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
class SettingsRepositoryImpl(
|
||||
private val localSettingsDataSource: SettingsDataSource,
|
||||
) : SettingsRepository {
|
||||
|
||||
override val adbPath: Flow<String?> = localSettingsDataSource.adbPath
|
||||
override val fontSizeMultiplier: StateFlow<Float> = localSettingsDataSource.fontSizeMultiplier
|
||||
|
||||
override fun getAdbPath(): String? = localSettingsDataSource.getAdbPath()
|
||||
|
||||
override suspend fun setAdbPath(path: String) {
|
||||
localSettingsDataSource.setAdbPath(path)
|
||||
}
|
||||
|
||||
override val adbPath: Flow<String?> = localSettingsDataSource.adbPath
|
||||
override suspend fun setFontSizeMultiplier(value: Float) {
|
||||
localSettingsDataSource.setFontSizeMultiplier(value)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
package io.github.openflocon.flocondesktop.core.data.settings.datasource.local
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
interface SettingsDataSource {
|
||||
fun getAdbPath(): String?
|
||||
|
||||
suspend fun setAdbPath(path: String)
|
||||
suspend fun setFontSizeMultiplier(value: Float)
|
||||
|
||||
val adbPath: Flow<String?>
|
||||
val fontSizeMultiplier: StateFlow<Float>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,25 +5,44 @@ package io.github.openflocon.flocondesktop.core.data.settings.datasource.local
|
|||
import com.russhwolf.settings.ExperimentalSettingsApi
|
||||
import com.russhwolf.settings.ObservableSettings
|
||||
import com.russhwolf.settings.coroutines.toFlowSettings
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
|
||||
// Expect class pour obtenir les Settings de manière multiplateforme
|
||||
expect fun createSettings(): ObservableSettings
|
||||
|
||||
class SettingsDataSourcePrefs : SettingsDataSource {
|
||||
class SettingsDataSourcePrefs(
|
||||
applicationScope: CoroutineScope
|
||||
) : SettingsDataSource {
|
||||
private val settings = createSettings()
|
||||
|
||||
private val flowSettings = settings.toFlowSettings()
|
||||
|
||||
override val adbPath: Flow<String?> = flowSettings.getStringOrNullFlow(ADB_PATH)
|
||||
override val fontSizeMultiplier: StateFlow<Float> = settings.toFlowSettings()
|
||||
.getFloatOrNullFlow(FONT_SIZE_MULTIPLIER)
|
||||
.filterNotNull()
|
||||
.stateIn(
|
||||
scope = applicationScope,
|
||||
started = SharingStarted.Lazily,
|
||||
initialValue = 1f
|
||||
)
|
||||
|
||||
override fun getAdbPath(): String? = settings.getStringOrNull(ADB_PATH)
|
||||
|
||||
override suspend fun setAdbPath(path: String) {
|
||||
settings.putString(ADB_PATH, path)
|
||||
}
|
||||
|
||||
override val adbPath: Flow<String?> = flowSettings.getStringOrNullFlow(ADB_PATH)
|
||||
override suspend fun setFontSizeMultiplier(value: Float) {
|
||||
settings.putFloat(FONT_SIZE_MULTIPLIER, value)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val ADB_PATH = "adb_path"
|
||||
private const val FONT_SIZE_MULTIPLIER = "font_size_multiplier"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package io.github.openflocon.flocondesktop.main.ui.settings
|
||||
|
||||
sealed interface SettingsAction {
|
||||
|
||||
data class FontSizeMultiplierChange(val value: Float) : SettingsAction
|
||||
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ package io.github.openflocon.flocondesktop.main.ui.settings
|
|||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
|
|
@ -22,11 +21,14 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import co.touchlab.kermit.Logger
|
||||
import io.github.openflocon.library.designsystem.FloconTheme
|
||||
import io.github.openflocon.library.designsystem.components.FloconButton
|
||||
import io.github.openflocon.library.designsystem.components.FloconFeature
|
||||
import io.github.openflocon.library.designsystem.components.FloconIcon
|
||||
import io.github.openflocon.library.designsystem.components.FloconSection
|
||||
import io.github.openflocon.library.designsystem.components.FloconSlider
|
||||
import io.github.openflocon.library.designsystem.components.FloconTextFieldWithoutM3
|
||||
import io.github.openflocon.library.designsystem.components.defaultPlaceHolder
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -39,79 +41,106 @@ fun SettingsScreen(
|
|||
val viewModel: SettingsViewModel = koinViewModel()
|
||||
val needsAdbSetup by viewModel.needsAdbSetup.collectAsState()
|
||||
val adbPathText by viewModel.adbPathInput.collectAsState()
|
||||
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
Box(modifier = modifier) {
|
||||
SettingsScreen(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
adbPathText = adbPathText,
|
||||
onAdbPathChanged = viewModel::onAdbPathChanged,
|
||||
saveAdbPath = viewModel::saveAdbPath,
|
||||
testAdbPath = viewModel::testAdbPath,
|
||||
needsAdbSetup = needsAdbSetup,
|
||||
)
|
||||
}
|
||||
SettingsScreen(
|
||||
uiState = uiState,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
adbPathText = adbPathText,
|
||||
onAdbPathChanged = viewModel::onAdbPathChanged,
|
||||
saveAdbPath = viewModel::saveAdbPath,
|
||||
testAdbPath = viewModel::testAdbPath,
|
||||
onAction = viewModel::onAction,
|
||||
needsAdbSetup = needsAdbSetup,
|
||||
)
|
||||
}
|
||||
|
||||
// Main composable for the screen, incorporating the filter bar
|
||||
@Composable
|
||||
private fun SettingsScreen(
|
||||
uiState: SettingsUiState,
|
||||
adbPathText: String,
|
||||
onAdbPathChanged: (String) -> Unit,
|
||||
saveAdbPath: () -> Unit,
|
||||
testAdbPath: () -> Unit,
|
||||
needsAdbSetup: Boolean,
|
||||
onAction: (SettingsAction) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
FloconFeature(
|
||||
modifier = modifier.fillMaxSize()
|
||||
) {
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.clip(FloconTheme.shapes.medium)
|
||||
.background(FloconTheme.colorPalette.primary)
|
||||
.padding(all = 8.dp)
|
||||
FloconSection(
|
||||
title = "Adb Path",
|
||||
initialValue = true
|
||||
) {
|
||||
if (needsAdbSetup) {
|
||||
Text(
|
||||
text = "Please setup ADB first, this field is mandatory",
|
||||
color = FloconTheme.colorPalette.onError,
|
||||
style = FloconTheme.typography.bodySmall,
|
||||
)
|
||||
} else {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
FloconIcon(
|
||||
imageVector = Icons.Outlined.Check,
|
||||
tint = FloconTheme.colorPalette.onAccent,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.clip(FloconTheme.shapes.medium)
|
||||
.background(FloconTheme.colorPalette.primary)
|
||||
.padding(all = 8.dp)
|
||||
) {
|
||||
if (needsAdbSetup) {
|
||||
Text(
|
||||
text = "ADB configuraton is valid",
|
||||
color = FloconTheme.colorPalette.onAccent,
|
||||
style = FloconTheme.typography.bodySmall
|
||||
text = "Please setup ADB first, this field is mandatory",
|
||||
color = FloconTheme.colorPalette.onError,
|
||||
style = FloconTheme.typography.bodySmall,
|
||||
)
|
||||
} else {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
FloconIcon(
|
||||
imageVector = Icons.Outlined.Check,
|
||||
tint = FloconTheme.colorPalette.onAccent,
|
||||
modifier = Modifier.size(16.dp)
|
||||
)
|
||||
Text(
|
||||
text = "ADB configuraton is valid",
|
||||
color = FloconTheme.colorPalette.onAccent,
|
||||
style = FloconTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
FloconTextFieldWithoutM3(
|
||||
value = adbPathText,
|
||||
onValueChange = onAdbPathChanged,
|
||||
placeholder = defaultPlaceHolder("Eg: /Users/youruser/Library/Android/sdk/platform-tools/adb"),
|
||||
containerColor = FloconTheme.colorPalette.secondary,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
SettingsButton(
|
||||
text = "Save",
|
||||
onClick = saveAdbPath
|
||||
)
|
||||
SettingsButton(
|
||||
onClick = testAdbPath,
|
||||
text = "Test",
|
||||
)
|
||||
}
|
||||
}
|
||||
FloconTextFieldWithoutM3(
|
||||
value = adbPathText,
|
||||
onValueChange = onAdbPathChanged,
|
||||
placeholder = defaultPlaceHolder("Eg: /Users/youruser/Library/Android/sdk/platform-tools/adb"),
|
||||
containerColor = FloconTheme.colorPalette.secondary,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
}
|
||||
FloconSection(
|
||||
title = "Appearance",
|
||||
initialValue = true
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.clip(FloconTheme.shapes.medium)
|
||||
.background(FloconTheme.colorPalette.primary)
|
||||
.padding(all = 8.dp)
|
||||
) {
|
||||
SettingsButton(
|
||||
text = "Save",
|
||||
onClick = saveAdbPath
|
||||
)
|
||||
SettingsButton(
|
||||
onClick = testAdbPath,
|
||||
text = "Test",
|
||||
FloconSlider(
|
||||
value = uiState.fontSizeMultiplier,
|
||||
onValueChange = { onAction(SettingsAction.FontSizeMultiplierChange(it)) },
|
||||
valueRange = 0.2f..5f,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -142,6 +171,7 @@ private fun SettingsScreenPreview() {
|
|||
FloconTheme {
|
||||
var adbPath by remember { mutableStateOf("/usr/local/bin/adb") }
|
||||
SettingsScreen(
|
||||
uiState = previewSettingsUiState(),
|
||||
adbPathText = adbPath,
|
||||
onAdbPathChanged = { adbPath = it },
|
||||
saveAdbPath = {
|
||||
|
|
@ -151,6 +181,7 @@ private fun SettingsScreenPreview() {
|
|||
Logger.d { "Test ADB FilePathDomainModel: $adbPath" }
|
||||
},
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onAction = {},
|
||||
needsAdbSetup = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -162,11 +193,13 @@ private fun SettingsScreenPreview_needsAdbSetup() {
|
|||
FloconTheme {
|
||||
var adbPath by remember { mutableStateOf("/usr/local/bin/adb") }
|
||||
SettingsScreen(
|
||||
uiState = previewSettingsUiState(),
|
||||
adbPathText = adbPath,
|
||||
onAdbPathChanged = { adbPath = it },
|
||||
saveAdbPath = { Logger.d { "Save ADB FilePathDomainModel: $adbPath" } },
|
||||
testAdbPath = { Logger.d { "Test ADB FilePathDomainModel: $adbPath" } },
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onAction = {},
|
||||
needsAdbSetup = true,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
package io.github.openflocon.flocondesktop.main.ui.settings
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
|
||||
@Immutable
|
||||
data class SettingsUiState(
|
||||
val fontSizeMultiplier: Float
|
||||
)
|
||||
|
||||
fun previewSettingsUiState() = SettingsUiState(
|
||||
fontSizeMultiplier = 1f
|
||||
)
|
||||
|
|
@ -5,15 +5,22 @@ import androidx.lifecycle.viewModelScope
|
|||
import io.github.openflocon.domain.common.DispatcherProvider
|
||||
import io.github.openflocon.domain.feedback.FeedbackDisplayer
|
||||
import io.github.openflocon.domain.settings.repository.SettingsRepository
|
||||
import io.github.openflocon.domain.settings.usecase.ObserveFontSizeMultiplierUseCase
|
||||
import io.github.openflocon.domain.settings.usecase.SetFontSizeMultiplierUseCase
|
||||
import io.github.openflocon.domain.settings.usecase.TestAdbUseCase
|
||||
import io.github.openflocon.flocondesktop.app.InitialSetupStateHolder
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SettingsViewModel(
|
||||
private val settingsRepository: SettingsRepository,
|
||||
private val testAdbUseCase: TestAdbUseCase,
|
||||
fontSizeMultiplierUseCase: ObserveFontSizeMultiplierUseCase,
|
||||
private val setFontSizeMultiplierUseCase: SetFontSizeMultiplierUseCase,
|
||||
private val feedbackDisplayer: FeedbackDisplayer,
|
||||
private val initialSetupStateHolder: InitialSetupStateHolder,
|
||||
private val dispatcherProvider: DispatcherProvider,
|
||||
|
|
@ -23,6 +30,15 @@ class SettingsViewModel(
|
|||
val adbPathInput = _adbPathInput.asStateFlow()
|
||||
val needsAdbSetup = initialSetupStateHolder.needsAdbSetup
|
||||
|
||||
val uiState = fontSizeMultiplierUseCase().map {
|
||||
SettingsUiState(fontSizeMultiplier = it)
|
||||
}
|
||||
.stateIn(
|
||||
viewModelScope,
|
||||
started = SharingStarted.WhileSubscribed(5_000),
|
||||
initialValue = SettingsUiState(fontSizeMultiplier = 1f)
|
||||
)
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
// Utiliser GlobalScope ici pour la simplicité de l'exemple, mais préférez un scope dédié
|
||||
|
|
@ -32,7 +48,20 @@ class SettingsViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun onAction(action: SettingsAction) {
|
||||
when (action) {
|
||||
is SettingsAction.FontSizeMultiplierChange -> onFontSizeMultiplierChange(action)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onFontSizeMultiplierChange(action: SettingsAction.FontSizeMultiplierChange) {
|
||||
viewModelScope.launch {
|
||||
setFontSizeMultiplierUseCase(action.value)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAdbPathChanged(newPath: String) {
|
||||
|
||||
_adbPathInput.value = newPath
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue