Skip to content

Commit

Permalink
Bugfixes and improvements (#70)
Browse files Browse the repository at this point in the history
* Mark lesson as completed + refactor

* Fix back navigation bug

* Remove delay from fake repo

* Turn off fakes
  • Loading branch information
ILIYANGERMANOV authored Dec 9, 2024
1 parent efa94a7 commit 21b88b3
Show file tree
Hide file tree
Showing 13 changed files with 85 additions and 47 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@ import data.CourseRepository
import ivy.content.EmptyContent
import ivy.data.source.model.CourseResponse
import ivy.model.*
import kotlinx.coroutines.delay

class FakeCourseRepository : CourseRepository {
override suspend fun fetchCourse(
courseId: CourseId
): Either<String, CourseResponse> {
delay(1_000)
return CourseResponse(
course = Course(
id = CourseId("1"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ class FakeLessonRepository : LessonRepository {
): Either<String, Unit> {
return Either.Right(Unit)
}

override suspend fun markLessonAsCompleted(
course: CourseId,
lesson: LessonId
): Either<String, Unit> {
return Either.Right(Unit)
}
}
23 changes: 22 additions & 1 deletion composeApp/src/commonMain/kotlin/data/lesson/LessonRepository.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import data.lesson.mapper.LessonMapper
import domain.SessionManager
import domain.model.LessonProgress
import domain.model.LessonWithProgress
import ivy.data.source.CoursesDataSource
import ivy.data.source.LessonDataSource
import ivy.model.CourseId
import ivy.model.LessonId
Expand All @@ -18,6 +19,7 @@ class LessonRepositoryImpl(
private val datasource: LessonDataSource,
private val mapper: LessonMapper,
private val sessionManager: SessionManager,
private val coursesDataSource: CoursesDataSource,
) : LessonRepository {

override suspend fun fetchLesson(
Expand Down Expand Up @@ -56,7 +58,21 @@ class LessonRepositoryImpl(
answered = progress.answered,
completed = progress.completed,
)
)
).bind()
}
}

override suspend fun markLessonAsCompleted(
course: CourseId,
lesson: LessonId,
): Either<String, Unit> = withContext(dispatchers.io) {
either {
val session = sessionManager.getSession().bind()
coursesDataSource.saveProgress(
session = session,
course = course,
lesson = lesson
).bind()
}
}
}
Expand All @@ -72,4 +88,9 @@ interface LessonRepository {
lesson: LessonId,
progress: LessonProgress,
): Either<String, Unit>

suspend fun markLessonAsCompleted(
course: CourseId,
lesson: LessonId,
): Either<String, Unit>
}
8 changes: 5 additions & 3 deletions composeApp/src/commonMain/kotlin/ui/EventHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@ package ui
import kotlinx.coroutines.CoroutineScope
import kotlin.reflect.KClass

interface EventHandler<Event : Any, State : Any> {
interface EventHandler<Event : Any, State : Any, Args : Any> {
val eventTypes: Set<KClass<*>>
suspend fun VmContext<State>.handleEvent(event: Event)
suspend fun VmContext<State, Args>.handleEvent(event: Event)
}

interface VmContext<State> {
interface VmContext<State : Any, Args : Any> {
@EventHandlerDsl
val state: State

@EventHandlerDsl
fun modifyState(transformation: (State) -> State)

val screenScope: CoroutineScope

val args: Args
}

@DslMarker
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,15 @@ class LessonScreen(
autoWire(::OnFinishClickEventHandler)
autoWire(::OnChoiceClickEventHandler)
register {
LessonViewModel(
LessonViewModel.Args(
courseId = courseId,
lessonId = lessonId,
lessonName = lessonName,
)
}
register {
LessonViewModel(
args = Di.get(),
screenScope = screenScope,
repository = Di.get(),
viewStateMapper = Di.get(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,17 @@ import kotlinx.coroutines.launch
import ui.ComposeViewModel
import ui.EventHandler
import ui.VmContext
import ui.screen.lesson.LessonViewModel.LocalState
import ui.screen.lesson.mapper.LessonViewStateMapper
import util.Logger


class LessonViewModel(
private val courseId: CourseId,
private val lessonId: LessonId,
private val lessonName: String,
override val args: Args,
override val screenScope: CoroutineScope,
private val repository: LessonRepository,
private val viewStateMapper: LessonViewStateMapper,
private val eventHandlers: Set<EventHandler<*, LocalState>>,
private val eventHandlers: Set<LessonEventHandler<*>>,
private val analytics: Analytics,
private val logger: Logger,
) : ComposeViewModel<LessonViewState, LessonViewEvent>, LessonVmContext {
Expand All @@ -46,8 +45,8 @@ class LessonViewModel(
private fun saveLessonProgress(localState: LocalState) {
screenScope.launch {
repository.saveLessonProgress(
course = courseId,
lesson = lessonId,
course = args.courseId,
lesson = args.lessonId,
progress = LessonProgress(
selectedAnswers = localState.selectedAnswers,
openAnswersInput = localState.openAnswersInput,
Expand All @@ -69,9 +68,9 @@ class LessonViewModel(
source = Source.Lesson,
event = "view",
params = mapOf(
Param.CourseId to courseId.value,
Param.LessonId to lessonId.value,
Param.LessonName to lessonName,
Param.CourseId to args.courseId.value,
Param.LessonId to args.lessonId.value,
Param.LessonName to args.lessonName,
)
)
}
Expand All @@ -83,7 +82,7 @@ class LessonViewModel(
}

null, is Either.Left -> LessonViewState(
title = lessonName,
title = args.lessonName,
items = persistentListOf(),
cta = null,
progress = LessonProgressViewState(0, 1),
Expand All @@ -94,8 +93,8 @@ class LessonViewModel(

private suspend fun loadLesson() {
lessonResponse = repository.fetchLesson(
course = courseId,
lesson = lessonId
course = args.courseId,
lesson = args.lessonId
).onRight {
localState = LocalState(
selectedAnswers = it.progress.selectedAnswers,
Expand All @@ -115,11 +114,12 @@ class LessonViewModel(

screenScope.launch {
@Suppress("UNCHECKED_CAST")
val typedEventHandler = eventHandler as EventHandler<LessonViewEvent, LocalState>
val typedEventHandler = eventHandler as LessonEventHandler<LessonViewEvent>
with(typedEventHandler) { handleEvent(event) }
}
}

@Immutable
@optics
data class LocalState(
val selectedAnswers: Map<LessonItemId, Set<AnswerId>>,
Expand All @@ -138,6 +138,13 @@ class LessonViewModel(
)
}
}

data class Args(
val courseId: CourseId,
val lessonId: LessonId,
val lessonName: String,
)
}

typealias LessonVmContext = VmContext<LessonViewModel.LocalState>
typealias LessonVmContext = VmContext<LocalState, LessonViewModel.Args>
typealias LessonEventHandler<E> = EventHandler<E, LocalState, LessonViewModel.Args>
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package ui.screen.lesson.handler

import navigation.Navigation
import ui.EventHandler
import ui.screen.lesson.LessonEventHandler
import ui.screen.lesson.LessonViewEvent
import ui.screen.lesson.LessonViewModel
import ui.screen.lesson.LessonVmContext

class OnBackClickEventHandler(
private val navigation: Navigation
) : EventHandler<LessonViewEvent.OnBackClick, LessonViewModel.LocalState> {
) : LessonEventHandler<LessonViewEvent.OnBackClick> {
override val eventTypes = setOf(LessonViewEvent.OnBackClick::class)

override suspend fun LessonVmContext.handleEvent(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,13 @@ import arrow.optics.copy
import domain.SoundUseCase
import ivy.content.SoundsUrls
import ivy.model.ChoiceOptionId
import ui.EventHandler
import ui.screen.lesson.LessonViewEvent
import ui.screen.lesson.*
import ui.screen.lesson.LessonViewModel.LocalState
import ui.screen.lesson.LessonVmContext
import ui.screen.lesson.chosen
import ui.screen.lesson.completed
import ui.screen.lesson.mapper.toDomain

class OnChoiceClickEventHandler(
private val soundUseCase: SoundUseCase
) : EventHandler<LessonViewEvent.OnChoiceClick, LocalState> {
) : LessonEventHandler<LessonViewEvent.OnChoiceClick> {
override val eventTypes = setOf(LessonViewEvent.OnChoiceClick::class)

override suspend fun LessonVmContext.handleEvent(event: LessonViewEvent.OnChoiceClick) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package ui.screen.lesson.handler

import domain.SoundUseCase
import ivy.content.SoundsUrls
import ui.EventHandler
import ui.screen.lesson.LessonEventHandler
import ui.screen.lesson.LessonViewEvent
import ui.screen.lesson.LessonViewModel.LocalState
import ui.screen.lesson.LessonVmContext
Expand All @@ -11,7 +11,7 @@ import ui.screen.lesson.mapper.toDomain

class OnContinueClickEventHandler(
private val soundUseCase: SoundUseCase
) : EventHandler<LessonViewEvent.OnContinueClick, LocalState> {
) : LessonEventHandler<LessonViewEvent.OnContinueClick> {
override val eventTypes = setOf(LessonViewEvent.OnContinueClick::class)

override suspend fun LessonVmContext.handleEvent(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
package ui.screen.lesson.handler

import data.lesson.LessonRepository
import domain.SoundUseCase
import ivy.content.SoundsUrls
import navigation.Navigation
import ui.EventHandler
import ui.screen.lesson.LessonEventHandler
import ui.screen.lesson.LessonViewEvent
import ui.screen.lesson.LessonViewModel.LocalState
import ui.screen.lesson.LessonVmContext
import util.Logger

class OnFinishClickEventHandler(
private val navigation: Navigation,
private val soundUseCase: SoundUseCase
) :
EventHandler<LessonViewEvent.OnFinishClick, LocalState> {
private val soundUseCase: SoundUseCase,
private val lessonRepository: LessonRepository,
private val logger: Logger,
) : LessonEventHandler<LessonViewEvent.OnFinishClick> {
override val eventTypes = setOf(LessonViewEvent.OnFinishClick::class)

override suspend fun LessonVmContext.handleEvent(
event: LessonViewEvent.OnFinishClick
) {
navigation.navigateBack()
soundUseCase.playSound(SoundsUrls.CompleteLesson)
lessonRepository.markLessonAsCompleted(
course = args.courseId,
lesson = args.lessonId,
).onLeft { errMsg ->
logger.error("Failed to mark lesson as completed because: $errMsg")
}
}
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package ui.screen.lesson.handler

import domain.SoundUseCase
import ui.EventHandler
import ui.screen.lesson.LessonEventHandler
import ui.screen.lesson.LessonViewEvent
import ui.screen.lesson.LessonViewModel
import ui.screen.lesson.LessonVmContext

class OnSoundClickEventHandler(
private val soundUseCase: SoundUseCase
) : EventHandler<LessonViewEvent.OnSoundClick, LessonViewModel.LocalState> {
) : LessonEventHandler<LessonViewEvent.OnSoundClick> {
override val eventTypes = setOf(LessonViewEvent.OnSoundClick::class)

override suspend fun LessonVmContext.handleEvent(event: LessonViewEvent.OnSoundClick) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,15 @@ import arrow.optics.typeclasses.Index
import domain.SoundUseCase
import ivy.content.SoundsUrls
import ivy.model.AnswerId
import ui.EventHandler
import ui.screen.lesson.*
import ui.screen.lesson.LessonViewModel.LocalState
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.answered
import ui.screen.lesson.mapper.toDomain
import ui.screen.lesson.selectedAnswers

class QuestionEventHandler(
private val soundUseCase: SoundUseCase
) : EventHandler<QuestionViewEvent, LocalState> {
) : LessonEventHandler<QuestionViewEvent> {
override val eventTypes = setOf(
QuestionViewEvent.OnAnswerCheckChange::class,
QuestionViewEvent.OnCheckClick::class,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ class WebSystemNavigation : SystemNavigation {
}

override fun navigateBack(): Boolean {
if (window.window.length <= 1) return false
if (window.history.length <= 1) return false

window.history.back() // Let the browser handle back navigation
// No need to call emitCurrentRoute() here; the listener will handle it
Expand Down

0 comments on commit 21b88b3

Please sign in to comment.