Skip to content

Commit 76528ba

Browse files
committed
refactor: Improve window state management and persistence
This commit refactors the window state management to simplify how size and position are handled and persisted. The previous approach, which used multiple `StateFlows` and complex logic in `main.kt`, has been streamlined. By removing the direct observation and saving of window state from `DataStore` in `main.kt`, we now rely on Compose's `rememberWindowState` for more robust and straightforward behavior. The logic for restoring window size and position has been temporarily disabled to address issues with incorrect state restoration. ### Key Changes: * **`main.kt`:** * Removed the logic that collected window size and position from `AppPreferenceManager`. * The `windowState` is now initialized with a default size and centered position using `rememberWindowState`. * Removed the `LaunchedEffect` that updated the window size based on `DataStore` values. * Removed a temporary `windowState` composable function and its associated logic for constraining window dimensions, which was causing issues. * Simplified the `LaunchedEffect` for saving window size and position by removing conditional checks, ensuring the latest state is always saved. * **`AppPreferenceManager.kt`:** * Commented out the `StateFlow`s for `windowSize`, `windowPosition`, `windowWidth`, `windowHeight`, and their positions. This removes the eager collection of these values, simplifying state management. * **`SplashScreen.kt`:** * Wrapped the `startAnimation` state value with `rememberUpdatedState` to ensure the animation effect always has access to the latest state value, preventing potential stale state issues.
1 parent 2a47b5e commit 76528ba

File tree

3 files changed

+132
-83
lines changed

3 files changed

+132
-83
lines changed

composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/data/datastore/AppPreferenceManager.kt

Lines changed: 62 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.meet.dev.analyzer.data.datastore
22

3-
import androidx.compose.ui.Alignment
43
import androidx.compose.ui.unit.DpSize
54
import androidx.compose.ui.unit.dp
65
import androidx.compose.ui.window.WindowPosition
@@ -10,18 +9,14 @@ import androidx.datastore.preferences.core.booleanPreferencesKey
109
import androidx.datastore.preferences.core.edit
1110
import androidx.datastore.preferences.core.floatPreferencesKey
1211
import com.jthemedetecor.OsThemeDetector
13-
import com.meet.dev.analyzer.core.utility.AppLogger
1412
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager.PreferencesKey.DARK_MODE_KEY
1513
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager.PreferencesKey.ONBOARDING_DONE_KEY
1614
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager.PreferencesKey.WINDOW_HEIGHT_KEY
1715
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager.PreferencesKey.WINDOW_POSITION_X_KEY
1816
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager.PreferencesKey.WINDOW_POSITION_Y_KEY
1917
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager.PreferencesKey.WINDOW_WIDTH_KEY
20-
import kotlinx.coroutines.CoroutineScope
2118
import kotlinx.coroutines.Dispatchers
22-
import kotlinx.coroutines.flow.SharingStarted
2319
import kotlinx.coroutines.flow.map
24-
import kotlinx.coroutines.flow.stateIn
2520
import kotlinx.coroutines.withContext
2621

