1
1
package me.sujanpoudel.playdeals.common.ui.components.common
2
2
3
+ import androidx.compose.animation.AnimatedContent
4
+ import androidx.compose.animation.core.tween
5
+ import androidx.compose.animation.fadeIn
6
+ import androidx.compose.animation.fadeOut
7
+ import androidx.compose.animation.togetherWith
8
+ import androidx.compose.foundation.interaction.MutableInteractionSource
9
+ import androidx.compose.foundation.layout.WindowInsets
10
+ import androidx.compose.foundation.layout.fillMaxWidth
11
+ import androidx.compose.foundation.layout.height
12
+ import androidx.compose.foundation.layout.heightIn
13
+ import androidx.compose.foundation.layout.windowInsetsPadding
14
+ import androidx.compose.foundation.text.BasicTextField
15
+ import androidx.compose.material.ExperimentalMaterialApi
16
+ import androidx.compose.material.TextFieldDefaults
3
17
import androidx.compose.material.icons.Icons
4
18
import androidx.compose.material.icons.filled.ArrowBack
5
19
import androidx.compose.material3.CenterAlignedTopAppBar
@@ -12,54 +26,153 @@ import androidx.compose.material3.TopAppBarDefaults
12
26
import androidx.compose.material3.TopAppBarScrollBehavior
13
27
import androidx.compose.material3.rememberTopAppBarState
14
28
import androidx.compose.runtime.Composable
29
+ import androidx.compose.runtime.Immutable
30
+ import androidx.compose.runtime.LaunchedEffect
31
+ import androidx.compose.runtime.State
32
+ import androidx.compose.runtime.remember
15
33
import androidx.compose.ui.Modifier
34
+ import androidx.compose.ui.focus.FocusRequester
35
+ import androidx.compose.ui.focus.focusRequester
16
36
import androidx.compose.ui.graphics.Color
37
+ import androidx.compose.ui.graphics.SolidColor
17
38
import androidx.compose.ui.text.font.FontWeight
39
+ import androidx.compose.ui.text.input.VisualTransformation
18
40
import androidx.compose.ui.text.style.TextAlign
41
+ import androidx.compose.ui.unit.dp
42
+ import androidx.compose.ui.unit.sp
19
43
import me.sujanpoudel.playdeals.common.navigation.Navigator
44
+ import me.sujanpoudel.playdeals.common.strings.Strings
45
+
46
+ @Composable
47
+ fun rememberTextTitle (title : String ): ScaffoldToolbar .ScaffoldTitle {
48
+ return remember(title) {
49
+ ScaffoldToolbar .ScaffoldTitle .TextTitle (title)
50
+ }
51
+ }
20
52
21
53
object ScaffoldToolbar {
22
54
55
+ sealed class ScaffoldTitle {
56
+ data object None : ScaffoldTitle ()
57
+
58
+ @Immutable
59
+ data class TextTitle (val text : String ) : ScaffoldTitle()
60
+
61
+ @Immutable
62
+ class SearchBarTitle (val text : State <String >, val onTextUpdated : (String ) -> Unit ) : ScaffoldTitle()
63
+ }
64
+
23
65
@Composable
24
66
fun NavigationIcon (navigator : Navigator ) {
25
67
IconButton (onClick = navigator::pop) {
26
68
Icon (Icons .Default .ArrowBack , contentDescription = " " )
27
69
}
28
70
}
29
71
30
- @OptIn(ExperimentalMaterial3Api ::class )
72
+ @OptIn(ExperimentalMaterial3Api ::class , ExperimentalMaterialApi :: class )
31
73
@Composable
32
74
operator fun invoke (
33
75
modifier : Modifier = Modifier ,
34
- title : String? = null,
35
- showNavBackIcon : Boolean = true,
76
+ title : ScaffoldTitle ,
77
+ showNavIcon : Boolean = true,
78
+ alwaysShowNavIcon : Boolean = false,
36
79
navigationIcon : @Composable (Navigator ) -> Unit = { it -> NavigationIcon (it) },
37
80
actions : (@Composable (Navigator ) -> Unit )? = null,
38
81
behaviour : TopAppBarScrollBehavior = TopAppBarDefaults .enterAlwaysScrollBehavior(rememberTopAppBarState()),
39
82
) {
40
83
val navigator = Navigator .current
41
-
42
84
CenterAlignedTopAppBar (
43
- modifier = modifier,
44
- title = {
45
- title?.let {
46
- Text (
47
- text = title,
48
- style = MaterialTheme .typography.titleMedium.copy(fontWeight = FontWeight .SemiBold ),
49
- textAlign = TextAlign .Center ,
50
- )
51
- }
52
- },
53
- navigationIcon = {
54
- if (showNavBackIcon && navigator.backStackCount.value > 1 ) {
55
- navigationIcon(navigator)
56
- }
57
- },
58
- actions = { actions?.invoke(navigator) },
85
+ modifier = modifier.windowInsetsPadding(WindowInsets (top = 10 .dp)),
86
+ title = { ToolbarTitle (title) },
87
+ navigationIcon =
88
+ {
89
+ if (alwaysShowNavIcon || (showNavIcon && navigator.backStackCount.value > 1 )) {
90
+ navigationIcon(navigator)
91
+ }
92
+ },
93
+ actions =
94
+ { actions?.invoke(navigator) },
59
95
colors = TopAppBarDefaults .topAppBarColors(
60
96
containerColor = Color .Transparent ,
61
97
),
62
98
scrollBehavior = behaviour,
63
99
)
64
100
}
101
+
102
+ @Composable
103
+ private fun ToolbarTitle (title : ScaffoldTitle ) {
104
+ val contentTransform = remember {
105
+ fadeIn(animationSpec = tween(220 , delayMillis = 90 ))
106
+ .togetherWith(fadeOut(animationSpec = tween(90 )))
107
+ }
108
+
109
+ AnimatedContent (
110
+ modifier = Modifier .fillMaxWidth().heightIn(min = 24 .dp),
111
+ targetState = title,
112
+ transitionSpec = { contentTransform },
113
+ ) { scaffoldTitle ->
114
+ when (scaffoldTitle) {
115
+ ScaffoldTitle .None -> {}
116
+ is ScaffoldTitle .SearchBarTitle -> SearchBarTitle (scaffoldTitle)
117
+ is ScaffoldTitle .TextTitle -> Text (
118
+ text = scaffoldTitle.text,
119
+ style = MaterialTheme .typography.titleMedium.copy(fontWeight = FontWeight .SemiBold ),
120
+ textAlign = TextAlign .Center ,
121
+ )
122
+ }
123
+ }
124
+ }
125
+
126
+ @OptIn(ExperimentalMaterialApi ::class )
127
+ @Composable
128
+ private fun SearchBarTitle (title : ScaffoldTitle .SearchBarTitle ) {
129
+ val interactionSource = remember { MutableInteractionSource () }
130
+ val focusRequester = remember { FocusRequester () }
131
+
132
+ LaunchedEffect (Unit ) {
133
+ focusRequester.requestFocus()
134
+ }
135
+
136
+ BasicTextField (
137
+ modifier = Modifier .fillMaxWidth().height(44 .dp)
138
+ .focusRequester(focusRequester),
139
+ value = title.text.value,
140
+ onValueChange = title.onTextUpdated,
141
+ interactionSource = interactionSource,
142
+ singleLine = true ,
143
+ textStyle = MaterialTheme .typography.bodySmall.copy(
144
+ color = MaterialTheme .colorScheme.onBackground,
145
+ fontSize = 16 .sp,
146
+ ),
147
+ cursorBrush = SolidColor (MaterialTheme .colorScheme.onBackground),
148
+ decorationBox = { innerTextField ->
149
+ TextFieldDefaults .OutlinedTextFieldDecorationBox (
150
+ value = title.text.value,
151
+ contentPadding = TextFieldDefaults .textFieldWithoutLabelPadding(
152
+ top = 8 .dp,
153
+ bottom = 8 .dp,
154
+ ),
155
+ innerTextField = innerTextField,
156
+ placeholder = {
157
+ Text (
158
+ Strings .search,
159
+ style = MaterialTheme .typography.bodySmall.copy(
160
+ color = MaterialTheme .colorScheme.onBackground,
161
+ fontSize = 16 .sp,
162
+ ),
163
+ )
164
+ },
165
+ enabled = true ,
166
+ singleLine = true ,
167
+ visualTransformation = VisualTransformation .None ,
168
+ interactionSource = interactionSource,
169
+ colors = TextFieldDefaults .outlinedTextFieldColors(
170
+ textColor = MaterialTheme .colorScheme.onSurface,
171
+ focusedBorderColor = MaterialTheme .colorScheme.primary,
172
+ unfocusedBorderColor = MaterialTheme .colorScheme.outline,
173
+ ),
174
+ )
175
+ },
176
+ )
177
+ }
65
178
}
0 commit comments