Skip to content

Commit

Permalink
Lesson feedback (#38)
Browse files Browse the repository at this point in the history
* Fix the progress bar on light theme

* Add "Continue" after answering a question

* Fix image sizes

* Fix the lesson card

* Print lesson story

* WIP: Improve content

* WIP: Improve content

* Apply Nicole's feedback

* Add joke AS image

* More fixes
  • Loading branch information
ILIYANGERMANOV authored Jun 6, 2024
1 parent 78ac972 commit 6730439
Show file tree
Hide file tree
Showing 16 changed files with 167 additions and 74 deletions.
5 changes: 5 additions & 0 deletions composeApp/src/commonMain/kotlin/component/text/Subtitle.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,22 @@ import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow

@Composable
fun SubTitle(
text: String,
modifier: Modifier = Modifier,
fontWeight: FontWeight? = null,
maxLines: Int = Int.MAX_VALUE,
overflow: TextOverflow = TextOverflow.Clip,
) {
Text(
modifier = modifier,
text = text,
style = MaterialTheme.typography.subtitle1,
fontWeight = fontWeight,
maxLines = maxLines,
overflow = overflow,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import component.BackButton
import component.LearnScaffold
import component.platformHorizontalPadding
import component.ScreenType.*
import component.screenType
import ui.screen.course.CourseItemViewState
import ui.screen.course.CourseViewEvent
import ui.screen.course.CourseViewState
Expand All @@ -28,7 +29,11 @@ fun CourseContent(
}),
title = viewState.name
) { contentPadding ->
val horizontalPadding = platformHorizontalPadding()
val horizontalPadding = when (screenType()) {
Mobile -> 8.dp
Tablet -> 16.dp
Desktop -> 16.dp
}
LazyColumn(
modifier = Modifier
.fillMaxSize()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import component.ScreenType.*
import component.isLargeScreen
import component.screenType
import component.text.SubTitle
import component.text.Title
import io.kamel.image.KamelImage
Expand Down Expand Up @@ -58,11 +62,11 @@ fun LessonCard(
Column(
modifier = Modifier
.onSizeChanged {
height = minOf(with(density) { it.height.toDp() }, height)
height = maxOf(with(density) { it.height.toDp() }, height)
}
.defaultMinSize(minHeight = height)
.padding(
start = if (isLargeScreen()) 24.dp else 20.dp,
start = if (isLargeScreen()) 24.dp else 16.dp,
end = if (isLargeScreen()) 16.dp else 12.dp
)
.padding(vertical = 16.dp),
Expand All @@ -72,8 +76,12 @@ fun LessonCard(
text = lesson.name,
fontWeight = FontWeight.Bold
)
Spacer(Modifier.height(4.dp))
SubTitle(text = lesson.tagline)
Spacer(Modifier.height(8.dp))
SubTitle(
text = lesson.tagline,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
)
}
}
}
Expand All @@ -86,10 +94,17 @@ private fun LessonImage(
) {
KamelImage(
modifier = modifier
.aspectRatio(1f),
contentScale = ContentScale.Crop,
.aspectRatio(
when (screenType()) {
Mobile -> 0.6f
Tablet -> 1f
Desktop -> 1f
}
)
.clip(RoundedCornerShape(topStart = 24.dp, bottomStart = 24.dp)),
contentScale = ContentScale.FillHeight,
contentAlignment = Alignment.CenterStart,
resource = asyncPainterResource(imageUrl),
contentDescription = null
contentDescription = null,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -73,16 +73,18 @@ class LessonViewModel(

@optics
data class LocalState(
val answers: Map<LessonItemId, Set<AnswerId>>,
val openAnswers: Map<LessonItemId, String>,
val selectedAnswers: Map<LessonItemId, Set<AnswerId>>,
val openAnswersInput: Map<LessonItemId, String>,
val chosen: Map<LessonItemId, ChoiceOptionId>,
val answered: Set<LessonItemId>,
val completed: Set<LessonItemId>,
val choices: Map<LessonItemId, ChoiceOptionId>,
) {
companion object {
val Initial = LocalState(
answers = emptyMap(),
openAnswers = emptyMap(),
choices = emptyMap(),
selectedAnswers = emptyMap(),
openAnswersInput = emptyMap(),
chosen = emptyMap(),
answered = emptySet(),
completed = emptySet(),
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import component.ScreenType.*
import component.screenType
import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.delay
import ui.screen.lesson.LessonItemViewState
import ui.screen.lesson.LessonViewState
import ui.screen.lesson.OpenQuestionItemViewState
import ui.screen.lesson.QuestionItemViewState

@Composable
fun AutoScrollEffect(
listState: LazyListState,
items: ImmutableList<LessonItemViewState>,
viewState: LessonViewState,
) {
val items = viewState.items
val itemsCount = items.size
LaunchedEffect(itemsCount) {
if (itemsCount >= 2 && !items[itemsCount - 2].isQuestion()) {
LaunchedEffect(itemsCount, viewState.cta) {
if (itemsCount >= 2) {
// ensure auto scrolls works for images that are loading
repeat(4) {
listState.animateScrollToItem(items.lastIndex)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ private fun LessonItemsLazyColum(

AutoScrollEffect(
listState = listState,
items = viewState.items,
viewState = viewState,
)

LazyColumn(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ fun LessonProgressBar(
ProgressBarLine(width = progressBarWidth, color = Gray)
// foreground (progress)
val progressWidth = (progressBarWidth * progressPercent).coerceAtMost(progressBarWidth)
ProgressBarLine(width = progressWidth, color = MaterialTheme.colors.primary)
ProgressBarLine(width = progressWidth, color = MaterialTheme.colors.secondary)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,21 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import component.ScreenType.*
import component.screenType
import io.kamel.image.KamelImage
import io.kamel.image.asyncPainterResource
import ui.screen.lesson.ImageItemViewState
import ui.screen.lesson.composable.ItemSpacingMedium

@Composable
fun imageSize(): Dp = when (screenType()) {
Mobile, Tablet -> 232.dp
Desktop -> 284.dp
}

@Composable
fun ImageLessonItem(
viewState: ImageItemViewState,
Expand All @@ -20,7 +29,7 @@ fun ImageLessonItem(
KamelImage(
modifier = modifier
.padding(top = ItemSpacingMedium)
.size(304.dp)
.size(imageSize())
.clip(RoundedCornerShape(16.dp)),
resource = asyncPainterResource(viewState.imageUrl),
contentDescription = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import component.RemoteLottieAnimation
import ui.screen.lesson.LottieAnimationItemViewState
import ui.screen.lesson.composable.ItemSpacing
Expand All @@ -17,7 +16,7 @@ fun LottieAnimationLessonItem(
RemoteLottieAnimation(
modifier = modifier
.padding(ItemSpacing)
.size(304.dp),
.size(imageSize()),
animationUrl = viewState.lottieUrl,
repeat = true,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import ui.EventHandler
import ui.screen.lesson.LessonViewEvent
import ui.screen.lesson.LessonViewModel.LocalState
import ui.screen.lesson.LessonVmContext
import ui.screen.lesson.choices
import ui.screen.lesson.chosen
import ui.screen.lesson.completed
import ui.screen.lesson.mapper.toDomain

Expand All @@ -21,7 +21,7 @@ class OnChoiceClickEventHandler(
modifyState { state ->
val questionId = event.questionId.toDomain()
state.copy {
LocalState.choices transform { it + (questionId to ChoiceOptionId(event.choiceId)) }
LocalState.chosen transform { it + (questionId to ChoiceOptionId(event.choiceId)) }
LocalState.completed transform { it + questionId }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import ui.screen.lesson.LessonVmContext
import ui.screen.lesson.QuestionTypeViewState.MultipleChoice
import ui.screen.lesson.QuestionTypeViewState.SingleChoice
import ui.screen.lesson.QuestionViewEvent
import ui.screen.lesson.answers
import ui.screen.lesson.completed
import ui.screen.lesson.answered
import ui.screen.lesson.mapper.toDomain
import ui.screen.lesson.selectedAnswers

class QuestionViewEventHandler(
private val platform: Platform,
Expand All @@ -37,9 +37,9 @@ class QuestionViewEventHandler(
modifyState { state ->
val questionId = event.questionId.toDomain()
val answerId = AnswerId(event.answerId)
if (questionId in state.answers) {
if (questionId in state.selectedAnswers) {
// Already answered, modify the existing answer
LocalState.answers.index(Index.map(), questionId).modify(state) {
LocalState.selectedAnswers.index(Index.map(), questionId).modify(state) {
when (event.questionType) {
SingleChoice -> if (event.checked) {
setOf(answerId)
Expand All @@ -57,7 +57,7 @@ class QuestionViewEventHandler(
}
} else {
// Never answered
LocalState.answers.modify(state) { answers ->
LocalState.selectedAnswers.modify(state) { answers ->
if (event.checked) {
answers + Pair(questionId, setOf(answerId))
} else {
Expand All @@ -70,7 +70,7 @@ class QuestionViewEventHandler(

private fun LessonVmContext.handleCheckClick(event: QuestionViewEvent.OnCheckClick) {
modifyState { state ->
LocalState.completed.modify(state) { completed ->
LocalState.answered.modify(state) { completed ->
completed + event.questionId.toDomain()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class LessonTreeManager {
localState: LessonViewModel.LocalState
): LessonItemId? = when (this) {
is LinearItem -> next
is ChoiceItem -> localState.choices[id]?.let { choiceId ->
is ChoiceItem -> localState.chosen[id]?.let { choiceId ->
options.firstOrNull { it.id == choiceId }?.next
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,32 @@ class LessonViewStateMapper(
it.toViewState(localState, content.items)
}.toImmutableList(),
cta = when (val currentItem = lessonItems.lastOrNull()) {
null, is QuestionItem, is OpenQuestionItem,
null, is OpenQuestionItem,
is ChoiceItem -> null

else -> {
platform.log(LogLevel.Debug, "Current item: $currentItem")
if (currentItem is LinearItem && currentItem.next == null) {
CtaViewState.Finish(currentItem.id.toViewState())
is QuestionItem -> {
if (currentItem.id in localState.answered) {
ctaViewState(currentItem)
} else {
CtaViewState.Continue(currentItem.id.toViewState())
null
}
}

else -> ctaViewState(currentItem)
},
progress = toProgressViewState(lessonItems)
)
}

private fun ctaViewState(currentItem: LessonItem): CtaViewState {
platform.log(LogLevel.Debug, "Current item: $currentItem")
return if (currentItem is LinearItem && currentItem.next == null) {
CtaViewState.Finish(currentItem.id.toViewState())
} else {
CtaViewState.Continue(currentItem.id.toViewState())
}
}

private fun Lesson.toProgressViewState(
lessonItems: List<LessonItem>,
): LessonProgressViewState {
Expand Down Expand Up @@ -119,7 +129,7 @@ class LessonViewStateMapper(
): OpenQuestionItemViewState = OpenQuestionItemViewState(
id = id.toViewState(),
question = question,
answer = localState.openAnswers[id],
answer = localState.openAnswersInput[id],
correctAnswer = correctAnswer,
answered = id in localState.completed,
)
Expand All @@ -133,7 +143,7 @@ class LessonViewStateMapper(
type = if (correct.size == 1) QuestionTypeViewState.SingleChoice else QuestionTypeViewState.MultipleChoice,
answers = answers.map { it.toViewState(this, localState) }
.toImmutableList(),
answered = id in localState.completed,
answered = id in localState.answered,
)

private fun Answer.toViewState(
Expand All @@ -144,7 +154,7 @@ class LessonViewStateMapper(
text = text,
explanation = explanation,
correct = id in question.correct,
selected = id in localState.answers.getOrElse(question.id) { emptySet() },
selected = id in localState.selectedAnswers.getOrElse(question.id) { emptySet() },
)

private fun TextItem.toViewState(): TextItemViewState = TextItemViewState(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ object ProgrammingFundamentals : CourseDsl({
lesson(
name = "Programming: Math in disguise",
tagline = "Mathematical functions can do much more than you think." +
" Function Composition.",
" Function composition.",
imageUrl = LessonImage
)
})
Loading

0 comments on commit 6730439

Please sign in to comment.