2722
class AppPreferenceManager(private val dataStore: DataStore<Preferences>) {
@@ -45,46 +40,68 @@ class AppPreferenceManager(private val dataStore: DataStore<Preferences>) {
4540
val isOnboardingDone = dataStore.data.map { prefs ->
4641
prefs[ONBOARDING_DONE_KEY] ?: false
4742
}
48-
val windowSize = dataStore.data.map { prefs ->
49-
val dpSize = DpSize(
50-
width = prefs[WINDOW_WIDTH_KEY]?.dp ?: defaultWindowSize.width,
51-
height = prefs[WINDOW_HEIGHT_KEY]?.dp ?: defaultWindowSize.height
52-
)
53-
AppLogger.d("windowSize") {
54-
"dpSize: $dpSize"
55-
}
56-
dpSize
57-
}.stateIn(
58-
scope = CoroutineScope(Dispatchers.Main),
59-
started = SharingStarted.Eagerly,
60-
initialValue = defaultWindowSize
61-
)
62-
val windowPosition = dataStore.data.map { prefs ->
63-
val x = prefs[WINDOW_POSITION_X_KEY]?.dp
64-
val y = prefs[WINDOW_POSITION_Y_KEY]?.dp
65-
AppLogger.d("windowPosition") {
66-
"x: $x, y: $y"
67-
}
68-
69-
val position = if (x != null && y != null) {
70-
WindowPosition(x, y)
71-
} else {
72-
WindowPosition.PlatformDefault
73-
}
74-
position
75-
}.stateIn(
76-
scope = CoroutineScope(Dispatchers.Main),
77-
started = SharingStarted.Eagerly,
78-
initialValue = WindowPosition(Alignment.Center)
79-
)
80-
81-
val windowWidth = dataStore.data.map { prefs ->
82-
prefs[WINDOW_WIDTH_KEY]?.dp ?: defaultWindowSize.width
83-
}
84-
85-
val windowHeight = dataStore.data.map { prefs ->
86-
prefs[WINDOW_HEIGHT_KEY]?.dp ?: defaultWindowSize.height
87-
}
43+
// val windowSize = dataStore.data.map { prefs ->
44+
// val dpSize = DpSize(
45+
// width = prefs[WINDOW_WIDTH_KEY]?.dp ?: defaultWindowSize.width,
46+
// height = prefs[WINDOW_HEIGHT_KEY]?.dp ?: defaultWindowSize.height
47+
// )
48+
// AppLogger.d("windowSize") {
49+
// "dpSize: $dpSize"
50+
// }
51+
// dpSize
52+
// }.stateIn(
53+
// scope = CoroutineScope(Dispatchers.Main),
54+
// started = SharingStarted.Eagerly,
55+
// initialValue = defaultWindowSize
56+
// )
57+
// val windowPosition = dataStore.data.map { prefs ->
58+
// val x = prefs[WINDOW_POSITION_X_KEY]?.dp
59+
// val y = prefs[WINDOW_POSITION_Y_KEY]?.dp
60+
// AppLogger.d("windowPosition") {
61+
// "x: $x, y: $y"
62+
// }
63+
//
64+
// val position = if (x != null && y != null) {
65+
// WindowPosition(x, y)
66+
// } else {
67+
// WindowPosition.PlatformDefault
68+
// }
69+
// position
70+
// }.stateIn(
71+
// scope = CoroutineScope(Dispatchers.Main),
72+
// started = SharingStarted.Eagerly,
73+
// initialValue = WindowPosition(Alignment.Center)
74+
// )
75+
//
76+
// val windowWidth = dataStore.data.map { prefs ->
77+
// prefs[WINDOW_WIDTH_KEY]?.dp ?: defaultWindowSize.width
78+
// }.stateIn(
79+
// scope = CoroutineScope(Dispatchers.Main),
80+
// started = SharingStarted.Eagerly,
81+
// initialValue = defaultWindowSize.width
82+
// )
83+
// val windowHeight = dataStore.data.map { prefs ->
84+
// prefs[WINDOW_HEIGHT_KEY]?.dp ?: defaultWindowSize.height
85+
// }.stateIn(
86+
// scope = CoroutineScope(Dispatchers.Main),
87+
// started = SharingStarted.Eagerly,
88+
// initialValue = defaultWindowSize.height
89+
// )
90+
//
91+
// val windowPositionX = dataStore.data.map { prefs ->
92+
// prefs[WINDOW_POSITION_X_KEY]?.dp
93+
// }.stateIn(
94+
// scope = CoroutineScope(Dispatchers.Main),
95+
// started = SharingStarted.Eagerly,
96+
// initialValue = null
97+
// )
98+
// val windowPositionY = dataStore.data.map { prefs ->
99+
// prefs[WINDOW_POSITION_Y_KEY]?.dp
100+
// }.stateIn(
101+
// scope = CoroutineScope(Dispatchers.Main),
102+
// started = SharingStarted.Eagerly,
103+
// initialValue =null
104+
// )
88105

89106

90107
suspend fun saveTheme(isDark: Boolean) =
@@ -116,15 +133,6 @@ class AppPreferenceManager(private val dataStore: DataStore<Preferences>) {
116133
preferences[WINDOW_POSITION_Y_KEY] = position.y.value
117134
}
118135
}
119-
120-
// fun saveWindowWidthHeight(width: Float, height: Float) {
121-
// CoroutineScope(Dispatchers.Main).launch {
122-
// dataStore.edit { preferences ->
123-
// preferences[WINDOW_WIDTH_KEY] = width
124-
// preferences[WINDOW_HEIGHT_KEY] = height
125-
// }
126-
// }
127-
// }
128136
}
129137

