From f12ac0826ebe7cbb36dbace242c46822a1451c4a Mon Sep 17 00:00:00 2001 From: GitHub Actions Bot Date: Tue, 2 Sep 2025 22:14:50 +0200 Subject: [PATCH] feat: Implement initial project structure and network layer This commit introduces the foundational structure of the Kahoot Quiz application and implements the core network layer. Key changes include: - Added new Gradle modules: `core:network`, `domain`, `model:data`, and `ui:quiz`. - Configured Detekt for static code analysis in the new modules. - Implemented Retrofit and Gson for network communication and JSON parsing. - Defined DTOs for the Kahoot quiz API response, splitting them into logical files (QuizResponseDto, CommonDtos, CoverDtos, QuestionDtos, MetadataDtos, ContentTagsDto) for better organization. - Created `QuizApi` interface with a GET request for fetching quiz data. - Added `QuizService` interface and its initial implementation `QuizServiceImpl`. - Set up Hilt for dependency injection in the network module, providing Retrofit and QuizApi instances. - Included a `sample_quiz.json` file for testing and development. - Added unit tests (`QuizResponseDtoParsingTest`) to verify the correct parsing of the sample JSON into DTOs. - Updated `.gitignore` to exclude additional generated files and IDE specific folders. - Modified `settings.gradle.kts` to include the new modules. - Updated `app/build.gradle.kts` to include dependencies on the new `ui:quiz` and `model:data` modules and removed unused dependencies. --- .gitignore | 5 +- Kahoot__-_App_Developer_Challenge.pdf | Bin 0 -> 50393 bytes app/build.gradle.kts | 6 +- core/network/build.gradle.kts | 22 + core/network/config/detekt/detekt.yml | 10 + .../kahootquiz/core/network/QuizService.kt | 4 + .../core/network/QuizServiceImpl.kt | 4 + .../core/network/di/NetworkModule.kt | 29 ++ .../core/network/model/CommonDtos.kt | 20 + .../core/network/model/ContentTagsDto.kt | 8 + .../core/network/model/CoverDtos.kt | 32 ++ .../core/network/model/MetadataDtos.kt | 42 ++ .../core/network/model/QuestionDtos.kt | 81 ++++ .../core/network/model/QuizResponse.kt | 10 + .../core/network/model/QuizResponseDto.kt | 36 ++ .../core/network/retrofit/QuizApi.kt | 10 + .../network/QuizResponseDtoParsingTest.kt | 100 +++++ .../src/test/resources/sample_quiz.json | 404 ++++++++++++++++++ domain/build.gradle.kts | 11 + domain/config/detekt/detekt.yml | 10 + gradle/libs.versions.toml | 3 + model/data/build.gradle.kts | 13 + model/data/config/detekt/detekt.yml | 10 + settings.gradle.kts | 4 + ui/config/detekt/detekt.yml | 10 + ui/quiz/build.gradle.kts | 12 + ui/quiz/config/detekt/detekt.yml | 10 + 27 files changed, 902 insertions(+), 4 deletions(-) create mode 100644 Kahoot__-_App_Developer_Challenge.pdf create mode 100644 core/network/build.gradle.kts create mode 100644 core/network/config/detekt/detekt.yml create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/QuizService.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/QuizServiceImpl.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/di/NetworkModule.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/CommonDtos.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/ContentTagsDto.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/CoverDtos.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/MetadataDtos.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuestionDtos.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponse.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponseDto.kt create mode 100644 core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/retrofit/QuizApi.kt create mode 100644 core/network/src/test/kotlin/dev/adriankuta/kahootquiz/core/network/QuizResponseDtoParsingTest.kt create mode 100644 core/network/src/test/resources/sample_quiz.json create mode 100644 domain/build.gradle.kts create mode 100644 domain/config/detekt/detekt.yml create mode 100644 model/data/build.gradle.kts create mode 100644 model/data/config/detekt/detekt.yml create mode 100644 ui/config/detekt/detekt.yml create mode 100644 ui/quiz/build.gradle.kts create mode 100644 ui/quiz/config/detekt/detekt.yml diff --git a/.gitignore b/.gitignore index 427a604..4434be0 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ /.idea/navEditor.xml /.idea/assetWizardSettings.xml .DS_Store -/build +build/ /captures .externalNativeBuild .cxx @@ -16,4 +16,5 @@ local.properties # Project exclude paths /build-logic/convention/build/ -/build-logic/convention/build/classes/kotlin/main/ \ No newline at end of file +/build-logic/convention/build/classes/kotlin/main/ +/.idea/ diff --git a/Kahoot__-_App_Developer_Challenge.pdf b/Kahoot__-_App_Developer_Challenge.pdf new file mode 100644 index 0000000000000000000000000000000000000000..3ded2190565a7a2fa95af3a707b24f4ac85b80bc GIT binary patch literal 50393 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 P4d!9R7Dnwt$(W- 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, + val generatedCurriculumCodes: List? +) diff --git a/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/CoverDtos.kt b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/CoverDtos.kt new file mode 100644 index 0000000..6192bb2 --- /dev/null +++ b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/CoverDtos.kt @@ -0,0 +1,32 @@ +package dev.adriankuta.kahootquiz.core.network.model + +// Cover metadata and related DTOs + +data class CoverMetadataDto( + val id: String?, + val altText: String?, + val contentType: String?, + val origin: String?, + val externalRef: String?, + val resources: String?, + val width: Int?, + val height: Int?, + val extractedColors: List?, + val blurhash: String?, + val crop: CropDto? +) + +// Color extracted from cover image + +data class ExtractedColorDto( + val swatch: String?, + val rgbHex: String? +) + +// Crop descriptor + +data class CropDto( + val origin: PointDto?, + val target: PointDto?, + val circular: Boolean? +) diff --git a/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/MetadataDtos.kt b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/MetadataDtos.kt new file mode 100644 index 0000000..16829fc --- /dev/null +++ b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/MetadataDtos.kt @@ -0,0 +1,42 @@ +package dev.adriankuta.kahootquiz.core.network.model + +// Metadata section DTOs + +data class MetadataDto( + val access: AccessDto?, + val duplicationProtection: Boolean?, + val featuredListMemberships: List?, + val lastEdit: LastEditDto?, + val versionMetadata: VersionMetadataDto? +) + +// Access settings + +data class AccessDto( + val groupRead: List?, + val folderGroupIds: List?, + val features: List? +) + +// Featured list membership + +data class FeaturedListMembershipDto( + val list: String?, + val addedAt: Long? +) + +// Last edit information + +data class LastEditDto( + val editorUserId: String?, + val editorUsername: String?, + val editTimestamp: Long? +) + +// Version metadata + +data class VersionMetadataDto( + val version: Int?, + val created: Long?, + val creator: String? +) diff --git a/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuestionDtos.kt b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuestionDtos.kt new file mode 100644 index 0000000..9adcab2 --- /dev/null +++ b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuestionDtos.kt @@ -0,0 +1,81 @@ +package dev.adriankuta.kahootquiz.core.network.model + +// Question and choice related DTOs + +data class QuestionDto( + val type: String?, + val question: String?, + val time: Int?, + val points: Boolean?, + val pointsMultiplier: Int?, + val choices: List?, + val layout: String?, + val image: String?, + val imageMetadata: ImageMetadataDto?, + val resources: String?, + val video: VideoDto?, + val questionFormat: Int?, + val languageInfo: LanguageInfoDto?, + val media: List?, + val choiceRange: ChoiceRangeDto? +) + +// Choice option + +data class ChoiceDto( + val answer: String?, + val correct: Boolean?, + val languageInfo: LanguageInfoDto? +) + +// Optional video attachment + +data class VideoDto( + val id: String? = null, + val startTime: Int?, + val endTime: Int?, + val service: String?, + val fullUrl: String? +) + +// Image metadata appearing in multiple places + +data class ImageMetadataDto( + val id: String?, + val altText: String? = null, + val contentType: String?, + val origin: String? = null, + val externalRef: String? = null, + val resources: String? = null, + val width: Int? = null, + val height: Int? = null, + val effects: List? = null, + val crop: CropDto? = null +) + +// Generic media item on question + +data class MediaItemDto( + val type: String?, + val zIndex: Int?, + val isColorOnly: Boolean?, + val id: String?, + val altText: String? = null, + val contentType: String?, + val origin: String? = null, + val externalRef: String? = null, + val resources: String? = null, + val width: Int? = null, + val height: Int? = null, + val crop: CropDto? = null +) + +// Slider range for "slider" question type + +data class ChoiceRangeDto( + val start: Int?, + val end: Int?, + val step: Int?, + val correct: Int?, + val tolerance: Int? +) diff --git a/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponse.kt b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponse.kt new file mode 100644 index 0000000..7001c97 --- /dev/null +++ b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponse.kt @@ -0,0 +1,10 @@ +package dev.adriankuta.kahootquiz.core.network.model + +// This file used to contain all DTOs in one place. +// The DTOs have been split into separate files for maintainability: +// - QuizResponseDto.kt +// - CommonDtos.kt +// - CoverDtos.kt +// - QuestionDtos.kt +// - MetadataDtos.kt +// Keeping this file as a placeholder to avoid breaking any imports by file path (package remains the same). diff --git a/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponseDto.kt b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponseDto.kt new file mode 100644 index 0000000..0c1c54b --- /dev/null +++ b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/model/QuizResponseDto.kt @@ -0,0 +1,36 @@ +package dev.adriankuta.kahootquiz.core.network.model + +import com.google.gson.annotations.SerializedName + +// Root response for a Kahoot quiz details (network layer DTO) +data class QuizResponseDto( + val uuid: String?, + val language: String?, + val creator: String?, + @SerializedName("creator_username") val creatorUsername: String?, + val compatibilityLevel: Int?, + @SerializedName("creator_primary_usage") val creatorPrimaryUsage: String?, + val folderId: String?, + val themeId: String?, + val visibility: Int?, + val audience: String?, + val title: String?, + val description: String?, + val quizType: String?, + val cover: String?, + val coverMetadata: CoverMetadataDto?, + val questions: List?, + val contentTags: ContentTagsDto?, + val metadata: MetadataDto?, + val resources: String?, + val slug: String?, + val languageInfo: LanguageInfoDto?, + val inventoryItemIds: List?, + val channels: List?, + val isValid: Boolean?, + val playAsGuest: Boolean?, + val hasRestrictedContent: Boolean?, + val type: String?, + val created: Long?, + val modified: Long? +) diff --git a/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/retrofit/QuizApi.kt b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/retrofit/QuizApi.kt new file mode 100644 index 0000000..8d1be03 --- /dev/null +++ b/core/network/src/main/kotlin/dev/adriankuta/kahootquiz/core/network/retrofit/QuizApi.kt @@ -0,0 +1,10 @@ +package dev.adriankuta.kahootquiz.core.network.retrofit + +import dev.adriankuta.kahootquiz.core.network.model.QuizResponseDto +import retrofit2.http.GET + +interface QuizApi { + + @GET("/rest/kahoots/fb4054fc-6a71-463e-88cd-243876715bc1") + suspend fun getQuiz(): QuizResponseDto +} diff --git a/core/network/src/test/kotlin/dev/adriankuta/kahootquiz/core/network/QuizResponseDtoParsingTest.kt b/core/network/src/test/kotlin/dev/adriankuta/kahootquiz/core/network/QuizResponseDtoParsingTest.kt new file mode 100644 index 0000000..372cd26 --- /dev/null +++ b/core/network/src/test/kotlin/dev/adriankuta/kahootquiz/core/network/QuizResponseDtoParsingTest.kt @@ -0,0 +1,100 @@ +package dev.adriankuta.kahootquiz.core.network + +import com.google.common.truth.Truth.assertThat +import com.google.gson.Gson +import dev.adriankuta.kahootquiz.core.network.model.QuizResponseDto +import org.junit.Test +import java.io.InputStreamReader + +class QuizResponseDtoParsingTest { + + private fun loadSample(): QuizResponseDto { + val stream = checkNotNull(javaClass.classLoader?.getResourceAsStream("sample_quiz.json")) { + "sample_quiz.json not found on test classpath" + } + stream.use { input -> + return Gson().fromJson(InputStreamReader(input), QuizResponseDto::class.java) + } + } + + @Test + fun parses_root_fields_correctly() { + val dto = loadSample() + + assertThat(dto.uuid).isEqualTo("fb4054fc-6a71-463e-88cd-243876715bc1") + assertThat(dto.title).isEqualTo("Seven Wonders of the Ancient World") + assertThat(dto.creatorUsername).isEqualTo("KahootStudio") + assertThat(dto.creatorPrimaryUsage).isEqualTo("teacher") + assertThat(dto.quizType).isEqualTo("quiz") + assertThat(dto.isValid).isTrue() + assertThat(dto.playAsGuest).isTrue() + assertThat(dto.hasRestrictedContent).isFalse() + assertThat(dto.created).isGreaterThan(0) + assertThat(dto.modified).isGreaterThan(0) + } + + @Test + fun parses_cover_metadata_and_colors() { + val dto = loadSample() + val cover = checkNotNull(dto.coverMetadata) + assertThat(cover.id).isEqualTo("0b64142f-0624-4014-9f50-b65e6be93d8f") + assertThat(cover.contentType).isEqualTo("image/jpeg") + assertThat(cover.extractedColors).isNotNull() + assertThat(cover.extractedColors).hasSize(4) + assertThat(cover.extractedColors?.first()?.swatch).isEqualTo("VIBRANT") + assertThat(cover.blurhash).isNotNull() + val crop = checkNotNull(cover.crop) + assertThat(crop.circular).isFalse() + assertThat(crop.origin?.x).isEqualTo(227) + assertThat(crop.target?.y).isEqualTo(1299) + } + + @Test + fun parses_questions_and_choices() { + val dto = loadSample() + val questions = checkNotNull(dto.questions) + assertThat(questions).hasSize(12) + + // First question true/false + val q1 = questions[0] + assertThat(q1.type).isEqualTo("quiz") + assertThat(q1.layout).isEqualTo("TRUE_FALSE") + assertThat(q1.choices).hasSize(2) + assertThat(q1.choices?.get(0)?.answer).isEqualTo("True") + assertThat(q1.choices?.get(0)?.correct).isTrue() + assertThat(q1.choices?.get(1)?.answer).isEqualTo("False") + assertThat(q1.choices?.get(1)?.correct).isFalse() + + // Open ended question exists and has accepted answers + val openEnded = questions.first { it.type == "open_ended" } + assertThat(openEnded.choices).isNotNull() + val answers = openEnded.choices!!.map { it.answer } + assertThat(answers).containsAtLeast("Helios", "helios") + + // Slider question has choiceRange + val slider = questions.first { it.type == "slider" } + val range = checkNotNull(slider.choiceRange) + assertThat(range.start).isEqualTo(0) + assertThat(range.end).isEqualTo(7) + assertThat(range.step).isEqualTo(1) + assertThat(range.correct).isEqualTo(1) + assertThat(range.tolerance).isEqualTo(0) + } + + @Test + fun parses_metadata_and_channels() { + val dto = loadSample() + val metadata = checkNotNull(dto.metadata) + assertThat(metadata.duplicationProtection).isTrue() + assertThat(metadata.featuredListMemberships).isNotNull() + assertThat(metadata.featuredListMemberships).isNotEmpty() + assertThat(metadata.versionMetadata?.version).isEqualTo(4) + assertThat(metadata.lastEdit?.editorUsername).isEqualTo("KahootStudio") + + val channels = checkNotNull(dto.channels) + assertThat(channels).hasSize(1) + assertThat(channels.first().id).isEqualTo("247c3eb4-af80-4c1f-b006-558682c7bd2f") + + assertThat(dto.languageInfo?.readAloudSupported).isTrue() + } +} diff --git a/core/network/src/test/resources/sample_quiz.json b/core/network/src/test/resources/sample_quiz.json new file mode 100644 index 0000000..aa8e613 --- /dev/null +++ b/core/network/src/test/resources/sample_quiz.json @@ -0,0 +1,404 @@ +{ + "uuid": "fb4054fc-6a71-463e-88cd-243876715bc1", + "language": "English", + "creator": "4c1574ee-de54-40a2-be15-8d72b333afad", + "creator_username": "KahootStudio", + "compatibilityLevel": 27, + "creator_primary_usage": "teacher", + "folderId": "20915e7a-34d5-458b-91e9-2ed290484438", + "themeId": "ace311f1-2de4-450b-ba7d-d8a7a81705a5", + "visibility": 1, + "audience": "Social", + "title": "Seven Wonders of the Ancient World", + "description": "A geography quiz about the Seven Wonders of the Ancient World. See how much you know about these ancient buildings and monuments!\n#trivia #history #geography #sevenwonders", + "quizType": "quiz", + "cover": "https://media.kahoot.it/0b64142f-0624-4014-9f50-b65e6be93d8f", + "coverMetadata": { + "id": "0b64142f-0624-4014-9f50-b65e6be93d8f", + "altText": "The Pyramids, Giza, Egypt", + "contentType": "image/jpeg", + "origin": "Getty Images", + "externalRef": "1360795720", + "resources": "Nick Brundle Photography/Moment/Getty Images", + "width": 2309, + "height": 1299, + "extractedColors": [ + { + "swatch": "VIBRANT", + "rgbHex": "#28a8d8" + }, + { + "swatch": "LIGHT_VIBRANT", + "rgbHex": "#88c8e0" + }, + { + "swatch": "DARK_VIBRANT", + "rgbHex": "#307890" + }, + { + "swatch": "LIGHT_MUTED", + "rgbHex": "#d0d0c0" + } + ], + "blurhash": "UuJ*#Qxtx]xaCAj[W=WqEma}M{R*M|WVn#j?", + "crop": { + "origin": {"x": 227, "y": 0}, + "target": {"x": 1948, "y": 1299}, + "circular": false + } + }, + "questions": [ + { + "type": "quiz", + "question": "\u003Cb\u003ETrue or false: \u003C/b\u003EThe list of seven wonders is based on ancient Greek guidebooks for tourists.", + "time": 20000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "True", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "False", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "layout": "TRUE_FALSE", + "image": "https://media.kahoot.it/b2709905-1c6e-45a0-9cc1-34c6580495e5", + "imageMetadata": { + "id": "b2709905-1c6e-45a0-9cc1-34c6580495e5", + "altText": "Old engraved illustration of bird's eye View of Alexandria, Egypt", + "contentType": "image/jpeg", + "origin": "Getty Images", + "externalRef": "1352146635", + "resources": "mikroman6/Moment/Getty Images", + "width": 2133, + "height": 1406 + }, + "resources": "mikroman6/Moment/Getty Images", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "The Great Pyramid of Giza is the oldest of the wonders. What was its purpose?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "A monument to the god Ra", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "A tomb", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "A momument to a great war victory", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "A temple", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "resources": "", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [ + { + "type": "background_image", + "zIndex": -1, + "isColorOnly": false, + "id": "0b64142f-0624-4014-9f50-b65e6be93d8f", + "altText": "The Pyramids, Giza, Egypt", + "contentType": "image/jpeg", + "origin": "Getty Images", + "externalRef": "1360795720", + "resources": "Nick Brundle Photography/Moment/Getty Images", + "width": 2309, + "height": 1299, + "crop": {"origin": {"x": 227, "y": 0}, "target": {"x": 1948, "y": 1299}, "circular": false} + } + ] + }, + { + "type": "quiz", + "question": "Why were the Hanging Gardens of Babylon supposedly built?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "As a tourist destination", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "A monument to Ninurta, the god of farmers", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "An engagement gift from a king to his future queen", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "A gift for the king's wife", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/7bce7efb-3d94-495c-905f-9c14190b7910", + "imageMetadata": { + "id": "7bce7efb-3d94-495c-905f-9c14190b7910", + "contentType": "image/*", + "resources": "https://upload.wikimedia.org/wikipedia/commons/a/ae/Hanging_Gardens_of_Babylon.jpg CC0", + "effects": [] + }, + "resources": "https://upload.wikimedia.org/wikipedia/commons/a/ae/Hanging_Gardens_of_Babylon.jpg CC0", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "The Temple of Artemis was located in the city Ephesus. It's the territory of _____ today.", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "Greece", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Turkey", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Syria", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Iran", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/f999f2a2-5450-4821-a3c8-94288720bd46", + "imageMetadata": { + "id": "f999f2a2-5450-4821-a3c8-94288720bd46", + "contentType": "image/*", + "resources": "Zee Prime at cs.wikipedia [GFDL (http://www.gnu.org/copyleft/fdl.html), CC-BY-SA-3.0 (http://creativecommons.org/licenses/by-sa/3.0/) or CC BY-SA 2.5 (https://creativecommons.org/licenses/by-sa/2.5)], from Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/1/1d/Miniaturk_009.jpg", + "effects": [] + }, + "resources": "Zee Prime at cs.wikipedia [GFDL (http://www.gnu.org/copyleft/fdl.html), CC-BY-SA-3.0 (http://creativecommons.org/licenses/by-sa/3.0/) or CC BY-SA 2.5 (https://creativecommons.org/licenses/by-sa/2.5)], from Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/1/1d/Miniaturk_009.jpg", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "The second Temple of Artemis was burnt down by Herostratus. Why did he set the temple on fire?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "To become famous", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "It was an accident", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "He was angry at the gods", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Because of a bet", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/fe2c5c06-6d2e-4a5a-9441-a9c77391130e_opt", + "imageMetadata": { + "id": "fe2c5c06-6d2e-4a5a-9441-a9c77391130e", + "contentType": "image/*", + "resources": " [Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/a/a9/Temple_of_Artemis.jpg", + "effects": [] + }, + "resources": " [Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/a/a9/Temple_of_Artemis.jpg", + "video": {"id": "", "startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "The Statue of Zeus was built by the sculptor Phidias. Where was it located?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "Sparta", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Athens", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Olympia", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Delphi", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/9074a275-1874-4cb9-9c9f-248173ceae9d", + "imageMetadata": { + "id": "9074a275-1874-4cb9-9c9f-248173ceae9d", + "contentType": "image/*", + "resources": " [Public domain or Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/6/66/Le_Jupiter_Olympien_ou_l%27art_de_la_sculpture_antique.jpg", + "effects": [], + "crop": {"origin": {"x": 53, "y": 0}, "target": {"x": 577, "y": 866}, "circular": false} + }, + "resources": " [Public domain or Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/6/66/Le_Jupiter_Olympien_ou_l%27art_de_la_sculpture_antique.jpg", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "The Mausoleum at Halicarnassus was a tomb for a Persian governor. Who was buried there?", + "time": 20000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "Darius", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Xerxes", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Cyrus", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Mausoleus", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/38f43ef3-4507-4f11-ae33-f3e833a47d19", + "imageMetadata": { + "id": "38f43ef3-4507-4f11-ae33-f3e833a47d19", + "altText": "Yellow Question Mark on Purple Background, Paper Craft", + "contentType": "image/jpeg", + "origin": "Getty Images", + "externalRef": "1501192535", + "resources": "MirageC/Moment/Getty Images", + "width": 2120, + "height": 1414 + }, + "resources": "MirageC/Moment/Getty Images", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "open_ended", + "question": "The Colossus of Rhodes was 108 feet (33 metres) high. Which God was it based on?", + "time": 60000, + "pointsMultiplier": 2, + "choices": [ + {"answer": "Helios", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "helios", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/d4ccbf4e-1026-46ad-ab35-84dc17c4d3a0_opt", + "imageMetadata": { + "id": "d4ccbf4e-1026-46ad-ab35-84dc17c4d3a0", + "contentType": "image/*", + "resources": "By gravure sur bois de Sidney Barclay numérisée Google [Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/5/5f/Colosse_de_Rhodes_%28Barclay%29.jpg", + "effects": [], + "crop": {"origin": {"x": 49, "y": 83}, "target": {"x": 531, "y": 796}, "circular": false} + }, + "resources": "By gravure sur bois de Sidney Barclay numérisée Google [Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/5/5f/Colosse_de_Rhodes_%28Barclay%29.jpg", + "video": {"id": "", "startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "The Lighthouse of Alexandria was destroyed in the 14th century. How?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "Fire", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Earthquake", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Tidal Wave", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "Storm", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/e2d22765-942b-4dbd-9fd6-d71142d775c3", + "imageMetadata": { + "id": "e2d22765-942b-4dbd-9fd6-d71142d775c3", + "contentType": "image/*", + "resources": "Emad Victor SHENOUDA [Attribution], from Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/3/33/PHAROS2013-3000x2250.jpg", + "effects": [], + "crop": {"origin": {"x": 0, "y": 10}, "target": {"x": 1024, "y": 683}, "circular": false} + }, + "resources": "Emad Victor SHENOUDA [Attribution], from Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/3/33/PHAROS2013-3000x2250.jpg", + "video": {"startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "Which of the seven wonders was the tallest?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "The Colossus of Rhodes", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "The Lighthouse of Alexandria", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "The Mausoleum at Halicarnassus", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "The Great Pyramid of Giza", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/19382163-196f-495d-9a84-c2d8c3fd716c", + "imageMetadata": { + "id": "19382163-196f-495d-9a84-c2d8c3fd716c", + "contentType": "image/*", + "resources": "By The original uploader was Mark22 at English Wikipedia (Transferred from en.wikipedia to Commons.) [Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/b/b7/SevenWondersOfTheWorld.png", + "effects": [], + "crop": {"origin": {"x": 19, "y": 0}, "target": {"x": 491, "y": 736}, "circular": false} + }, + "resources": "By The original uploader was Mark22 at English Wikipedia (Transferred from en.wikipedia to Commons.) [Public domain], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/b/b7/SevenWondersOfTheWorld.png", + "video": {"id": "", "startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "slider", + "question": "How many of the Seven Wonders still exist?", + "time": 20000, + "pointsMultiplier": 2, + "choiceRange": {"start": 0, "end": 7, "step": 1, "correct": 1, "tolerance": 0}, + "image": "https://media.kahoot.it/b431b3aa-4a46-49c9-b4ac-aa1dde40333f", + "imageMetadata": { + "id": "b431b3aa-4a46-49c9-b4ac-aa1dde40333f", + "contentType": "image/*", + "resources": "By Kandi [GFDL (http://www.gnu.org/copyleft/fdl.html) or CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], from Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/a/a4/Seven_Wonders_of_the_Ancient_World.png", + "effects": [] + }, + "resources": "By Kandi [GFDL (http://www.gnu.org/copyleft/fdl.html) or CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], from Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/a/a4/Seven_Wonders_of_the_Ancient_World.png", + "video": {"id": "", "startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + }, + { + "type": "quiz", + "question": "Which of the Seven Wonders is the only one that still exists?", + "time": 30000, + "points": true, + "pointsMultiplier": 1, + "choices": [ + {"answer": "The Great Pyramid of Giza", "correct": true, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "The Temple of Artemis", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "The Mausoleum at Halicarnassus", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}}, + {"answer": "The Colossus of Rhodes", "correct": false, "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}} + ], + "image": "https://media.kahoot.it/34b01038-031c-4d23-b8a0-55402916586f_opt", + "imageMetadata": { + "id": "34b01038-031c-4d23-b8a0-55402916586f", + "contentType": "image/*", + "resources": "By Varios [CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/d/d6/Siete_maravillas_antiguas.jpg", + "effects": [], + "crop": {"origin": {"x": 19, "y": 0}, "target": {"x": 491, "y": 736}, "circular": false} + }, + "resources": "By Varios [CC BY-SA 3.0 (https://creativecommons.org/licenses/by-sa/3.0)], via Wikimedia Commons\nhttps://upload.wikimedia.org/wikipedia/commons/d/d6/Siete_maravillas_antiguas.jpg", + "video": {"id": "", "startTime": 0, "endTime": 0, "service": "youtube", "fullUrl": ""}, + "questionFormat": 0, + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "media": [] + } + ], + "contentTags": {"curriculumCodes": [], "generatedCurriculumCodes": []}, + "metadata": { + "access": { + "groupRead": [ + "b5c71d39-c229-4eeb-8648-cd8518ec068a", + "99159f29-7004-460b-a87a-cd4aab39ba4c", + "4597f49e-8b3b-40a5-985a-e6da01761947", + "ff2abab7-29f9-4669-8085-66e1089045a0", + "4caead57-3dd8-41c0-8f60-c5da69881b8e", + "36022fd9-43e1-4b36-9c98-a6a3b2b53038" + ], + "folderGroupIds": [], + "features": ["PremiumEduContent"] + }, + "duplicationProtection": true, + "featuredListMemberships": [ + {"list": "youngfeatured", "addedAt": 1682336780289}, + {"list": "featured", "addedAt": 1682336738189} + ], + "lastEdit": { + "editorUserId": "4c1574ee-de54-40a2-be15-8d72b333afad", + "editorUsername": "KahootStudio", + "editTimestamp": 1727277651459 + }, + "versionMetadata": { + "version": 4, + "created": 1727277651160, + "creator": "4c1574ee-de54-40a2-be15-8d72b333afad" + } + }, + "resources": "Nick Brundle Photography/Moment/Getty Images", + "slug": "seven-wonders-of-the-ancient-world", + "languageInfo": {"language": "en-US", "lastUpdatedOn": 1741920189202, "readAloudSupported": true}, + "inventoryItemIds": [], + "channels": [{"id": "247c3eb4-af80-4c1f-b006-558682c7bd2f"}], + "isValid": true, + "playAsGuest": true, + "hasRestrictedContent": false, + "type": "quiz", + "created": 1527169083018, + "modified": 1754523196463 +} \ No newline at end of file diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts new file mode 100644 index 0000000..be201de --- /dev/null +++ b/domain/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + alias(libs.plugins.kahootquiz.android.library) + alias(libs.plugins.kahootquiz.android.library.hilt) +} + +android { + namespace = "dev.adriankuta.kahootquiz.domain" +} + +dependencies { +} diff --git a/domain/config/detekt/detekt.yml b/domain/config/detekt/detekt.yml new file mode 100644 index 0000000..809b757 --- /dev/null +++ b/domain/config/detekt/detekt.yml @@ -0,0 +1,10 @@ +# Deviations from defaults +formatting: + TrailingCommaOnCallSite: + active: true + autoCorrect: true + useTrailingCommaOnCallSite: true + TrailingCommaOnDeclarationSite: + active: true + autoCorrect: true + useTrailingCommaOnDeclarationSite: true \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4339f08..242de8d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,4 +1,5 @@ [versions] +retrofit = "3.0.0" targetSdk = "36" compileSdk = "36" minSdk = "23" @@ -115,6 +116,8 @@ kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx- material = { group = "com.google.android.material", name = "material", version.ref = "material" } mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockk" } play-services-ads = { module = "com.google.android.gms:play-services-ads", version.ref = "playServicesAds" } +retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } +retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } review-ktx = { module = "com.google.android.play:review-ktx", version.ref = "reviewKtx" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } diff --git a/model/data/build.gradle.kts b/model/data/build.gradle.kts new file mode 100644 index 0000000..09eaf63 --- /dev/null +++ b/model/data/build.gradle.kts @@ -0,0 +1,13 @@ +plugins { + alias(libs.plugins.kahootquiz.android.library) + alias(libs.plugins.kahootquiz.android.library.hilt) +} + +android { + namespace = "dev.adriankuta.kahootquiz.model.data" +} + +dependencies { + implementation(projects.core.network) + implementation(projects.domain) +} diff --git a/model/data/config/detekt/detekt.yml b/model/data/config/detekt/detekt.yml new file mode 100644 index 0000000..809b757 --- /dev/null +++ b/model/data/config/detekt/detekt.yml @@ -0,0 +1,10 @@ +# Deviations from defaults +formatting: + TrailingCommaOnCallSite: + active: true + autoCorrect: true + useTrailingCommaOnCallSite: true + TrailingCommaOnDeclarationSite: + active: true + autoCorrect: true + useTrailingCommaOnDeclarationSite: true \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index febf40f..6a10fb0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -25,3 +25,7 @@ rootProject.name = "KahootQuiz" enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") include(":app") +include(":core:network") +include(":domain") +include(":model:data") +include(":ui:quiz") diff --git a/ui/config/detekt/detekt.yml b/ui/config/detekt/detekt.yml new file mode 100644 index 0000000..809b757 --- /dev/null +++ b/ui/config/detekt/detekt.yml @@ -0,0 +1,10 @@ +# Deviations from defaults +formatting: + TrailingCommaOnCallSite: + active: true + autoCorrect: true + useTrailingCommaOnCallSite: true + TrailingCommaOnDeclarationSite: + active: true + autoCorrect: true + useTrailingCommaOnDeclarationSite: true \ No newline at end of file diff --git a/ui/quiz/build.gradle.kts b/ui/quiz/build.gradle.kts new file mode 100644 index 0000000..20f0afc --- /dev/null +++ b/ui/quiz/build.gradle.kts @@ -0,0 +1,12 @@ +plugins { + alias(libs.plugins.kahootquiz.android.library.compose) + alias(libs.plugins.kahootquiz.android.library.hilt) +} + +android { + namespace = "dev.adriankuta.kahootquiz.ui.quiz" +} + +dependencies { + implementation(projects.domain) +} diff --git a/ui/quiz/config/detekt/detekt.yml b/ui/quiz/config/detekt/detekt.yml new file mode 100644 index 0000000..809b757 --- /dev/null +++ b/ui/quiz/config/detekt/detekt.yml @@ -0,0 +1,10 @@ +# Deviations from defaults +formatting: + TrailingCommaOnCallSite: + active: true + autoCorrect: true + useTrailingCommaOnCallSite: true + TrailingCommaOnDeclarationSite: + active: true + autoCorrect: true + useTrailingCommaOnDeclarationSite: true \ No newline at end of file