1+ package com.mrboomdev.awery.ui.screens.intro
2+
3+ import androidx.compose.foundation.Image
4+ import androidx.compose.foundation.layout.*
5+ import androidx.compose.foundation.rememberScrollState
6+ import androidx.compose.foundation.verticalScroll
7+ import androidx.compose.material3.*
8+ import androidx.compose.runtime.Composable
9+ import androidx.compose.runtime.remember
10+ import androidx.compose.ui.Alignment
11+ import androidx.compose.ui.Modifier
12+ import androidx.compose.ui.graphics.painter.Painter
13+ import androidx.compose.ui.graphics.vector.VectorPainter
14+ import androidx.compose.ui.text.font.FontWeight
15+ import androidx.compose.ui.unit.Dp
16+ import androidx.compose.ui.unit.dp
17+ import com.mrboomdev.awery.ui.utils.*
18+
19+ @DslMarker
20+ private annotation class IntroDslMarker
21+
22+ @IntroDslMarker
23+ interface IntroDsl {
24+ var iconSize: Dp
25+ var icon: @Composable () -> Painter ?
26+ var title: @Composable () -> String?
27+ var description: @Composable () -> String?
28+
29+ var primaryContent: (@Composable ColumnScope .(PaddingValues ) -> Unit )?
30+ var primaryContentAlignment: Alignment .Horizontal
31+
32+ var secondaryContent: (@Composable ColumnScope .(PaddingValues ) -> Unit )?
33+ var secondaryContentAlignment: Alignment .Horizontal
34+
35+ fun addAction (content : IntroActionDsl .() -> Unit )
36+ var actionsAlignment: Alignment .Horizontal
37+ var actionScale: Float
38+ }
39+
40+ fun IntroDsl.setAlignment (alignment : Alignment .Horizontal ) {
41+ primaryContentAlignment = alignment
42+ secondaryContentAlignment = alignment
43+ actionsAlignment = alignment
44+ }
45+
46+ @IntroDslMarker
47+ interface IntroActionDsl {
48+ var text: String
49+ var enabled: Boolean
50+ var onClick: () -> Unit
51+ }
52+
53+ @Composable
54+ fun IntroDslWrapper (
55+ contentPadding : PaddingValues = PaddingValues .Zero ,
56+ content : IntroDsl .() -> Unit
57+ ) {
58+ val windowSize = currentWindowSize()
59+
60+ val contentImpl = remember(content) {
61+ object : IntroDsl {
62+ val actions = mutableListOf<IntroActionDsl >()
63+ override var actionScale = 1f
64+ override var iconSize = IntroDefaults .iconSize
65+ override var icon: @Composable () -> Painter ? = { null }
66+ override var title: @Composable () -> String? = { null }
67+ override var description: @Composable () -> String? = { null }
68+ override var primaryContent: @Composable (ColumnScope .(PaddingValues ) -> Unit )? = null
69+ override var primaryContentAlignment = Alignment .Start
70+ override var secondaryContent: @Composable (ColumnScope .(PaddingValues ) -> Unit )? = null
71+ override var secondaryContentAlignment = Alignment .Start
72+ override var actionsAlignment = Alignment .Start
73+
74+ override fun addAction (content : IntroActionDsl .() -> Unit ) {
75+ actions + = object : IntroActionDsl {
76+ override lateinit var text: String
77+ override var enabled = true
78+ override lateinit var onClick: () -> Unit
79+ }.apply (content)
80+ }
81+ }.apply (content)
82+ }
83+
84+ val icon: (@Composable () -> Unit )? = contentImpl.icon()?.let { icon ->
85+ when (icon) {
86+ is VectorPainter -> {{
87+ Icon (
88+ modifier = Modifier .size(contentImpl.iconSize),
89+ tint = MaterialTheme .colorScheme.primary,
90+ painter = icon,
91+ contentDescription = null
92+ )
93+ }}
94+
95+ else -> {{
96+ Image (
97+ modifier = Modifier .size(contentImpl.iconSize),
98+ painter = icon,
99+ contentDescription = null
100+ )
101+ }}
102+ }
103+ }
104+
105+ if (windowSize.width >= WindowSizeType .Large ) {
106+ Row (
107+ modifier = Modifier .fillMaxSize(),
108+ horizontalArrangement = Arrangement .spacedBy(64 .dp)
109+ ) {
110+ Column (
111+ modifier = Modifier
112+ .fillMaxHeight()
113+ .weight(1f )
114+ .verticalScroll(rememberScrollState())
115+ .padding(contentPadding.only(top = true , horizontal = true ))
116+ .padding(start = niceSideInset()),
117+ verticalArrangement = Arrangement .spacedBy(8 .dp),
118+ horizontalAlignment = contentImpl.primaryContentAlignment
119+ ) {
120+ icon?.invoke()
121+
122+ contentImpl.title()?.also { title ->
123+ Text (
124+ modifier = Modifier .padding(top = 8 .dp),
125+ style = MaterialTheme .typography.headlineLarge,
126+ color = MaterialTheme .colorScheme.onBackground,
127+ fontWeight = FontWeight .Normal ,
128+ textAlign = contentImpl.primaryContentAlignment.toTextAlign(),
129+ text = title
130+ )
131+ }
132+
133+ contentImpl.description()?.also { description ->
134+ Text (
135+ textAlign = contentImpl.primaryContentAlignment.toTextAlign(),
136+ text = description
137+ )
138+ }
139+ }
140+
141+ Box (
142+ modifier = Modifier
143+ .fillMaxHeight()
144+ .weight(1f )
145+ ) {
146+ Column (
147+ modifier = Modifier
148+ .fillMaxSize()
149+ .verticalScroll(rememberScrollState()),
150+ horizontalAlignment = contentImpl.secondaryContentAlignment
151+ ) {
152+ contentImpl.secondaryContent?.invoke(this , contentPadding.only(
153+ top = true , end = true
154+ ).add(bottom = 64 .dp, end = niceSideInset()))
155+ }
156+
157+ Row (
158+ modifier = Modifier
159+ .fillMaxWidth()
160+ .padding(contentPadding.only(bottom = true , end = true ))
161+ .padding(end = niceSideInset())
162+ .align(Alignment .BottomCenter ),
163+ horizontalArrangement = Arrangement .spacedBy(IntroDefaults .spaceBetweenActions, contentImpl.actionsAlignment)
164+ ) {
165+ contentImpl.actions.forEach { action ->
166+ Button (
167+ enabled = action.enabled,
168+ onClick = action.onClick
169+ ) {
170+ Text (action.text)
171+ }
172+ }
173+ }
174+ }
175+ }
176+ } else {
177+ Column (Modifier .fillMaxSize()) {
178+ Column (
179+ modifier = Modifier
180+ .fillMaxWidth()
181+ .weight(1f )
182+ .verticalScroll(rememberScrollState())
183+ .padding(contentPadding.only(top = true , start = true , end = true )),
184+ verticalArrangement = Arrangement .spacedBy(8 .dp),
185+ horizontalAlignment = contentImpl.primaryContentAlignment
186+ ) {
187+ if (icon != null && contentImpl.primaryContentAlignment == Alignment .CenterHorizontally ) {
188+ Spacer (Modifier .height(16 .dp))
189+ }
190+
191+ icon?.invoke()
192+
193+ contentImpl.title()?.also { title ->
194+ Text (
195+ modifier = Modifier .padding(top = 8 .dp),
196+ style = MaterialTheme .typography.headlineLarge,
197+ color = MaterialTheme .colorScheme.onBackground,
198+ fontWeight = FontWeight .Normal ,
199+ textAlign = contentImpl.primaryContentAlignment.toTextAlign(),
200+ text = title
201+ )
202+ }
203+
204+ contentImpl.description()?.also { description ->
205+ Text (
206+ textAlign = contentImpl.primaryContentAlignment.toTextAlign(),
207+ text = description
208+ )
209+ }
210+
211+ Column (
212+ modifier = Modifier
213+ .fillMaxWidth()
214+ .weight(1f ),
215+ horizontalAlignment = contentImpl.secondaryContentAlignment
216+ ) {
217+ contentImpl.secondaryContent?.invoke(this , contentPadding.only(
218+ /* horizontal = true*/
219+ ).add(vertical = 16 .dp))
220+ }
221+ }
222+
223+ Row (
224+ modifier = Modifier
225+ .fillMaxWidth()
226+ .padding(contentPadding.only(horizontal = true , bottom = true )),
227+ horizontalArrangement = Arrangement .spacedBy(IntroDefaults .spaceBetweenActions, contentImpl.actionsAlignment)
228+ ) {
229+ contentImpl.actions.forEach { action ->
230+ Button (
231+ enabled = action.enabled,
232+ onClick = action.onClick,
233+
234+ contentPadding = ButtonDefaults .ContentPadding *
235+ (contentImpl.actionScale.takeUnless { it == 1f }?.times(1.25f ) ? : 1f )
236+ ) {
237+ Text (
238+ fontSize = LocalTextStyle .current.fontSize * contentImpl.actionScale,
239+ text = action.text
240+ )
241+ }
242+ }
243+ }
244+
245+ if (contentImpl.actionScale > 1f ) {
246+ Spacer (Modifier .height((contentImpl.actionScale * 10f ).dp))
247+ }
248+ }
249+ }
250+ }
0 commit comments