130138
val defaultWindowSize = DpSize(width = 1024.dp, height = 768.dp)
Lines changed: 68 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package com.meet.dev.analyzer
22

3+
import androidx.compose.runtime.Composable
34
import androidx.compose.runtime.LaunchedEffect
45
import androidx.compose.runtime.collectAsState
56
import androidx.compose.runtime.getValue
67
import androidx.compose.runtime.snapshotFlow
78
import androidx.compose.ui.Alignment
9+
import androidx.compose.ui.unit.Dp
10+
import androidx.compose.ui.unit.DpSize
11+
import androidx.compose.ui.unit.dp
812
import androidx.compose.ui.window.Window
913
import androidx.compose.ui.window.WindowPosition
14+
import androidx.compose.ui.window.WindowState
1015
import androidx.compose.ui.window.application
1116
import androidx.compose.ui.window.rememberWindowState
1217
import com.meet.dev.analyzer.core.utility.AppLogger
13-
import com.meet.dev.analyzer.data.datastore.AppPreferenceManager
14-
import com.meet.dev.analyzer.data.datastore.defaultWindowPosition
1518
import com.meet.dev.analyzer.data.datastore.defaultWindowSize
1619
import com.meet.dev.analyzer.di.initKoin
1720
import com.meet.dev.analyzer.presentation.navigation.AppNavigation
@@ -20,24 +23,30 @@ import com.meet.dev.analyzer.presentation.screen.app.AppViewModel
2023
import com.meet.dev.analyzer.presentation.theme.DevAnalyzerTheme
2124
import io.github.vinceglb.filekit.FileKit
2225
import org.jetbrains.compose.resources.painterResource
23-
import org.koin.compose.koinInject
2426
import org.koin.compose.viewmodel.koinViewModel
2527
import java.awt.Dimension
28+
import java.awt.Toolkit
2629

