Skip to content

Commit 9fa2432

Browse files
committed
feat: improve animation and structure of details section components
- Add `animateContentSize()` to the `About` section to ensure smooth transitions during content updates - Refactor `WhatsNew` section by extracting `ExpandableMarkdownContent` into a separate Composable function for better maintainability - Ensure the `WhatsNew` markdown content properly utilizes `animateContentSize()` and `liquefiable` state - Improve layout consistency in `WhatsNew` by wrapping the expandable content in a dedicated `item` block within the list view
1 parent 21c2707 commit 9fa2432

2 files changed

Lines changed: 123 additions & 96 deletions

File tree

  • feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections

feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/About.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,8 @@ fun LazyListScope.about(
131131
modifier =
132132
Modifier
133133
.fillMaxWidth()
134-
.liquefiable(liquidState),
134+
.liquefiable(liquidState)
135+
.animateContentSize(),
135136
)
136137
}
137138
}

feature/details/presentation/src/commonMain/kotlin/zed/rainxch/details/presentation/components/sections/WhatsNew.kt

Lines changed: 121 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import androidx.compose.material3.HorizontalDivider
2222
import androidx.compose.material3.MaterialTheme
2323
import androidx.compose.material3.Text
2424
import androidx.compose.material3.TextButton
25+
import androidx.compose.runtime.Composable
2526
import androidx.compose.runtime.getValue
2627
import androidx.compose.runtime.mutableFloatStateOf
2728
import androidx.compose.runtime.remember
@@ -36,6 +37,7 @@ import androidx.compose.ui.text.font.FontWeight
3637
import androidx.compose.ui.unit.Dp
3738
import androidx.compose.ui.unit.dp
3839
import com.mikepenz.markdown.compose.Markdown
40+
import io.github.fletchmckee.liquid.LiquidState
3941
import io.github.fletchmckee.liquid.liquefiable
4042
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
4143
import org.jetbrains.compose.resources.stringResource
@@ -123,102 +125,126 @@ fun LazyListScope.whatsNew(
123125
modifier = Modifier.liquefiable(liquidState),
124126
)
125127
}
128+
}
129+
}
130+
}
131+
132+
item {
133+
val liquidState = LocalTopbarLiquidState.current
134+
135+
Spacer(Modifier.height(12.dp))
136+
137+
ExpandableMarkdownContent(
138+
translationState = translationState,
139+
release = release,
140+
collapsedHeight = collapsedHeight,
141+
isExpanded = isExpanded,
142+
liquidState = liquidState,
143+
onToggleExpanded = onToggleExpanded,
144+
)
145+
}
146+
}
147+
148+
@Composable
149+
private fun ExpandableMarkdownContent(
150+
translationState: TranslationState,
151+
release: GithubRelease,
152+
collapsedHeight: Dp,
153+
isExpanded: Boolean,
154+
liquidState: LiquidState,
155+
onToggleExpanded: () -> Unit,
156+
) {
157+
val displayContent =
158+
if (translationState.isShowingTranslation && translationState.translatedText != null) {
159+
translationState.translatedText
160+
} else {
161+
release.description ?: stringResource(Res.string.no_release_notes)
162+
}
126163

127-
Spacer(Modifier.height(12.dp))
128-
129-
val density = LocalDensity.current
130-
val colors = rememberMarkdownColors()
131-
val typography = rememberMarkdownTypography()
132-
val flavour = remember { GFMFlavourDescriptor() }
133-
val cardColor = MaterialTheme.colorScheme.surfaceContainerLow
134-
135-
val displayContent =
136-
if (translationState.isShowingTranslation && translationState.translatedText != null) {
137-
translationState.translatedText
138-
} else {
139-
release.description ?: stringResource(Res.string.no_release_notes)
140-
}
141-
142-
AnimatedContent(
143-
targetState = displayContent,
144-
transitionSpec = { fadeIn() togetherWith fadeOut() },
145-
label = "whats_new_content",
146-
) { content ->
147-
val collapsedHeightPx = with(density) { collapsedHeight.toPx() }
148-
var contentHeightPx by remember(content, collapsedHeightPx) {
149-
mutableFloatStateOf(0f)
150-
}
151-
val needsExpansion =
152-
remember(contentHeightPx, collapsedHeightPx) {
153-
contentHeightPx > collapsedHeightPx && collapsedHeightPx > 0f
154-
}
155-
156-
Column(
157-
modifier = Modifier.animateContentSize(),
158-
) {
159-
Box {
160-
Box(
161-
modifier =
162-
if (!isExpanded && needsExpansion) {
163-
Modifier.heightIn(max = collapsedHeight).clipToBounds()
164-
} else {
165-
Modifier
166-
},
167-
) {
168-
Markdown(
169-
content = content,
170-
colors = colors,
171-
typography = typography,
172-
flavour = flavour,
173-
imageTransformer = MarkdownImageTransformer,
174-
modifier =
175-
Modifier
176-
.fillMaxWidth()
177-
.liquefiable(liquidState)
178-
.onGloballyPositioned { coordinates ->
179-
val measured = coordinates.size.height.toFloat()
180-
if (measured > contentHeightPx) {
181-
contentHeightPx = measured
182-
}
183-
},
184-
)
185-
}
186-
187-
if (!isExpanded && needsExpansion) {
188-
Box(
189-
modifier =
190-
Modifier
191-
.align(Alignment.BottomCenter)
192-
.fillMaxWidth()
193-
.height(80.dp)
194-
.background(
195-
Brush.verticalGradient(
196-
0f to cardColor.copy(alpha = 0f),
197-
1f to cardColor,
198-
),
199-
),
200-
)
201-
}
202-
}
203-
204-
if (needsExpansion) {
205-
TextButton(
206-
onClick = onToggleExpanded,
207-
modifier = Modifier.align(Alignment.CenterHorizontally),
208-
) {
209-
Text(
210-
text =
211-
if (isExpanded) {
212-
stringResource(Res.string.show_less)
213-
} else {
214-
stringResource(Res.string.read_more)
215-
},
216-
style = MaterialTheme.typography.labelLarge,
217-
color = MaterialTheme.colorScheme.primary,
218-
)
219-
}
220-
}
221-
}
164+
val density = LocalDensity.current
165+
val colors = rememberMarkdownColors()
166+
val typography = rememberMarkdownTypography()
167+
val flavour = remember { GFMFlavourDescriptor() }
168+
val cardColor = MaterialTheme.colorScheme.surfaceContainerLow
169+
170+
AnimatedContent(
171+
targetState = displayContent,
172+
transitionSpec = { fadeIn() togetherWith fadeOut() },
173+
label = "whats_new_content",
174+
) { content ->
175+
176+
val collapsedHeightPx = with(density) { collapsedHeight.toPx() }
177+
var contentHeightPx by remember(content, collapsedHeightPx) {
178+
mutableFloatStateOf(0f)
179+
}
180+
val needsExpansion =
181+
remember(contentHeightPx, collapsedHeightPx) {
182+
contentHeightPx > collapsedHeightPx && collapsedHeightPx > 0f
183+
}
184+
185+
Column(
186+
modifier = Modifier.animateContentSize(),
187+
) {
188+
Box {
189+
Box(
190+
modifier =
191+
if (!isExpanded && needsExpansion) {
192+
Modifier.heightIn(max = collapsedHeight).clipToBounds()
193+
} else {
194+
Modifier
195+
},
196+
) {
197+
Markdown(
198+
content = content,
199+
colors = colors,
200+
typography = typography,
201+
flavour = flavour,
202+
imageTransformer = MarkdownImageTransformer,
203+
modifier =
204+
Modifier
205+
.fillMaxWidth()
206+
.liquefiable(liquidState)
207+
.onGloballyPositioned { coordinates ->
208+
val measured = coordinates.size.height.toFloat()
209+
if (measured > contentHeightPx) {
210+
contentHeightPx = measured
211+
}
212+
},
213+
)
214+
}
215+
216+
if (!isExpanded && needsExpansion) {
217+
Box(
218+
modifier =
219+
Modifier
220+
.align(Alignment.BottomCenter)
221+
.fillMaxWidth()
222+
.height(80.dp)
223+
.background(
224+
Brush.verticalGradient(
225+
0f to cardColor.copy(alpha = 0f),
226+
1f to cardColor,
227+
),
228+
),
229+
)
230+
}
231+
}
232+
233+
if (needsExpansion) {
234+
TextButton(
235+
onClick = onToggleExpanded,
236+
modifier = Modifier.align(Alignment.CenterHorizontally),
237+
) {
238+
Text(
239+
text =
240+
if (isExpanded) {
241+
stringResource(Res.string.show_less)
242+
} else {
243+
stringResource(Res.string.read_more)
244+
},
245+
style = MaterialTheme.typography.labelLarge,
246+
color = MaterialTheme.colorScheme.primary,
247+
)
222248
}
223249
}
224250
}

0 commit comments

Comments
 (0)