From 2c64d784c31f042daebc9c82a0715b76828e47f9 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 14:38:34 +0300 Subject: [PATCH 01/11] Improve the LessonContent DSL --- .../programming/ProgrammingFundamentals.kt | 2 +- .../learn/data/cms/lesson/demo/DemoLesson.kt | 36 ++++++------- .../ProgrammingMathInDisguise.kt | 2 +- .../kotlin/ivy/model/dsl/LearnCmsDsl.kt | 41 +++++++++----- .../kotlin/ivy/model/dsl/LearnLessonDsl.kt | 54 ++++++++++++++----- 5 files changed, 89 insertions(+), 46 deletions(-) diff --git a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt index 9370ce4..82193d8 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt @@ -9,7 +9,7 @@ object ProgrammingFundamentals : CourseDsl({ imageUrl = "https://i.ibb.co/PTXn42F/fundamentals.webp" lesson( name = "Programming: Math in disguise", - tagline = "Why your calculator is more powerful than you think?", + tagline = "Mathematical functions and formulas can do much more than you think.", imageUrl = LessonImage ) }) \ No newline at end of file diff --git a/server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt b/server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt index 920abaf..7aee3e2 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt @@ -16,7 +16,9 @@ fun demoLesson() = lessonContent { text = "This is a demo lesson for testing purposes." style = TextStyle.Body } - image("img1", LessonImage) + image("img1") { + imageUrl = LessonImage + } question("q1") { question = "What is the answer to the ultimate question of life, the universe, and everything?" answer( @@ -44,22 +46,21 @@ fun demoLesson() = lessonContent { text = "Good job! You answered your first question correctly. Here's another question for you." style = TextStyle.Body } - sound( - id = "complete-sound", - text = "Complete Lesson sound", + sound("complete-sound") { + buttonText = "Complete Lesson sound" soundUrl = "https://github.com/ILIYANGERMANOV/ivy-resources/raw/master/ivy-learn/sounds/complete-lesson.mp3" - ) - sound( - id = "ups", - text = "Ups sound", + } + sound("ups") { + buttonText = "Ups sound" soundUrl = "https://github.com/ILIYANGERMANOV/ivy-resources/raw/master/ivy-learn/sounds/ups.wav" - ) - sound( - id = "success", - text = "Success sound", + } + sound("success") { + buttonText = "Success sound" soundUrl = "https://github.com/ILIYANGERMANOV/ivy-resources/raw/master/ivy-learn/sounds/success.mp3" - ) - image("img2", CourseImage) + } + image("img2") { + imageUrl = CourseImage + } textItem("content3") { text = "Congratulations! You're on fire!" style = TextStyle.Body @@ -69,11 +70,10 @@ fun demoLesson() = lessonContent { "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".repeat(3) style = TextStyle.Body } - lessonNavigation( - id = "next", - text = "Go to Question 1", + lessonNavigation("next") { + text = "Go to Question 1" onClick = LessonItemId("q1") - ) + } openQuestion("q2") { question = "What is the answer to the ultimate question of life, the universe, and everything?" correctAnswer = "42" diff --git a/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index e3a959d..8e86378 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -3,5 +3,5 @@ package ivy.learn.data.cms.lesson.programmingfundamentals import ivy.model.dsl.lessonContent fun programmingMathInDisguise() = lessonContent { - + lottie("") } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt index ee1ab2c..cd4e3bb 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt @@ -52,24 +52,16 @@ interface LessonContentScope { fun openQuestion(id: String, builder: OpenQuestionScope.() -> Unit) @LearnCmsDsl - fun lessonNavigation( - id: String, - text: String, - onClick: LessonItemId - ) + fun lessonNavigation(id: String, builder: LessonNavigationScope.() -> Unit) @LearnCmsDsl - fun link( - id: String, - text: String, - url: String - ) + fun link(id: String, builder: LinkScope.() -> Unit) @LearnCmsDsl - fun lottie(id: String, jsonUrl: String) + fun lottie(id: String, builder: LottieAnimationScope.() -> Unit) @LearnCmsDsl - fun image(id: String, imageUrl: String) + fun image(id: String, builder: ImageScope.() -> Unit) @LearnCmsDsl fun choice(id: String, builder: ChoiceScope.() -> Unit) @@ -78,7 +70,7 @@ interface LessonContentScope { fun mystery(id: String, builder: MysteryItemScope.() -> Unit) @LearnCmsDsl - fun sound(id: String, text: String, soundUrl: String) + fun sound(id: String, builder: SoundScope.() -> Unit) fun build(): LessonContent } @@ -120,5 +112,28 @@ interface MysteryItemScope { fun hiddenItemId(item: LessonItemId) } +interface LottieAnimationScope { + var jsonUrl: String +} + +interface ImageScope { + var imageUrl: String +} + +interface SoundScope { + var soundUrl: String + var buttonText: String +} + +interface LessonNavigationScope { + var text: String + var onClick: LessonItemId +} + +interface LinkScope { + var text: String + var url: String +} + @DslMarker annotation class LearnCmsDsl \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt index 8a54aea..08f1ab9 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt @@ -49,36 +49,40 @@ class LessonContentScopeImpl : LessonContentScope { ) } - override fun lessonNavigation(id: String, text: String, onClick: LessonItemId) { + override fun lessonNavigation(id: String, builder: LessonNavigationScope.() -> Unit) { + val scope = LessonNavigationScopeImpl().also(builder) items[chain(id)] = LessonNavigationItem( id = LessonItemId(id), - text = text, - onClick = onClick, + text = scope.text, + onClick = scope.onClick, next = null, ) } - override fun link(id: String, text: String, url: String) { + override fun link(id: String, builder: LinkScope.() -> Unit) { + val scope = LinkScopeImpl().also(builder) items[chain(id)] = LinkItem( id = LessonItemId(id), - text = text, - url = url, + text = scope.text, + url = scope.url, next = null, ) } - override fun lottie(id: String, jsonUrl: String) { + override fun lottie(id: String, builder: LottieAnimationScope.() -> Unit) { + val scope = LottieAnimationScopeImpl().also(builder) items[chain(id)] = LottieAnimationItem( id = LessonItemId(id), - lottie = LottieAnimation(url = jsonUrl), + lottie = LottieAnimation(url = scope.jsonUrl), next = null, ) } - override fun image(id: String, imageUrl: String) { + override fun image(id: String, builder: ImageScope.() -> Unit) { + val scope = ImageScopeImpl().also(builder) items[chain(id)] = ImageItem( id = LessonItemId(id), - image = ImageUrl(imageUrl), + image = ImageUrl(scope.imageUrl), next = null, ) } @@ -111,11 +115,12 @@ class LessonContentScopeImpl : LessonContentScope { ) } - override fun sound(id: String, text: String, soundUrl: String) { + override fun sound(id: String, builder: SoundScope.() -> Unit) { + val scope = SoundScopeImpl().also(builder) items[chain(id)] = SoundItem( id = LessonItemId(id), - text = text, - sound = SoundUrl(soundUrl), + text = scope.buttonText, + sound = SoundUrl(scope.soundUrl), next = null, ) } @@ -213,4 +218,27 @@ class MysteryItemScopeImpl : MysteryItemScope { override fun hiddenItemId(item: LessonItemId) { hiddenItemId = item } +} + +class LottieAnimationScopeImpl : LottieAnimationScope { + override var jsonUrl: String = "" +} + +class ImageScopeImpl : ImageScope { + override var imageUrl: String = "" +} + +class LessonNavigationScopeImpl : LessonNavigationScope { + override var text: String = "" + override var onClick: LessonItemId = LessonItemId("") +} + +class SoundScopeImpl : SoundScope { + override var soundUrl: String = "" + override var buttonText: String = "" +} + +class LinkScopeImpl : LinkScope { + override var text: String = "" + override var url: String = "" } \ No newline at end of file From 7ca0c693371013eee59fecf1eac06a101e892929 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 15:09:08 +0300 Subject: [PATCH 02/11] WIP: Lesson + fix stuff --- .../kotlin/data/LessonRepository.kt | 17 ++++++++++- .../ui/screen/lesson/composable/CtaBar.kt | 2 +- .../handler/OnFinishClickEventHandler.kt | 6 ++-- .../ivy/learn/data/cms/dsl/CourseDsl.kt | 2 ++ .../ivy/learn/data/cms/dsl/TopicsDsl.kt | 1 + .../ProgrammingMathInDisguise.kt | 7 ----- .../commonMain/kotlin/ivy/content}/CmsUtil.kt | 2 +- .../ivy/content}/lesson/demo/DemoLesson.kt | 16 +++++----- .../ProgrammingMathInDisguise.kt | 30 +++++++++++++++++++ .../kotlin/ivy/model/dsl/LearnCmsDsl.kt | 2 +- .../kotlin/ivy/model/dsl/LearnLessonDsl.kt | 2 +- 11 files changed, 64 insertions(+), 23 deletions(-) delete mode 100644 server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt rename {server/src/main/kotlin/ivy/learn/data/cms/dsl => shared/src/commonMain/kotlin/ivy/content}/CmsUtil.kt (92%) rename {server/src/main/kotlin/ivy/learn/data/cms => shared/src/commonMain/kotlin/ivy/content}/lesson/demo/DemoLesson.kt (92%) create mode 100644 shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt diff --git a/composeApp/src/commonMain/kotlin/data/LessonRepository.kt b/composeApp/src/commonMain/kotlin/data/LessonRepository.kt index a64f769..395b09a 100644 --- a/composeApp/src/commonMain/kotlin/data/LessonRepository.kt +++ b/composeApp/src/commonMain/kotlin/data/LessonRepository.kt @@ -1,8 +1,11 @@ package data import arrow.core.Either +import arrow.core.right import ivy.data.source.LessonDataSource +import ivy.learn.data.cms.lesson.programmingfundamentals.programmingMathInDisguise import ivy.model.CourseId +import ivy.model.ImageUrl import ivy.model.Lesson import ivy.model.LessonId import kotlinx.coroutines.withContext @@ -12,10 +15,22 @@ class LessonRepository( private val dispatchers: DispatchersProvider, private val datasource: LessonDataSource, ) { + private val fakeLessonEnabled = true + suspend fun fetchLesson( course: CourseId, lesson: LessonId ): Either = withContext(dispatchers.io) { - datasource.fetchLesson(course, lesson) + fakeLesson()?.right() ?: datasource.fetchLesson(course, lesson) } + + private fun fakeLesson(): Lesson? = if (fakeLessonEnabled) { + Lesson( + id = LessonId("fake"), + name = "Programming: Math in disguise", + tagline = "", + image = ImageUrl(""), + content = programmingMathInDisguise(), + ) + } else null } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/CtaBar.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/CtaBar.kt index 0248f91..3bbfe1c 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/CtaBar.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/CtaBar.kt @@ -57,7 +57,7 @@ private fun FinishButton( ) { CtaButton( modifier = modifier, - text = "FINISH!", + text = "COMPLETE LESSON", onClick = onClick, ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnFinishClickEventHandler.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnFinishClickEventHandler.kt index 5267311..748a47e 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnFinishClickEventHandler.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnFinishClickEventHandler.kt @@ -12,11 +12,11 @@ class OnFinishClickEventHandler( private val platform: Platform, private val navigation: Navigation, ) : - EventHandler { - override val eventTypes = setOf(LessonViewEvent.OnContinueClick::class) + EventHandler { + override val eventTypes = setOf(LessonViewEvent.OnFinishClick::class) override suspend fun LessonVmContext.handleEvent( - event: LessonViewEvent.OnContinueClick + event: LessonViewEvent.OnFinishClick ) { navigation.back() platform.playSound(SoundsUrls.CompleteLesson) diff --git a/server/src/main/kotlin/ivy/learn/data/cms/dsl/CourseDsl.kt b/server/src/main/kotlin/ivy/learn/data/cms/dsl/CourseDsl.kt index 9c0e4ea..2a15888 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/dsl/CourseDsl.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/dsl/CourseDsl.kt @@ -1,5 +1,7 @@ package ivy.learn.data.cms.dsl +import ivy.content.EmptyContent +import ivy.content.nameToId import ivy.model.* import ivy.model.dsl.LearnCmsDsl diff --git a/server/src/main/kotlin/ivy/learn/data/cms/dsl/TopicsDsl.kt b/server/src/main/kotlin/ivy/learn/data/cms/dsl/TopicsDsl.kt index 14711c4..b1c9b7c 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/dsl/TopicsDsl.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/dsl/TopicsDsl.kt @@ -1,5 +1,6 @@ package ivy.learn.data.cms.dsl +import ivy.content.nameToId import ivy.model.CourseId import ivy.model.Topic import ivy.model.TopicId diff --git a/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt deleted file mode 100644 index 8e86378..0000000 --- a/server/src/main/kotlin/ivy/learn/data/cms/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ /dev/null @@ -1,7 +0,0 @@ -package ivy.learn.data.cms.lesson.programmingfundamentals - -import ivy.model.dsl.lessonContent - -fun programmingMathInDisguise() = lessonContent { - lottie("") -} \ No newline at end of file diff --git a/server/src/main/kotlin/ivy/learn/data/cms/dsl/CmsUtil.kt b/shared/src/commonMain/kotlin/ivy/content/CmsUtil.kt similarity index 92% rename from server/src/main/kotlin/ivy/learn/data/cms/dsl/CmsUtil.kt rename to shared/src/commonMain/kotlin/ivy/content/CmsUtil.kt index 05923c3..f90625e 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/dsl/CmsUtil.kt +++ b/shared/src/commonMain/kotlin/ivy/content/CmsUtil.kt @@ -1,4 +1,4 @@ -package ivy.learn.data.cms.dsl +package ivy.content import ivy.model.LessonContent import ivy.model.LessonItemId diff --git a/server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt similarity index 92% rename from server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt rename to shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt index 7aee3e2..8dc5feb 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/lesson/demo/DemoLesson.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt @@ -1,18 +1,18 @@ package ivy.learn.data.cms.lesson.demo -import ivy.learn.data.cms.dsl.CourseImage -import ivy.learn.data.cms.dsl.LessonImage +import ivy.content.CourseImage +import ivy.content.LessonImage import ivy.model.LessonItemId import ivy.model.TextStyle import ivy.model.dsl.lessonContent import ivy.model.dsl.printLessonJson fun demoLesson() = lessonContent { - textItem("title") { + text("title") { text = "Demo Lesson" style = TextStyle.Heading } - textItem("content") { + text("content") { text = "This is a demo lesson for testing purposes." style = TextStyle.Body } @@ -42,7 +42,7 @@ fun demoLesson() = lessonContent { explanation = "1 is not the answer to the ultimate question of life, the universe, and everything." ) } - textItem("content2") { + text("content2") { text = "Good job! You answered your first question correctly. Here's another question for you." style = TextStyle.Body } @@ -61,11 +61,11 @@ fun demoLesson() = lessonContent { image("img2") { imageUrl = CourseImage } - textItem("content3") { + text("content3") { text = "Congratulations! You're on fire!" style = TextStyle.Body } - textItem("content4") { + text("content4") { text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.".repeat(3) style = TextStyle.Body @@ -78,7 +78,7 @@ fun demoLesson() = lessonContent { question = "What is the answer to the ultimate question of life, the universe, and everything?" correctAnswer = "42" } - textItem("end") { + text("end") { text = "End of demo lesson." style = TextStyle.Body } diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt new file mode 100644 index 0000000..f5a7e8b --- /dev/null +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -0,0 +1,30 @@ +package ivy.learn.data.cms.lesson.programmingfundamentals + +import ivy.model.TextStyle +import ivy.model.dsl.lessonContent +import ivy.model.dsl.printLessonJson + +fun programmingMathInDisguise() = lessonContent { + lottie("harry_hi_anim") { + jsonUrl = "tbd" + } + text("harry_hi") { + text = "Meet Harry, a shape-shifting alien from the KX-147 planet. " + + "Harry is a gifted scientist who can solve complex problems in a simple and elegant way. " + + "He's also quite impatient and tends to test his ideas in Friday on production." + style = TextStyle.Body + } + lottie("harry_crash_anim") { + jsonUrl = "tbf" + } + text("harry_crash") { + text = "Unfortunately, during one of his time-traveling experiments, Harry crashed his spaceship on Earth. " + + "He needs your help to fix it and get back home. " + + "Harry has a plan, but he needs your knowledge and help to fix his ship." + } +} + +fun main() { + val lesson = programmingMathInDisguise() + printLessonJson(lesson) +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt index cd4e3bb..0b40ee4 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt @@ -43,7 +43,7 @@ private fun allItemsIds( interface LessonContentScope { @LearnCmsDsl - fun textItem(id: String, builder: TextScope.() -> Unit) + fun text(id: String, builder: TextScope.() -> Unit) @LearnCmsDsl fun question(id: String, builder: QuestionScope.() -> Unit) diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt index 08f1ab9..919b066 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt @@ -7,7 +7,7 @@ class LessonContentScopeImpl : LessonContentScope { private var currentItem: LessonItemId? = null private val items = mutableMapOf() - override fun textItem(id: String, builder: TextScope.() -> Unit) { + override fun text(id: String, builder: TextScope.() -> Unit) { val scope = TextScopeImpl().also(builder) items[chain(id)] = TextItem( id = LessonItemId(id), From 24479f70f81a40b213050b1e21fc6e0d5f061251 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 15:37:33 +0300 Subject: [PATCH 03/11] WIP: First lesson --- ...ieAnimation.kt => LocalLottieAnimation.kt} | 27 ++++++++++++++- .../kotlin/data/LessonRepository.kt | 2 +- .../screen/intro/composable/IntroContent.kt | 4 +-- .../screen/lesson/composable/LessonContent.kt | 18 +++++----- .../item/LottieAnimationLessonItem.kt | 20 +++++++++++ .../screen/lesson/mapper/LessonTreeManager.kt | 2 +- .../learn/data/cms/course/demo/DemoCourse.kt | 4 +-- .../cms/course/programming/DataStructures.kt | 2 +- .../programming/FunctionalProgramming.kt | 4 +-- .../programming/ObjectOrientedProgramming.kt | 4 +-- .../programming/ProgrammingFundamentals.kt | 2 +- .../kotlin/ivy/content/LottieUrls.kt | 9 +++++ .../ivy/content/lesson/demo/DemoLesson.kt | 2 +- .../ProgrammingMathInDisguise.kt | 25 +++++++++----- .../kotlin/ivy/data/LottieAnimationLoader.kt | 19 +++++++++++ .../commonMain/kotlin/ivy/di/SharedModule.kt | 2 ++ .../kotlin/ivy/model/dsl/LearnCmsDsl.kt | 33 ++++++++++++++++++- 17 files changed, 146 insertions(+), 33 deletions(-) rename composeApp/src/commonMain/kotlin/component/{LearnLottieAnimation.kt => LocalLottieAnimation.kt} (60%) create mode 100644 composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt create mode 100644 shared/src/commonMain/kotlin/ivy/content/LottieUrls.kt create mode 100644 shared/src/commonMain/kotlin/ivy/data/LottieAnimationLoader.kt diff --git a/composeApp/src/commonMain/kotlin/component/LearnLottieAnimation.kt b/composeApp/src/commonMain/kotlin/component/LocalLottieAnimation.kt similarity index 60% rename from composeApp/src/commonMain/kotlin/component/LearnLottieAnimation.kt rename to composeApp/src/commonMain/kotlin/component/LocalLottieAnimation.kt index 66bdafa..61d4699 100644 --- a/composeApp/src/commonMain/kotlin/component/LearnLottieAnimation.kt +++ b/composeApp/src/commonMain/kotlin/component/LocalLottieAnimation.kt @@ -5,6 +5,8 @@ import androidx.compose.ui.Modifier import io.github.alexzhirkevich.compottie.LottieAnimation import io.github.alexzhirkevich.compottie.LottieCompositionSpec import io.github.alexzhirkevich.compottie.rememberLottieComposition +import ivy.data.LottieAnimationLoader +import ivy.di.Di import learn.composeapp.generated.resources.Res import org.jetbrains.compose.resources.ExperimentalResourceApi @@ -14,7 +16,7 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi */ @OptIn(ExperimentalResourceApi::class) @Composable -fun LearnLottieAnimation( +fun LocalLottieAnimation( animationFile: String, repeat: Boolean, modifier: Modifier = Modifier, @@ -31,4 +33,27 @@ fun LearnLottieAnimation( iterations = if (repeat) Int.MAX_VALUE else 1, ) } +} + +@Composable +fun RemoteLottieAnimation( + animationUrl: String, + repeat: Boolean, + modifier: Modifier = Modifier, +) { + var lottieJson by remember { mutableStateOf(null) } + LaunchedEffect(Unit) { + Di.get().loadJson(animationUrl) + .onRight { json -> + lottieJson = json + } + } + lottieJson?.let { + val composition by rememberLottieComposition(LottieCompositionSpec.JsonString(it)) + LottieAnimation( + modifier = modifier, + composition = composition, + iterations = if (repeat) Int.MAX_VALUE else 1, + ) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/data/LessonRepository.kt b/composeApp/src/commonMain/kotlin/data/LessonRepository.kt index 395b09a..66127a8 100644 --- a/composeApp/src/commonMain/kotlin/data/LessonRepository.kt +++ b/composeApp/src/commonMain/kotlin/data/LessonRepository.kt @@ -2,8 +2,8 @@ package data import arrow.core.Either import arrow.core.right +import ivy.content.lesson.programmingfundamentals.programmingMathInDisguise import ivy.data.source.LessonDataSource -import ivy.learn.data.cms.lesson.programmingfundamentals.programmingMathInDisguise import ivy.model.CourseId import ivy.model.ImageUrl import ivy.model.Lesson diff --git a/composeApp/src/commonMain/kotlin/ui/screen/intro/composable/IntroContent.kt b/composeApp/src/commonMain/kotlin/ui/screen/intro/composable/IntroContent.kt index 7eb2d48..39b7eb7 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/intro/composable/IntroContent.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/intro/composable/IntroContent.kt @@ -6,7 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import component.LearnLottieAnimation +import component.LocalLottieAnimation import component.button.CtaButton import component.platformHorizontalPadding import component.text.Headline @@ -31,7 +31,7 @@ fun IntroContent( verticalArrangement = Arrangement.Center ) { Spacer(modifier = Modifier.weight(1f)) - LearnLottieAnimation( + LocalLottieAnimation( modifier = Modifier.size(400.dp), animationFile = "intro_lottie_anim.json", repeat = true diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt index 4154930..a974cc6 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt @@ -14,12 +14,10 @@ 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.lesson.* -import ui.screen.lesson.composable.item.ImageLessonItem -import ui.screen.lesson.composable.item.QuestionLessonItem -import ui.screen.lesson.composable.item.SoundLessonItem -import ui.screen.lesson.composable.item.TextLessonItem +import ui.screen.lesson.composable.item.* val ItemSpacing = 12.dp val ItemSpacingBig = 16.dp @@ -65,7 +63,11 @@ private fun LessonItemsLazyColum( onEvent: (LessonViewEvent) -> Unit, modifier: Modifier = Modifier, ) { - val horizontalPadding = platformHorizontalPadding() + val horizontalPadding = when (screenType()) { + ScreenType.Mobile -> 16.dp + ScreenType.Tablet -> 24.dp + ScreenType.Desktop -> 64.dp + } val listState = rememberLazyListState() LaunchedEffect(viewState.items.size) { @@ -106,9 +108,7 @@ private fun LessonItemsLazyColum( // TODO } - is LottieAnimationItemViewState -> { - // TODO - } + is LottieAnimationItemViewState -> LottieAnimationLessonItem(itemViewState) is MysteryItemViewState -> { // TODO diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt new file mode 100644 index 0000000..eadaaed --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt @@ -0,0 +1,20 @@ +package ui.screen.lesson.composable.item + +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 + +@Composable +fun LottieAnimationLessonItem( + viewState: LottieAnimationItemViewState, + modifier: Modifier = Modifier, +) { + RemoteLottieAnimation( + modifier = modifier.size(232.dp), + animationUrl = viewState.lottieUrl, + repeat = true, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt index 2ad7b72..a335f0d 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt @@ -31,7 +31,7 @@ class LessonTreeManager { currentItemId in localState.completed } - is SoundItem -> true + is SoundItem, is LottieAnimationItem -> true else -> { currentItemId in localState.completed || autoLoadNextN > 0 diff --git a/server/src/main/kotlin/ivy/learn/data/cms/course/demo/DemoCourse.kt b/server/src/main/kotlin/ivy/learn/data/cms/course/demo/DemoCourse.kt index 364f2f9..d806d25 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/course/demo/DemoCourse.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/course/demo/DemoCourse.kt @@ -1,8 +1,8 @@ package ivy.learn.data.cms.course.demo +import ivy.content.CourseImage +import ivy.content.LessonImage import ivy.learn.data.cms.dsl.CourseDsl -import ivy.learn.data.cms.dsl.CourseImage -import ivy.learn.data.cms.dsl.LessonImage object DemoCourse : CourseDsl({ name = "Demo Course" diff --git a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/DataStructures.kt b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/DataStructures.kt index 3af1d72..6f1e1eb 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/DataStructures.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/DataStructures.kt @@ -1,7 +1,7 @@ package ivy.learn.data.cms.course.programming +import ivy.content.LessonImage import ivy.learn.data.cms.dsl.CourseDsl -import ivy.learn.data.cms.dsl.LessonImage object DataStructures : CourseDsl({ name = "Data Structures" diff --git a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/FunctionalProgramming.kt b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/FunctionalProgramming.kt index ae35169..db6d689 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/FunctionalProgramming.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/FunctionalProgramming.kt @@ -1,8 +1,8 @@ package ivy.learn.data.cms.course.programming +import ivy.content.LessonImage +import ivy.content.LessonImage2 import ivy.learn.data.cms.dsl.CourseDsl -import ivy.learn.data.cms.dsl.LessonImage -import ivy.learn.data.cms.dsl.LessonImage2 object FunctionalProgramming : CourseDsl({ name = "Functional Programming (FP)" diff --git a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ObjectOrientedProgramming.kt b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ObjectOrientedProgramming.kt index bc0efc3..900db6b 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ObjectOrientedProgramming.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ObjectOrientedProgramming.kt @@ -1,8 +1,8 @@ package ivy.learn.data.cms.course.programming +import ivy.content.LessonImage +import ivy.content.LessonImage2 import ivy.learn.data.cms.dsl.CourseDsl -import ivy.learn.data.cms.dsl.LessonImage -import ivy.learn.data.cms.dsl.LessonImage2 object ObjectOrientedProgramming : CourseDsl({ name = "Object-Oriented Programming (OOP)" diff --git a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt index 82193d8..16fe765 100644 --- a/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt +++ b/server/src/main/kotlin/ivy/learn/data/cms/course/programming/ProgrammingFundamentals.kt @@ -1,7 +1,7 @@ package ivy.learn.data.cms.course.programming +import ivy.content.LessonImage import ivy.learn.data.cms.dsl.CourseDsl -import ivy.learn.data.cms.dsl.LessonImage object ProgrammingFundamentals : CourseDsl({ name = "Programming Fundamentals" diff --git a/shared/src/commonMain/kotlin/ivy/content/LottieUrls.kt b/shared/src/commonMain/kotlin/ivy/content/LottieUrls.kt new file mode 100644 index 0000000..beb2f67 --- /dev/null +++ b/shared/src/commonMain/kotlin/ivy/content/LottieUrls.kt @@ -0,0 +1,9 @@ +package ivy.content + +object LottieUrls { + val HarrySunglases = lottieJsonUrl("harry-hi-v1") + val SpaceshipFly = lottieJsonUrl("spaceship-fly") + + fun lottieJsonUrl(animationName: String): String = + "https://raw.githubusercontent.com/ILIYANGERMANOV/ivy-resources/master/ivy-learn/lottie/$animationName.json" +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt index 8dc5feb..a7d93e2 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/demo/DemoLesson.kt @@ -1,4 +1,4 @@ -package ivy.learn.data.cms.lesson.demo +package ivy.content.lesson.demo import ivy.content.CourseImage import ivy.content.LessonImage diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index f5a7e8b..527495b 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -1,26 +1,33 @@ -package ivy.learn.data.cms.lesson.programmingfundamentals +package ivy.content.lesson.programmingfundamentals +import ivy.content.LottieUrls import ivy.model.TextStyle import ivy.model.dsl.lessonContent import ivy.model.dsl.printLessonJson +import ivy.model.dsl.textBuilder fun programmingMathInDisguise() = lessonContent { lottie("harry_hi_anim") { - jsonUrl = "tbd" + jsonUrl = LottieUrls.HarrySunglases } text("harry_hi") { - text = "Meet Harry, a shape-shifting alien from the KX-147 planet. " + - "Harry is a gifted scientist who can solve complex problems in a simple and elegant way. " + - "He's also quite impatient and tends to test his ideas in Friday on production." + text = textBuilder { + line("Meet Harry, a shape-shifting alien from the KX-147 planet.") + line("Harry is a gifted scientist who can solve complex problems in a simple and elegant way.") + line("He's also quite impatient and tends to test his ideas in Friday on production.") + } style = TextStyle.Body } lottie("harry_crash_anim") { - jsonUrl = "tbf" + jsonUrl = LottieUrls.SpaceshipFly } text("harry_crash") { - text = "Unfortunately, during one of his time-traveling experiments, Harry crashed his spaceship on Earth. " + - "He needs your help to fix it and get back home. " + - "Harry has a plan, but he needs your knowledge and help to fix his ship." + text = textBuilder { + line("Unfortunately, during one of his time-traveling experiments... ") + line("Harry crashed his spaceship on Earth. (Oops!)") + line("Now he needs your help to fix it and get back home.") + line("But don't worry, Harry has a plan!") + } } } diff --git a/shared/src/commonMain/kotlin/ivy/data/LottieAnimationLoader.kt b/shared/src/commonMain/kotlin/ivy/data/LottieAnimationLoader.kt new file mode 100644 index 0000000..f6672b7 --- /dev/null +++ b/shared/src/commonMain/kotlin/ivy/data/LottieAnimationLoader.kt @@ -0,0 +1,19 @@ +package ivy.data + +import arrow.core.Either +import arrow.core.left +import arrow.core.raise.catch +import arrow.core.right +import io.ktor.client.* +import io.ktor.client.call.* +import io.ktor.client.request.* + +class LottieAnimationLoader( + private val httpClient: HttpClient +) { + suspend fun loadJson(url: String): Either = catch({ + httpClient.get(url).body().right() + }) { + "Failed to load Lottie animation from '$url'.".left() + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/ivy/di/SharedModule.kt b/shared/src/commonMain/kotlin/ivy/di/SharedModule.kt index 76d4cae..c134770 100644 --- a/shared/src/commonMain/kotlin/ivy/di/SharedModule.kt +++ b/shared/src/commonMain/kotlin/ivy/di/SharedModule.kt @@ -7,6 +7,7 @@ import io.ktor.client.plugins.logging.* import io.ktor.http.* import io.ktor.serialization.kotlinx.json.* import ivy.data.HerokuServerUrlProvider +import ivy.data.LottieAnimationLoader import ivy.data.ServerUrlProvider import ivy.data.source.CoursesDataSource import ivy.data.source.LessonDataSource @@ -31,6 +32,7 @@ object SharedModule : DiModule { register { LessonDataSource(Di.get(), Di.get()) } register { TopicsDataSource(Di.get(), Di.get()) } register { CoursesDataSource(Di.get(), Di.get()) } + register { LottieAnimationLoader(Di.get()) } } private fun Di.DiScope.json() = singleton { diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt index 0b40ee4..e03910c 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt @@ -136,4 +136,35 @@ interface LinkScope { } @DslMarker -annotation class LearnCmsDsl \ No newline at end of file +annotation class LearnCmsDsl + +@TextBuilderDsl +fun textBuilder(builder: TextBuilderScope.() -> Unit): String { + val scope = TextBuilder().apply(builder) + return scope.build() +} + +interface TextBuilderScope { + @TextBuilderDsl + fun line(text: String) + + fun newLine() +} + +class TextBuilder : TextBuilderScope { + private val lines = mutableListOf() + + override fun line(text: String) { + lines += text + } + + override fun newLine() { + lines += "" + } + + fun build(): String = lines.joinToString("\n") +} + +@DslMarker +annotation class TextBuilderDsl + From bdf27da292bb8a62d3ca4a0d1f1d153354f9a970 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 15:56:08 +0300 Subject: [PATCH 04/11] WIP: Lesson 1 --- .../commonMain/kotlin/component/text/Body.kt | 19 +++++++++++++++++ .../screen/lesson/composable/LessonContent.kt | 2 +- .../item/LottieAnimationLessonItem.kt | 6 +++++- .../lesson/composable/item/TextLessonItem.kt | 8 ++++--- .../screen/lesson/mapper/LessonTreeManager.kt | 4 +++- .../ProgrammingMathInDisguise.kt | 21 +++++++++++++++++-- 6 files changed, 52 insertions(+), 8 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/component/text/Body.kt b/composeApp/src/commonMain/kotlin/component/text/Body.kt index 115b978..832fa81 100644 --- a/composeApp/src/commonMain/kotlin/component/text/Body.kt +++ b/composeApp/src/commonMain/kotlin/component/text/Body.kt @@ -5,6 +5,25 @@ import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.sp + +@Composable +fun BodyBig( + text: String, + modifier: Modifier = Modifier, + textAlign: TextAlign = TextAlign.Start, +) { + Text( + modifier = modifier, + text = text, + style = MaterialTheme.typography.body1.copy( + fontSize = 18.sp + ), + textAlign = textAlign, + ) +} + @Composable fun Body( diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt index a974cc6..a5f07ee 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt @@ -20,7 +20,7 @@ import ui.screen.lesson.* import ui.screen.lesson.composable.item.* val ItemSpacing = 12.dp -val ItemSpacingBig = 16.dp +val ItemSpacingBig = 20.dp @Composable fun LessonContent( diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt index eadaaed..da13b56 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/LottieAnimationLessonItem.kt @@ -1,11 +1,13 @@ package ui.screen.lesson.composable.item +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 @Composable fun LottieAnimationLessonItem( @@ -13,7 +15,9 @@ fun LottieAnimationLessonItem( modifier: Modifier = Modifier, ) { RemoteLottieAnimation( - modifier = modifier.size(232.dp), + modifier = modifier + .padding(ItemSpacing) + .size(232.dp), animationUrl = viewState.lottieUrl, repeat = true, ) diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt index 960c6df..7c76e13 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt @@ -3,7 +3,8 @@ package ui.screen.lesson.composable.item import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import component.text.Body +import androidx.compose.ui.text.style.TextAlign +import component.text.BodyBig import component.text.HeadlineSmall import ui.screen.lesson.TextItemViewState import ui.screen.lesson.TextStyleViewState @@ -21,9 +22,10 @@ fun TextLessonItem( text = viewState.text ) - TextStyleViewState.Body -> Body( + TextStyleViewState.Body -> BodyBig( modifier = modifier.padding(top = ItemSpacing), - text = viewState.text + text = viewState.text, + textAlign = TextAlign.Center, ) } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt index a335f0d..217309b 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt @@ -34,7 +34,9 @@ class LessonTreeManager { is SoundItem, is LottieAnimationItem -> true else -> { - currentItemId in localState.completed || autoLoadNextN > 0 + currentItemId in localState.completed || + autoLoadNextN > 0 || + (currentItem is TextItem && currentItem.style == TextStyle.Heading) } } }?.let { diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index 527495b..dd01681 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -6,15 +6,21 @@ import ivy.model.dsl.lessonContent import ivy.model.dsl.printLessonJson import ivy.model.dsl.textBuilder +const val HarryPlanet = "KX-147" + fun programmingMathInDisguise() = lessonContent { + text("meet_harry") { + text = "Meet Harry!" + style = TextStyle.Heading + } lottie("harry_hi_anim") { jsonUrl = LottieUrls.HarrySunglases } text("harry_hi") { text = textBuilder { - line("Meet Harry, a shape-shifting alien from the KX-147 planet.") + line("Harry is a shape-shifting alien from the $HarryPlanet planet.") line("Harry is a gifted scientist who can solve complex problems in a simple and elegant way.") - line("He's also quite impatient and tends to test his ideas in Friday on production.") + line("He's also quite impatient and tends to test his ideas at Friday on production.") } style = TextStyle.Body } @@ -29,6 +35,17 @@ fun programmingMathInDisguise() = lessonContent { line("But don't worry, Harry has a plan!") } } + text("chapter_1_title") { + text = "Chapter 1: Repairing the Spaceship" + style = TextStyle.Heading + } + text("chater_1_intro") { + text = textBuilder { + line("Harry planned to use his superior math and science skills.") + line("To repair his warp drive, Harry needs exactly 3141.59 grams of gold.") + line("However, it turned out that things on Earth are different from his $HarryPlanet home planet...") + } + } } fun main() { From 77abdae3f2e42a064bf3f12221c7834de9341f53 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 17:00:14 +0300 Subject: [PATCH 05/11] WIP: Support choice --- .../kotlin/ui/screen/lesson/LessonScreen.kt | 2 + .../ui/screen/lesson/LessonViewState.kt | 8 +++- .../screen/lesson/composable/LessonContent.kt | 18 +++++--- .../composable/item/ChoiceLessonItem.kt | 42 +++++++++++++++++++ .../handler/OnChoiceClickEventHandler.kt | 26 ++++++++++++ .../handler/QuestionViewEventHandler.kt | 12 +++--- .../ProgrammingMathInDisguise.kt | 35 +++++++++++++++- .../kotlin/ivy/model/dsl/LearnCmsDsl.kt | 3 +- .../kotlin/ivy/model/dsl/LearnLessonDsl.kt | 6 +-- 9 files changed, 130 insertions(+), 22 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt create mode 100644 composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt index 5d138d1..6c34a5b 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt @@ -26,6 +26,7 @@ class LessonScreen( register { QuestionViewEventHandler(Di.get()) } register { OnSoundClickEventHandler(Di.get()) } register { OnFinishClickEventHandler(Di.get(), Di.get()) } + register { OnChoiceClickEventHandler() } register { LessonViewModel( courseId = courseId, @@ -40,6 +41,7 @@ class LessonScreen( Di.get(), Di.get(), Di.get(), + Di.get(), ) ) } diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt index 7c6ac08..b8e05b6 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt @@ -130,19 +130,23 @@ sealed interface LessonViewEvent { data class OnContinueClick(val currentItemId: LessonItemIdViewState) : LessonViewEvent data class OnSoundClick(val soundUrl: String) : LessonViewEvent data class OnFinishClick(val currentItemId: LessonItemIdViewState) : LessonViewEvent + data class OnChoiceClick( + val questionId: LessonItemIdViewState, + val choiceId: String + ) : LessonViewEvent } sealed interface QuestionViewEvent : LessonViewEvent { val questionId: LessonItemIdViewState - data class AnswerCheckChange( + data class OnAnswerCheckChange( override val questionId: LessonItemIdViewState, val questionType: QuestionTypeViewState, val answerId: String, val checked: Boolean ) : QuestionViewEvent - data class CheckClick( + data class OnCheckClick( override val questionId: LessonItemIdViewState, val answers: List, ) : QuestionViewEvent diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt index a5f07ee..4319d0d 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt @@ -94,9 +94,17 @@ private fun LessonItemsLazyColum( } ) { itemViewState -> when (itemViewState) { - is ChoiceItemViewState -> { - // TODO - } + is ChoiceItemViewState -> ChoiceLessonItem( + viewState = itemViewState, + onChoiceClick = { choiceOptionViewState -> + onEvent( + LessonViewEvent.OnChoiceClick( + questionId = itemViewState.id, + choiceId = choiceOptionViewState.id + ) + ) + } + ) is ImageItemViewState -> ImageLessonItem(itemViewState) @@ -122,7 +130,7 @@ private fun LessonItemsLazyColum( viewState = itemViewState, onAnswerCheckChange = { type, answerViewState, checked -> onEvent( - QuestionViewEvent.AnswerCheckChange( + QuestionViewEvent.OnAnswerCheckChange( questionId = itemViewState.id, questionType = type, answerId = answerViewState.id, @@ -131,7 +139,7 @@ private fun LessonItemsLazyColum( ) }, onCheckClick = { answers -> - onEvent(QuestionViewEvent.CheckClick(itemViewState.id, answers)) + onEvent(QuestionViewEvent.OnCheckClick(itemViewState.id, answers)) } ) diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt new file mode 100644 index 0000000..ae7ad30 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt @@ -0,0 +1,42 @@ +package ui.screen.lesson.composable.item + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import component.text.SubTitle +import ui.screen.lesson.ChoiceItemViewState +import ui.screen.lesson.ChoiceOptionViewState +import ui.screen.lesson.composable.ItemSpacingBig + +@Composable +fun ChoiceLessonItem( + viewState: ChoiceItemViewState, + onChoiceClick: (ChoiceOptionViewState) -> Unit, + modifier: Modifier = Modifier, +) { + Card( + modifier = modifier.padding(top = ItemSpacingBig), + shape = RoundedCornerShape(16.dp), + elevation = 4.dp + ) { + Column(modifier = Modifier.padding(16.dp)) { + QuestionText(text = viewState.question) + Spacer(Modifier.height(8.dp)) + + } + } +} + +@Composable +private fun QuestionText( + text: String, + modifier: Modifier = Modifier +) { + SubTitle(modifier = modifier, text = text) +} diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt new file mode 100644 index 0000000..a7a0ef8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt @@ -0,0 +1,26 @@ +package ui.screen.lesson.handler + +import arrow.optics.copy +import ivy.model.ChoiceOptionId +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.completed +import ui.screen.lesson.mapper.toDomain + +class OnChoiceClickEventHandler : EventHandler { + override val eventTypes = setOf(LessonViewEvent.OnChoiceClick::class) + + override suspend fun LessonVmContext.handleEvent(event: LessonViewEvent.OnChoiceClick) { + modifyState { state -> + val questionId = event.questionId.toDomain() + state.copy { + LocalState.choices transform { it + (questionId to ChoiceOptionId(event.choiceId)) } + LocalState.completed transform { it + questionId } + } + + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/QuestionViewEventHandler.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/QuestionViewEventHandler.kt index fb7ed08..9cbebfa 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/QuestionViewEventHandler.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/QuestionViewEventHandler.kt @@ -20,20 +20,20 @@ class QuestionViewEventHandler( ) : EventHandler { override val eventTypes = setOf( - QuestionViewEvent.AnswerCheckChange::class, - QuestionViewEvent.CheckClick::class, + QuestionViewEvent.OnAnswerCheckChange::class, + QuestionViewEvent.OnCheckClick::class, ) override suspend fun LessonVmContext.handleEvent( event: QuestionViewEvent ) { when (event) { - is QuestionViewEvent.AnswerCheckChange -> handleAnswerCheckChange(event) - is QuestionViewEvent.CheckClick -> handleCheckClick(event) + is QuestionViewEvent.OnAnswerCheckChange -> handleAnswerCheckChange(event) + is QuestionViewEvent.OnCheckClick -> handleCheckClick(event) } } - private fun LessonVmContext.handleAnswerCheckChange(event: QuestionViewEvent.AnswerCheckChange) { + private fun LessonVmContext.handleAnswerCheckChange(event: QuestionViewEvent.OnAnswerCheckChange) { modifyState { state -> val questionId = event.questionId.toDomain() val answerId = AnswerId(event.answerId) @@ -68,7 +68,7 @@ class QuestionViewEventHandler( } } - private fun LessonVmContext.handleCheckClick(event: QuestionViewEvent.CheckClick) { + private fun LessonVmContext.handleCheckClick(event: QuestionViewEvent.OnCheckClick) { modifyState { state -> LocalState.completed.modify(state) { completed -> completed + event.questionId.toDomain() diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index dd01681..4822b71 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -1,6 +1,7 @@ package ivy.content.lesson.programmingfundamentals import ivy.content.LottieUrls +import ivy.model.LessonItemId import ivy.model.TextStyle import ivy.model.dsl.lessonContent import ivy.model.dsl.printLessonJson @@ -35,17 +36,47 @@ fun programmingMathInDisguise() = lessonContent { line("But don't worry, Harry has a plan!") } } + image("harry_plan_mg") { + imageUrl = "https://i.ibb.co/gvBWYJ3/harry-spaceship-repair.webp" + } text("chapter_1_title") { - text = "Chapter 1: Repairing the Spaceship" + text = "Chapter 1: Repairing the spaceship" style = TextStyle.Heading } text("chater_1_intro") { text = textBuilder { line("Harry planned to use his superior math and science skills.") - line("To repair his warp drive, Harry needs exactly 3141.59 grams of gold.") + line("To repair his warp drive, Harry needs exactly pure gold that weights 3141.59 grams.") line("However, it turned out that things on Earth are different from his $HarryPlanet home planet...") } } + choice("gravity_choice") { + question = "Do you think 3141.59 grams of gold on $HarryPlanet is the same as on Earth?" + option( + text = "Yes, gold is gold.", + next = LessonItemId("same_weight"), + ) + option( + text = "No, gravity affects weight.", + next = LessonItemId("diff_weight"), + ) + } + text("same_weight") { + text = textBuilder { + line("Harry: Nope, very unlikely! Gravity affects weights.") + line("weight = mass * gravitational acceleration") + line("Assuming the Earth and my planet have different gravity => gold will weight differently.") + } + style = TextStyle.Body + } + text("diff_weight") { + text = textBuilder { + line("Harry: Yes, that's right! Gravity affects weights.") + line("weight = mass * gravitational acceleration") + line("Assuming the Earth and my planet have different gravity => gold will weight differently.") + } + style = TextStyle.Body + } } fun main() { diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt index e03910c..8835aa1 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt @@ -95,8 +95,7 @@ interface OpenQuestionScope { } interface ChoiceScope { - @LearnCmsDsl - fun question(text: String) + var question: String @LearnCmsDsl fun option( diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt index 919b066..b931362 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt @@ -190,13 +190,9 @@ class OpenQuestionScopeImpl : OpenQuestionScope { } class ChoiceScopeImpl : ChoiceScope { - lateinit var question: String + override var question: String = "" val options = mutableListOf() - override fun question(text: String) { - question = text - } - override fun option(text: String, next: LessonItemId) { options.add(ChoiceData(text, next)) } From 3feba0c47fa442c1d2542b4690263f88d903b969 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 17:14:26 +0300 Subject: [PATCH 06/11] Support choice --- .../kotlin/component/button/PrimaryButton.kt | 18 +++++++ .../ui/screen/lesson/LessonViewState.kt | 2 +- .../composable/item/ChoiceLessonItem.kt | 48 +++++++++++++++++-- .../lesson/mapper/LessonViewStateMapper.kt | 2 +- .../ProgrammingMathInDisguise.kt | 4 +- 5 files changed, 65 insertions(+), 9 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/component/button/PrimaryButton.kt b/composeApp/src/commonMain/kotlin/component/button/PrimaryButton.kt index 0629eb6..604426d 100644 --- a/composeApp/src/commonMain/kotlin/component/button/PrimaryButton.kt +++ b/composeApp/src/commonMain/kotlin/component/button/PrimaryButton.kt @@ -2,6 +2,7 @@ package component.button import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material.Button +import androidx.compose.material.OutlinedButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -22,4 +23,21 @@ fun PrimaryButton( ) { Text(text = text) } +} + +@Composable +fun PrimaryOutlinedButton( + text: String, + modifier: Modifier = Modifier, + enabled: Boolean = true, + onClick: () -> Unit, +) { + OutlinedButton( + modifier = modifier, + enabled = enabled, + contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp), + onClick = onClick + ) { + Text(text = text) + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt index b8e05b6..75502e4 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt @@ -98,7 +98,7 @@ data class ImageItemViewState( data class ChoiceItemViewState( override val id: LessonItemIdViewState, val question: String, - val options: List, + val options: ImmutableList, ) : LessonItemViewState @Immutable diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt index ae7ad30..97624d7 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt @@ -1,15 +1,15 @@ package ui.screen.lesson.composable.item -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Card import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import component.button.PrimaryOutlinedButton import component.text.SubTitle +import kotlinx.collections.immutable.ImmutableList import ui.screen.lesson.ChoiceItemViewState import ui.screen.lesson.ChoiceOptionViewState import ui.screen.lesson.composable.ItemSpacingBig @@ -28,7 +28,10 @@ fun ChoiceLessonItem( Column(modifier = Modifier.padding(16.dp)) { QuestionText(text = viewState.question) Spacer(Modifier.height(8.dp)) - + ChoicesOptions( + options = viewState.options, + onChoiceClick = onChoiceClick, + ) } } } @@ -40,3 +43,38 @@ private fun QuestionText( ) { SubTitle(modifier = modifier, text = text) } + +@Composable +private fun ChoicesOptions( + options: ImmutableList, + onChoiceClick: (ChoiceOptionViewState) -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + ) { + for ((index, itemViewState) in options.withIndex()) { + ChoiceOption( + viewState = itemViewState, + onClick = { onChoiceClick(it) }, + ) + if (index != options.lastIndex) { + Spacer(Modifier.width(16.dp)) + } + } + } +} + +@Composable +private fun ChoiceOption( + viewState: ChoiceOptionViewState, + onClick: (ChoiceOptionViewState) -> Unit, + modifier: Modifier = Modifier, +) { + PrimaryOutlinedButton( + modifier = modifier, + text = viewState.text, + onClick = { onClick(viewState) }, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt index 1058a7f..0890c5d 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt @@ -60,7 +60,7 @@ class LessonViewStateMapper( private fun ChoiceItem.toViewState(): ChoiceItemViewState = ChoiceItemViewState( id = id.toViewState(), question = question, - options = options.map { it.toViewState() } + options = options.map { it.toViewState() }.toImmutableList(), ) private fun ChoiceOption.toViewState(): ChoiceOptionViewState = ChoiceOptionViewState( diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index 4822b71..cbcc5a3 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -53,11 +53,11 @@ fun programmingMathInDisguise() = lessonContent { choice("gravity_choice") { question = "Do you think 3141.59 grams of gold on $HarryPlanet is the same as on Earth?" option( - text = "Yes, gold is gold.", + text = "Yes, gold is gold", next = LessonItemId("same_weight"), ) option( - text = "No, gravity affects weight.", + text = "No, gravity affects weight", next = LessonItemId("diff_weight"), ) } From e8edab53599d1345adabc4e159d85020476621e7 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 17:22:20 +0300 Subject: [PATCH 07/11] WIP: Fix chaining issues --- .../commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt | 6 +++++- .../kotlin/ivy/model/dsl/LearnLessonDsl.kt | 13 ++++++++----- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt index 8835aa1..1d861b6 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnCmsDsl.kt @@ -43,7 +43,11 @@ private fun allItemsIds( interface LessonContentScope { @LearnCmsDsl - fun text(id: String, builder: TextScope.() -> Unit) + fun text( + id: String, + nextItemId: String? = null, + builder: TextScope.() -> Unit + ) @LearnCmsDsl fun question(id: String, builder: QuestionScope.() -> Unit) diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt index b931362..27cd3ea 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt @@ -7,7 +7,11 @@ class LessonContentScopeImpl : LessonContentScope { private var currentItem: LessonItemId? = null private val items = mutableMapOf() - override fun text(id: String, builder: TextScope.() -> Unit) { + override fun text( + id: String, + nextItemId: String?, + builder: TextScope.() -> Unit + ) { val scope = TextScopeImpl().also(builder) items[chain(id)] = TextItem( id = LessonItemId(id), @@ -134,15 +138,14 @@ class LessonContentScopeImpl : LessonContentScope { val lessonItemId = LessonItemId(id) if (rootItem == null) { rootItem = lessonItemId - currentItem = rootItem } else { - items[currentItem!!]!!.chainTo(lessonItemId) - currentItem = lessonItemId + items[currentItem!!]!!.setNextTo(lessonItemId) } + currentItem = lessonItemId return currentItem!! } - private fun LessonItem.chainTo(next: LessonItemId) { + private fun LessonItem.setNextTo(next: LessonItemId) { val updated = when (this) { is ChoiceItem -> this is ImageItem -> copy(next = next) From 71bc451dc913d41e8dfe438e17f2bff29eac2110 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 18:10:47 +0300 Subject: [PATCH 08/11] Fix stuff --- .../kotlin/ui/screen/lesson/LessonScreen.kt | 2 +- .../screen/lesson/composable/LessonContent.kt | 3 + .../composable/item/ChoiceLessonItem.kt | 25 ++++----- .../lesson/composable/item/ImageLessonItem.kt | 3 +- .../composable/item/QuestionLessonItem.kt | 55 +++++++++---------- .../composable/item/common/QuestionCard.kt | 27 +++++++++ .../handler/OnChoiceClickEventHandler.kt | 8 ++- .../ProgrammingMathInDisguise.kt | 28 +++++++++- .../kotlin/ivy/model/dsl/LearnCmsDsl.kt | 2 +- .../kotlin/ivy/model/dsl/LearnLessonDsl.kt | 21 +++++-- 10 files changed, 116 insertions(+), 58 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionCard.kt diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt index 6c34a5b..ba685c6 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonScreen.kt @@ -26,7 +26,7 @@ class LessonScreen( register { QuestionViewEventHandler(Di.get()) } register { OnSoundClickEventHandler(Di.get()) } register { OnFinishClickEventHandler(Di.get(), Di.get()) } - register { OnChoiceClickEventHandler() } + register { OnChoiceClickEventHandler(Di.get()) } register { LessonViewModel( courseId = courseId, diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt index 4319d0d..aacfe2c 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt @@ -16,6 +16,7 @@ import component.BackButton import component.LearnScaffold import component.ScreenType import component.screenType +import kotlinx.coroutines.delay import ui.screen.lesson.* import ui.screen.lesson.composable.item.* @@ -73,6 +74,8 @@ private fun LessonItemsLazyColum( LaunchedEffect(viewState.items.size) { if (viewState.items.size > 1) { listState.animateScrollToItem(viewState.items.lastIndex) + delay(1_000) // ensure auto scrolls works for images that are loading + listState.animateScrollToItem(viewState.items.lastIndex) } } diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt index 97624d7..d0d4c96 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt @@ -1,18 +1,17 @@ package ui.screen.lesson.composable.item import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import component.button.PrimaryOutlinedButton +import component.button.PrimaryButton import component.text.SubTitle import kotlinx.collections.immutable.ImmutableList import ui.screen.lesson.ChoiceItemViewState import ui.screen.lesson.ChoiceOptionViewState import ui.screen.lesson.composable.ItemSpacingBig +import ui.screen.lesson.composable.item.common.QuestionCard @Composable fun ChoiceLessonItem( @@ -20,19 +19,15 @@ fun ChoiceLessonItem( onChoiceClick: (ChoiceOptionViewState) -> Unit, modifier: Modifier = Modifier, ) { - Card( + QuestionCard( modifier = modifier.padding(top = ItemSpacingBig), - shape = RoundedCornerShape(16.dp), - elevation = 4.dp ) { - Column(modifier = Modifier.padding(16.dp)) { - QuestionText(text = viewState.question) - Spacer(Modifier.height(8.dp)) - ChoicesOptions( - options = viewState.options, - onChoiceClick = onChoiceClick, - ) - } + QuestionText(text = viewState.question) + Spacer(Modifier.height(8.dp)) + ChoicesOptions( + options = viewState.options, + onChoiceClick = onChoiceClick, + ) } } @@ -72,7 +67,7 @@ private fun ChoiceOption( onClick: (ChoiceOptionViewState) -> Unit, modifier: Modifier = Modifier, ) { - PrimaryOutlinedButton( + PrimaryButton( modifier = modifier, text = viewState.text, onClick = { onClick(viewState) }, diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt index 9026ecd..b60eced 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt @@ -17,7 +17,8 @@ fun ImageLessonItem( modifier: Modifier = Modifier, ) { KamelImage( - modifier = modifier.padding(top = ItemSpacing) + modifier = modifier + .padding(top = ItemSpacing) .clip(RoundedCornerShape(16.dp)), resource = asyncPainterResource(viewState.imageUrl), contentDescription = null diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt index e0748f0..fb3690d 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt @@ -1,8 +1,6 @@ package ui.screen.lesson.composable.item import androidx.compose.foundation.layout.* -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.Card import androidx.compose.material.Checkbox import androidx.compose.material.CheckboxDefaults import androidx.compose.material.MaterialTheme @@ -18,6 +16,7 @@ import ui.screen.lesson.AnswerViewState import ui.screen.lesson.QuestionItemViewState import ui.screen.lesson.QuestionTypeViewState import ui.screen.lesson.composable.ItemSpacingBig +import ui.screen.lesson.composable.item.common.QuestionCard import ui.theme.Green import ui.theme.Red @@ -28,36 +27,32 @@ fun QuestionLessonItem( onAnswerCheckChange: (QuestionTypeViewState, AnswerViewState, Boolean) -> Unit, onCheckClick: (List) -> Unit ) { - Card( + QuestionCard( modifier = modifier.padding(top = ItemSpacingBig), - shape = RoundedCornerShape(16.dp), - elevation = 4.dp ) { - Column(modifier = Modifier.padding(16.dp)) { - QuestionText(text = viewState.question) - if (viewState.type == QuestionTypeViewState.MultipleChoice) { - Spacer(Modifier.height(4.dp)) - SelectAllThatApplyText() - } - viewState.answers.forEach { - Spacer(Modifier.height(8.dp)) - AnswerItem( - viewState = it, - questionType = viewState.type, - questionAnswered = viewState.answered, - onCheckedChange = { checked -> - onAnswerCheckChange(viewState.type, it, checked) - } - ) - } - if (!viewState.answered) { - Spacer(Modifier.height(8.dp)) - CheckButton( - modifier = Modifier.align(Alignment.End), - enabled = viewState.answers.any { it.selected }, - onClick = { onCheckClick(viewState.answers) } - ) - } + QuestionText(text = viewState.question) + if (viewState.type == QuestionTypeViewState.MultipleChoice) { + Spacer(Modifier.height(4.dp)) + SelectAllThatApplyText() + } + viewState.answers.forEach { + Spacer(Modifier.height(8.dp)) + AnswerItem( + viewState = it, + questionType = viewState.type, + questionAnswered = viewState.answered, + onCheckedChange = { checked -> + onAnswerCheckChange(viewState.type, it, checked) + } + ) + } + if (!viewState.answered) { + Spacer(Modifier.height(8.dp)) + CheckButton( + modifier = Modifier.align(Alignment.End), + enabled = viewState.answers.any { it.selected }, + onClick = { onCheckClick(viewState.answers) } + ) } } } diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionCard.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionCard.kt new file mode 100644 index 0000000..da02907 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionCard.kt @@ -0,0 +1,27 @@ +package ui.screen.lesson.composable.item.common + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun QuestionCard( + modifier: Modifier = Modifier, + content: @Composable ColumnScope.() -> Unit, +) { + Card( + modifier = modifier, + shape = RoundedCornerShape(16.dp), + elevation = 4.dp + ) { + Column( + modifier = Modifier.padding(16.dp), + content = content, + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt index a7a0ef8..217af5d 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/handler/OnChoiceClickEventHandler.kt @@ -1,6 +1,8 @@ package ui.screen.lesson.handler +import Platform import arrow.optics.copy +import ivy.content.SoundsUrls import ivy.model.ChoiceOptionId import ui.EventHandler import ui.screen.lesson.LessonViewEvent @@ -10,7 +12,9 @@ import ui.screen.lesson.choices import ui.screen.lesson.completed import ui.screen.lesson.mapper.toDomain -class OnChoiceClickEventHandler : EventHandler { +class OnChoiceClickEventHandler( + private val platform: Platform, +) : EventHandler { override val eventTypes = setOf(LessonViewEvent.OnChoiceClick::class) override suspend fun LessonVmContext.handleEvent(event: LessonViewEvent.OnChoiceClick) { @@ -20,7 +24,7 @@ class OnChoiceClickEventHandler : EventHandler Unit ) diff --git a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt index 27cd3ea..e7584e8 100644 --- a/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt +++ b/shared/src/commonMain/kotlin/ivy/model/dsl/LearnLessonDsl.kt @@ -4,12 +4,12 @@ import ivy.model.* class LessonContentScopeImpl : LessonContentScope { private var rootItem: LessonItemId? = null - private var currentItem: LessonItemId? = null + private var currentItemId: LessonItemId? = null private val items = mutableMapOf() override fun text( id: String, - nextItemId: String?, + next: String?, builder: TextScope.() -> Unit ) { val scope = TextScopeImpl().also(builder) @@ -17,7 +17,7 @@ class LessonContentScopeImpl : LessonContentScope { id = LessonItemId(id), text = scope.text, style = scope.style, - next = null, + next = next?.let(::LessonItemId), ) } @@ -139,10 +139,13 @@ class LessonContentScopeImpl : LessonContentScope { if (rootItem == null) { rootItem = lessonItemId } else { - items[currentItem!!]!!.setNextTo(lessonItemId) + val currentItem = items[currentItemId!!]!! + if (currentItem.nextOrNull() == null) { + currentItem.setNextTo(lessonItemId) + } } - currentItem = lessonItemId - return currentItem!! + currentItemId = lessonItemId + return currentItemId!! } private fun LessonItem.setNextTo(next: LessonItemId) { @@ -161,6 +164,12 @@ class LessonContentScopeImpl : LessonContentScope { items[id] = updated } + private fun LessonItem.nextOrNull(): LessonItemId? { + return when (this) { + is LinearItem -> next + else -> null + } + } } class TextScopeImpl : TextScope { From f95222cb09b091c10e266f58781bfa0a3af5d17a Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 18:26:25 +0300 Subject: [PATCH 09/11] Improve UX --- .../kotlin/component/text/Subtitle.kt | 7 ++-- .../screen/lesson/composable/LessonContent.kt | 18 +++++----- .../composable/item/ChoiceLessonItem.kt | 10 +----- .../lesson/composable/item/ImageLessonItem.kt | 2 ++ .../composable/item/QuestionLessonItem.kt | 12 ++----- .../composable/item/common/QuestionText.kt | 18 ++++++++++ .../screen/lesson/mapper/LessonTreeManager.kt | 2 +- .../ProgrammingMathInDisguise.kt | 34 +++++++++++++------ 8 files changed, 63 insertions(+), 40 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt diff --git a/composeApp/src/commonMain/kotlin/component/text/Subtitle.kt b/composeApp/src/commonMain/kotlin/component/text/Subtitle.kt index a27b37f..5dc7d7c 100644 --- a/composeApp/src/commonMain/kotlin/component/text/Subtitle.kt +++ b/composeApp/src/commonMain/kotlin/component/text/Subtitle.kt @@ -4,15 +4,18 @@ import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight @Composable fun SubTitle( text: String, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + fontWeight: FontWeight? = null, ) { Text( modifier = modifier, text = text, - style = MaterialTheme.typography.subtitle1 + style = MaterialTheme.typography.subtitle1, + fontWeight = fontWeight, ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt index aacfe2c..0bc5855 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/LessonContent.kt @@ -1,9 +1,6 @@ package ui.screen.lesson.composable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState @@ -21,7 +18,7 @@ import ui.screen.lesson.* import ui.screen.lesson.composable.item.* val ItemSpacing = 12.dp -val ItemSpacingBig = 20.dp +val ItemSpacingBig = 48.dp @Composable fun LessonContent( @@ -73,9 +70,11 @@ private fun LessonItemsLazyColum( LaunchedEffect(viewState.items.size) { if (viewState.items.size > 1) { - listState.animateScrollToItem(viewState.items.lastIndex) - delay(1_000) // ensure auto scrolls works for images that are loading - listState.animateScrollToItem(viewState.items.lastIndex) + // ensure auto scrolls works for images that are loading + repeat(4) { + listState.animateScrollToItem(viewState.items.lastIndex) + delay(200) + } } } @@ -156,5 +155,8 @@ private fun LessonItemsLazyColum( ) } } + item("empty_space") { + Spacer(Modifier.height(200.dp)) + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt index d0d4c96..443d8bf 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt @@ -6,12 +6,12 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import component.button.PrimaryButton -import component.text.SubTitle import kotlinx.collections.immutable.ImmutableList import ui.screen.lesson.ChoiceItemViewState import ui.screen.lesson.ChoiceOptionViewState import ui.screen.lesson.composable.ItemSpacingBig import ui.screen.lesson.composable.item.common.QuestionCard +import ui.screen.lesson.composable.item.common.QuestionText @Composable fun ChoiceLessonItem( @@ -31,14 +31,6 @@ fun ChoiceLessonItem( } } -@Composable -private fun QuestionText( - text: String, - modifier: Modifier = Modifier -) { - SubTitle(modifier = modifier, text = text) -} - @Composable private fun ChoicesOptions( options: ImmutableList, diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt index b60eced..6cf9449 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ImageLessonItem.kt @@ -1,6 +1,7 @@ package ui.screen.lesson.composable.item import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -19,6 +20,7 @@ fun ImageLessonItem( KamelImage( modifier = modifier .padding(top = ItemSpacing) + .size(264.dp) .clip(RoundedCornerShape(16.dp)), resource = asyncPainterResource(viewState.imageUrl), contentDescription = null diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt index fb3690d..921352e 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/QuestionLessonItem.kt @@ -1,5 +1,6 @@ package ui.screen.lesson.composable.item +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.Checkbox import androidx.compose.material.CheckboxDefaults @@ -11,12 +12,12 @@ import androidx.compose.ui.unit.dp import component.button.PrimaryButton import component.text.Body import component.text.BodySmall -import component.text.SubTitle import ui.screen.lesson.AnswerViewState import ui.screen.lesson.QuestionItemViewState import ui.screen.lesson.QuestionTypeViewState import ui.screen.lesson.composable.ItemSpacingBig import ui.screen.lesson.composable.item.common.QuestionCard +import ui.screen.lesson.composable.item.common.QuestionText import ui.theme.Green import ui.theme.Red @@ -57,13 +58,6 @@ fun QuestionLessonItem( } } -@Composable -private fun QuestionText( - text: String, - modifier: Modifier = Modifier -) { - SubTitle(modifier = modifier, text = text) -} @Composable private fun SelectAllThatApplyText( @@ -84,7 +78,7 @@ private fun AnswerItem( onCheckedChange: (Boolean) -> Unit ) { Row( - modifier = modifier, + modifier = modifier.clickable { onCheckedChange(!viewState.selected) }, verticalAlignment = Alignment.CenterVertically ) { AnswerCheckbox( diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt new file mode 100644 index 0000000..5695638 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt @@ -0,0 +1,18 @@ +package ui.screen.lesson.composable.item.common + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import component.text.SubTitle + +@Composable +fun QuestionText( + text: String, + modifier: Modifier = Modifier +) { + SubTitle( + modifier = modifier, + text = text, + fontWeight = FontWeight.Bold + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt index 217309b..1c87d69 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonTreeManager.kt @@ -31,7 +31,7 @@ class LessonTreeManager { currentItemId in localState.completed } - is SoundItem, is LottieAnimationItem -> true + is ImageItem, is SoundItem, is LottieAnimationItem -> true else -> { currentItemId in localState.completed || diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index 8989e25..ef486d8 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -25,6 +25,10 @@ fun programmingMathInDisguise() = lessonContent { } style = TextStyle.Body } + text("harry_experimenting") { + text = "Harry loves experimenting... a lot!" + style = TextStyle.Heading + } lottie("harry_crash_anim") { jsonUrl = LottieUrls.SpaceshipFly } @@ -36,13 +40,13 @@ fun programmingMathInDisguise() = lessonContent { line("But don't worry, Harry has a plan!") } } - image("harry_plan_mg") { - imageUrl = "https://i.ibb.co/gvBWYJ3/harry-spaceship-repair.webp" - } text("chapter_1_title") { text = "Chapter 1: Repairing the spaceship" style = TextStyle.Heading } + image("harry_plan_mg") { + imageUrl = "https://i.ibb.co/gvBWYJ3/harry-spaceship-repair.webp" + } text("chater_1_intro") { text = textBuilder { line("Harry planned to use his superior math and science skills.") @@ -54,25 +58,33 @@ fun programmingMathInDisguise() = lessonContent { question = "Do you think 3141.59 grams of gold on $HarryPlanet is the same as on Earth?" option( text = "Yes, gold is gold", - next = LessonItemId("same_weight"), + next = LessonItemId("same_weight1"), ) option( text = "No, gravity affects weight", - next = LessonItemId("diff_weight"), + next = LessonItemId("diff_weight1"), ) } - text("same_weight", next = "gravity_question") { + text("same_weight1", next = "same_weight2") { + text = "Harry: \"Nope\"" + style = TextStyle.Heading + } + text("same_weight2", next = "gravity_question") { text = textBuilder { - line("Harry: Nope, very unlikely! Gravity affects weights.") - line("weight = mass * gravitational acceleration") + line("That's very unlikely! Gravity affects weights.") + line("Formula: weight = mass * gravitational acceleration") line("Assuming the Earth and my planet have different gravity => gold will weight differently.") } style = TextStyle.Body } - text("diff_weight", next = "gravity_question") { + text("diff_weigh1", next = "diff_weight2") { + text = "Harry: \"That's right!\"" + style = TextStyle.Heading + } + text("diff_weight2", next = "gravity_question") { text = textBuilder { - line("Harry: Yes, that's right! Gravity affects weights.") - line("weight = mass * gravitational acceleration") + line("Correct! Gravity affects weights.") + line("Formula: weight = mass * gravitational acceleration") line("Assuming the Earth and my planet have different gravity => gold will weight differently.") } style = TextStyle.Body From eb90e7928d7e2975ed521a4a93f6203521523782 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 18:43:03 +0300 Subject: [PATCH 10/11] WIP --- .../ui/screen/lesson/LessonViewState.kt | 2 +- .../composable/item/ChoiceLessonItem.kt | 2 +- .../lesson/composable/item/TextLessonItem.kt | 12 ++++++- .../composable/item/common/QuestionText.kt | 10 ++++-- .../lesson/mapper/LessonViewStateMapper.kt | 4 +-- .../ProgrammingMathInDisguise.kt | 35 +++++++++++-------- .../kotlin/ivy/model/LessonModel.kt | 4 ++- 7 files changed, 46 insertions(+), 23 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt index 75502e4..ae367a3 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/LessonViewState.kt @@ -25,7 +25,7 @@ data class TextItemViewState( @Immutable enum class TextStyleViewState { - Heading, Body + Heading, Body, BodyBigSpacing } @Immutable diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt index 443d8bf..fa8d51e 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/ChoiceLessonItem.kt @@ -23,7 +23,7 @@ fun ChoiceLessonItem( modifier = modifier.padding(top = ItemSpacingBig), ) { QuestionText(text = viewState.question) - Spacer(Modifier.height(8.dp)) + Spacer(Modifier.height(16.dp)) ChoicesOptions( options = viewState.options, onChoiceClick = onChoiceClick, diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt index 7c76e13..25191e7 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/TextLessonItem.kt @@ -23,7 +23,17 @@ fun TextLessonItem( ) TextStyleViewState.Body -> BodyBig( - modifier = modifier.padding(top = ItemSpacing), + modifier = modifier.padding( + top = ItemSpacing + ), + text = viewState.text, + textAlign = TextAlign.Center, + ) + + TextStyleViewState.BodyBigSpacing -> BodyBig( + modifier = modifier.padding( + top = ItemSpacingBig + ), text = viewState.text, textAlign = TextAlign.Center, ) diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt index 5695638..0ddac33 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/composable/item/common/QuestionText.kt @@ -1,18 +1,22 @@ package ui.screen.lesson.composable.item.common +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontWeight -import component.text.SubTitle +import androidx.compose.ui.unit.sp @Composable fun QuestionText( text: String, modifier: Modifier = Modifier ) { - SubTitle( + Text( modifier = modifier, text = text, - fontWeight = FontWeight.Bold + style = MaterialTheme.typography.subtitle1, + fontSize = 18.sp, + fontWeight = FontWeight.Bold, ) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt index 0890c5d..5896444 100644 --- a/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt +++ b/composeApp/src/commonMain/kotlin/ui/screen/lesson/mapper/LessonViewStateMapper.kt @@ -3,8 +3,7 @@ package ui.screen.lesson.mapper import LogLevel import Platform import ivy.model.* -import ivy.model.TextStyle.Body -import ivy.model.TextStyle.Heading +import ivy.model.TextStyle.* import kotlinx.collections.immutable.toImmutableList import ui.screen.lesson.* @@ -139,6 +138,7 @@ class LessonViewStateMapper( style = when (style) { Heading -> TextStyleViewState.Heading Body -> TextStyleViewState.Body + BodyBigSpacing -> TextStyleViewState.BodyBigSpacing } ) diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index ef486d8..a77e4fb 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -57,34 +57,34 @@ fun programmingMathInDisguise() = lessonContent { choice("gravity_choice") { question = "Do you think 3141.59 grams of gold on $HarryPlanet is the same as on Earth?" option( - text = "Yes, gold is gold", - next = LessonItemId("same_weight1"), + text = "Same weight, gold is gold", + next = LessonItemId("same_w1"), ) option( - text = "No, gravity affects weight", - next = LessonItemId("diff_weight1"), + text = "Different weight, gravity affects weight", + next = LessonItemId("diff_w1"), ) } - text("same_weight1", next = "same_weight2") { - text = "Harry: \"Nope\"" + text("same_w1", next = "same_w2") { + text = "Harry says \"Nope\"" style = TextStyle.Heading } - text("same_weight2", next = "gravity_question") { + text("same_w2", next = "gravity_question") { text = textBuilder { line("That's very unlikely! Gravity affects weights.") - line("Formula: weight = mass * gravitational acceleration") - line("Assuming the Earth and my planet have different gravity => gold will weight differently.") + line("weight = mass * gravitational acceleration") + line("Assuming the Earth's and my planet's gravity are different, then the gold will weight differently.") } style = TextStyle.Body } - text("diff_weigh1", next = "diff_weight2") { - text = "Harry: \"That's right!\"" + text("diff_w1", next = "diff_w2") { + text = "Harry says \"That's right!\"" style = TextStyle.Heading } - text("diff_weight2", next = "gravity_question") { + text("diff_w2", next = "gravity_question") { text = textBuilder { line("Correct! Gravity affects weights.") - line("Formula: weight = mass * gravitational acceleration") + line("weight = mass * gravitational acceleration") line("Assuming the Earth and my planet have different gravity => gold will weight differently.") } style = TextStyle.Body @@ -109,8 +109,15 @@ fun programmingMathInDisguise() = lessonContent { explanation = "Gravity is not subtracted from your mass." ) } + text("first_program") { + text = textBuilder { + line("Knowing that let's create our first program!") + line("We need a function that will calculate the mass of gold by its weight on Earth.") + } + style = TextStyle.BodyBigSpacing + } text("final") { - text = "That's it!" + text = "End of lesson." style = TextStyle.Heading } } diff --git a/shared/src/commonMain/kotlin/ivy/model/LessonModel.kt b/shared/src/commonMain/kotlin/ivy/model/LessonModel.kt index 160c446..ee539a6 100644 --- a/shared/src/commonMain/kotlin/ivy/model/LessonModel.kt +++ b/shared/src/commonMain/kotlin/ivy/model/LessonModel.kt @@ -50,7 +50,9 @@ data class TextItem( @Serializable enum class TextStyle { - Heading, Body + Heading, + Body, + BodyBigSpacing } @Serializable From 2f1bbe7528896f1758cfe2e9699f76cfe055cb69 Mon Sep 17 00:00:00 2001 From: iliyangermanov Date: Mon, 3 Jun 2024 18:43:40 +0300 Subject: [PATCH 11/11] WIP --- .../lesson/programmingfundamentals/ProgrammingMathInDisguise.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt index a77e4fb..4e64a0b 100644 --- a/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt +++ b/shared/src/commonMain/kotlin/ivy/content/lesson/programmingfundamentals/ProgrammingMathInDisguise.kt @@ -85,7 +85,7 @@ fun programmingMathInDisguise() = lessonContent { text = textBuilder { line("Correct! Gravity affects weights.") line("weight = mass * gravitational acceleration") - line("Assuming the Earth and my planet have different gravity => gold will weight differently.") + line("Assuming the Earth's and my planet's gravity are different, then the gold will weight differently.") } style = TextStyle.Body }