2730
fun main() {
2831
initKoin()
2932
FileKit.init(appId = "DevAnalyzer")
3033
System.setProperty("apple.awt.application.appearance", "system")
3134
application {
32-
val appPreferenceManager = koinInject<AppPreferenceManager>()
33-
val preferredWindowSize by appPreferenceManager.windowSize.collectAsState(
34-
initial = defaultWindowSize
35-
)
36-
val preferredWindowPosition by appPreferenceManager.windowPosition.collectAsState(
37-
initial = defaultWindowPosition
38-
)
35+
// val appPreferenceManager = koinInject<AppPreferenceManager>()
36+
// val preferredWindowSize by appPreferenceManager.windowSize.collectAsState()
37+
// val preferredWindowPosition by appPreferenceManager.windowPosition.collectAsState()
38+
// val windowWidth by appPreferenceManager.windowWidth.collectAsState()
39+
// val windowHeight by appPreferenceManager.windowHeight.collectAsState()
40+
// val windowPositionX by appPreferenceManager.windowPositionX.collectAsState()
41+
// val windowPositionY by appPreferenceManager.windowPositionY.collectAsState()
42+
// val windowState = windowState(
43+
// savedWidthDp = windowWidth,
44+
// savedHeightDp = windowHeight,
45+
// savedPositionX = windowPositionX,
46+
// savedPositionY = windowPositionY
47+
// )
3948
val windowState = rememberWindowState(
40-
size = preferredWindowSize,
49+
size = defaultWindowSize,
4150
position = WindowPosition(Alignment.Center), /// preferredWindowPosition it not work proper
4251
)
4352
Window(
@@ -57,23 +66,16 @@ fun main() {
5766
appViewModel.handleIntent(AppUiIntent.ChangeTheme(appUiState.isDarkMode))
5867
}
5968
)
60-
LaunchedEffect(preferredWindowSize) {
61-
windowState.size = preferredWindowSize
62-
}
63-
// LaunchedEffect(preferredWindowPosition) {
64-
// windowState.position = preferredWindowPosition
65-
// }
69+
6670
LaunchedEffect(windowState) {
6771
snapshotFlow { windowState.position }
6872
.collect { position ->
6973
AppLogger.d("window_position") {
7074
"window position changed to $position"
7175
}
72-
if (preferredWindowPosition != position) {
73-
appViewModel.saveWindowPosition(
74-
position = position
75-
)
76-
}
76+
appViewModel.saveWindowPosition(
77+
position = position
78+
)
7779
}
7880
}
7981
LaunchedEffect(windowState) {
@@ -82,15 +84,53 @@ fun main() {
8284
AppLogger.d("window_size") {
8385
"window size changed to $size"
8486
}
85-
if (preferredWindowSize != size) {
86-
appViewModel.saveWindowWidthHeight(
87-
width = windowState.size.width.value,
88-
height = windowState.size.height.value,
89-
)
90-
}
87+
appViewModel.saveWindowWidthHeight(
88+
width = windowState.size.width.value,
89+
height = windowState.size.height.value,
90+
)
9191
}
9292
}
9393
}
9494
}
9595
}
96+
}
97+
98+
@Composable
99+
private fun windowState(
100+
savedWidthDp: Dp,
101+
savedHeightDp: Dp,
102+
savedPositionX: Dp?,
103+
savedPositionY: Dp?
104+
): WindowState {
105+
106+
val toolkit = Toolkit.getDefaultToolkit()
107+
val screenSize = toolkit.screenSize
108+
val maxWidth = screenSize.width.dp
109+
val maxHeight = screenSize.height.dp
110+
111+
val width = savedWidthDp.coerceAtMost(maxWidth)
112+
val height = savedHeightDp.coerceAtMost(maxHeight)
113+
114+
val xPos = savedPositionX
115+
?.coerceIn(0.dp, (maxWidth - width).coerceAtLeast(minimumValue = 0.dp))
116+
?: Dp.Unspecified
117+
118+
val yPos = savedPositionY
119+
?.coerceIn(0.dp, (maxHeight - height).coerceAtLeast(minimumValue = 0.dp))
120+
?: Dp.Unspecified
121+
122+
val position = if (xPos != Dp.Unspecified && yPos != Dp.Unspecified) {
123+
WindowPosition.Absolute(
124+
x = xPos,
125+
y = yPos,
126+
)
127+
} else {
128+
WindowPosition.PlatformDefault
129+
}
130+
131+
val windowState = rememberWindowState(
132+
size = DpSize(width, height),
133+
position = position,
134+
)
135+
return windowState
96136
}

composeApp/src/jvmMain/kotlin/com/meet/dev/analyzer/presentation/screen/splash/SplashScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import androidx.compose.material3.Text
2020
import androidx.compose.runtime.Composable
2121
import androidx.compose.runtime.collectAsState
2222
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.rememberUpdatedState
2324
import androidx.compose.ui.Alignment
2425
import androidx.compose.ui.Modifier
2526
import androidx.compose.ui.draw.alpha
@@ -41,7 +42,7 @@ fun SplashScreen(
4142
) {
4243
val splashViewModel = koinViewModel<SplashViewModel>()
4344
val uiState by splashViewModel.uiState.collectAsState()
44-
val startAnimation = uiState.startAnimation
45+
val startAnimation by rememberUpdatedState(uiState.startAnimation)
4546

4647
splashViewModel.effect.ObserveAsEvents { splashEffect ->
4748
when (splashEffect) {

0 commit comments

Comments
 (0)