@@ -8,20 +8,20 @@ import androidx.compose.animation.core.tween
88import androidx.compose.foundation.border
99import androidx.compose.foundation.layout.Arrangement
1010import androidx.compose.foundation.layout.Box
11- import androidx.compose.foundation.layout.BoxScope
1211import androidx.compose.foundation.layout.Column
1312import androidx.compose.foundation.layout.Row
1413import androidx.compose.foundation.layout.fillMaxHeight
1514import androidx.compose.foundation.layout.padding
1615import androidx.compose.foundation.layout.width
1716import androidx.compose.material.icons.Icons
1817import androidx.compose.material.icons.outlined.Close
19- import androidx.compose.material.icons.outlined.Pin
2018import androidx.compose.runtime.Composable
2119import androidx.compose.runtime.LaunchedEffect
20+ import androidx.compose.runtime.Stable
2221import androidx.compose.runtime.getValue
2322import androidx.compose.runtime.mutableStateOf
2423import androidx.compose.runtime.remember
24+ import androidx.compose.runtime.rememberCoroutineScope
2525import androidx.compose.runtime.setValue
2626import androidx.compose.ui.Modifier
2727import androidx.compose.ui.graphics.graphicsLayer
@@ -30,106 +30,152 @@ import androidx.compose.ui.unit.dp
3030import io.github.openflocon.library.designsystem.FloconTheme
3131import io.github.openflocon.library.designsystem.components.FloconIcon
3232import io.github.openflocon.library.designsystem.components.FloconIconTonalButton
33+ import io.github.openflocon.library.designsystem.components.escape.EscapeHandler
34+ import kotlinx.coroutines.launch
3335
3436private const val AnimDuration = 500
35- private val PanelWidth = 500 .dp
37+ val PanelWidth = 500 .dp
38+
39+ @Stable
40+ class FloconPanelState internal constructor(initialValue : Boolean ) {
41+
42+ internal var expanded by mutableStateOf(initialValue)
43+
44+ val translationX = AnimationState (typeConverter = Dp .VectorConverter , PanelWidth )
45+
46+ suspend fun show () {
47+ expanded = true
48+ translationX.animateTo(0 .dp, animationSpec = tween(AnimDuration , easing = EaseOutExpo ))
49+ }
50+
51+ suspend fun hide () {
52+ translationX.animateTo(PanelWidth , animationSpec = tween(AnimDuration , easing = EaseOutExpo ))
53+ expanded = false
54+ }
55+
56+ }
57+
58+ @Composable
59+ fun rememberFloconPanelState (initialValue : Boolean = false): FloconPanelState {
60+ return remember { FloconPanelState (initialValue) }
61+ }
62+
63+
64+ interface FloconPanelScope {
65+ val state: FloconPanelState
66+
67+ fun Modifier.animatePanelAction (): Modifier
68+
69+ }
70+
71+ private class FloconPanelScopeImpl (
72+ override val state : FloconPanelState
73+ ) : FloconPanelScope {
74+
75+ @Stable
76+ override fun Modifier.animatePanelAction (): Modifier = graphicsLayer {
77+ this .alpha = 1f - state.translationX.value.div(PanelWidth )
78+ }
79+
80+ }
81+
3682
37- // TODO Rework
3883@Composable
3984fun FloconPanel (
40- expanded : Boolean ,
41- onClose : (( ) -> Unit ) ? = null ,
42- onPin : (( ) -> Unit ) ? = null ,
43- content : @Composable BoxScope .() -> Unit
85+ state : FloconPanelState ,
86+ onDismissRequest : () -> Unit ,
87+ actions : @Composable FloconPanelScope .( ) -> Unit = {} ,
88+ content : @Composable FloconPanelScope .() -> Unit
4489) {
45- var innerExpanded by remember { mutableStateOf(expanded ) }
46- val translationX = remember { AnimationState (typeConverter = Dp . VectorConverter , PanelWidth ) }
90+ val scope = remember(state) { FloconPanelScopeImpl (state ) }
91+ val coroutineScope = rememberCoroutineScope()
4792
48- suspend fun hide () {
49- translationX.animateTo(PanelWidth , animationSpec = tween(AnimDuration , easing = EaseOutExpo ))
50- innerExpanded = false
93+ LaunchedEffect (Unit ) {
94+ if (state.expanded) {
95+ state.show()
96+ }
5197 }
5298
53- LaunchedEffect (expanded) {
54- if (expanded) {
55- innerExpanded = true
56- translationX.animateTo(0 .dp, animationSpec = tween(AnimDuration , easing = EaseOutExpo ))
57- } else {
58- hide()
99+ LaunchedEffect (state.expanded) {
100+ if (! state.expanded) {
101+ onDismissRequest()
59102 }
60103 }
61104
105+ EscapeHandler {
106+ coroutineScope.launch {
107+ state.hide()
108+ onDismissRequest()
109+ }
110+ true
111+ }
112+
62113 Row (
63114 modifier = Modifier
64115 .fillMaxHeight()
65116 ) {
66- if (onClose != null ) {
67- Column (
68- verticalArrangement = Arrangement .spacedBy(8 .dp),
69- modifier = Modifier .padding(8 .dp)
70- ) {
71- FloconIconTonalButton (
72- onClick = onClose,
73- modifier = Modifier
74- .graphicsLayer {
75- this .alpha = 1f - translationX.value.div(PanelWidth )
76- }
77- ) {
78- FloconIcon (
79- Icons .Outlined .Close
80- )
81- }
82- if (onPin != null ) {
83- FloconIconTonalButton (
84- onClick = onPin,
85- modifier = Modifier
86- .graphicsLayer {
87- this .alpha = 1f - translationX.value.div(PanelWidth )
88- }
89- ) {
90- FloconIcon (
91- Icons .Outlined .Pin
92- )
93- }
94- }
95- }
96- }
117+ Column (
118+ verticalArrangement = Arrangement .spacedBy(8 .dp),
119+ modifier = Modifier .padding(8 .dp),
120+ content = { scope.actions() }
121+ )
97122 Box (
98123 modifier = Modifier
99124 .width(PanelWidth )
100125 .fillMaxHeight()
101126 .graphicsLayer {
102- this .translationX = translationX.value.toPx()
127+ this .translationX = state. translationX.value.toPx()
103128 }
104- .border(width = 1 .dp, color = FloconTheme .colorPalette.surface),
105- content = content
106- )
129+ .border(width = 1 .dp, color = FloconTheme .colorPalette.surface)
130+ ) {
131+ scope.content()
132+ }
107133 }
108134}
109135
110- // val floconPanelController = LocalFloconPanelController.current
136+ // TODO Rework
137+ @Composable
138+ fun FloconPanel (
139+ expanded : Boolean ,
140+ onDismissRequest : () -> Unit ,
141+ actions : @Composable FloconPanelScope .() -> Unit = {},
142+ content : @Composable FloconPanelScope .() -> Unit
143+ ) {
144+ var innerExpand by remember { mutableStateOf(expanded) }
145+ val state = rememberFloconPanelState(expanded)
146+ val scope = rememberCoroutineScope()
111147
112- // if (innerExpanded) {
113- // DisposableEffect(Unit) {
114- // floconPanelController.display {
115- // if (onClose != null) {
116- // EscapeHandler {
117- // onClose()
118- // true
119- // }
120- // }
121- //
148+ LaunchedEffect (expanded) {
149+ if (expanded) {
150+ innerExpand = true
151+ state.show()
152+ } else {
153+ state.hide()
154+ innerExpand = false
155+ }
156+ }
122157
123- // onDispose { floconPanelController.hide() }
124- // }
125- // }
126- // }
158+ if (innerExpand) {
159+ FloconPanel (
160+ state = state,
161+ onDismissRequest = {
162+ scope.launch {
163+ state.hide()
164+ innerExpand = false
165+ onDismissRequest()
166+ }
167+ },
168+ actions = actions,
169+ content = content
170+ )
171+ }
172+ }
127173
128174@Composable
129175fun <T : Any ?> FloconPanel (
130176 contentState : T ,
131177 onClose : (() -> Unit )? = null,
132- content : @Composable BoxScope .(T & Any ) -> Unit
178+ content : @Composable FloconPanelScope .(T & Any ) -> Unit
133179) {
134180 var rememberTarget by remember { mutableStateOf(contentState) }
135181
@@ -141,7 +187,19 @@ fun <T : Any?> FloconPanel(
141187
142188 FloconPanel (
143189 expanded = contentState != null ,
144- onClose = onClose,
190+ onDismissRequest = { onClose?.invoke() },
191+ actions = {
192+ if (onClose != null ) {
193+ FloconIconTonalButton (
194+ onClick = onClose,
195+ modifier = Modifier
196+ ) {
197+ FloconIcon (
198+ Icons .Outlined .Close
199+ )
200+ }
201+ }
202+ },
145203 ) {
146204 rememberTarget?.let { content(this , it) }
147205 }
0 commit comments