Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lesson feedback #38

Merged
merged 10 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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