From 3f13a45d2cc5f8e646da60e2852504c42122818a Mon Sep 17 00:00:00 2001 From: Adam McNeilly Date: Sun, 1 Dec 2024 00:20:21 -0500 Subject: [PATCH] Adding base ktor client and some octane GG models. (#577) --- gradle/libs.versions.toml | 3 +- shared/app/build.gradle.kts | 13 +++ .../shared/app/core/models/Event.kt | 22 ++-- .../shared/app/core/models/EventStage.kt | 18 +-- .../data/octanegg/OctaneGGEventTierMapper.kt | 21 ++++ .../app/data/octanegg/OctaneGGKtorClient.kt | 10 ++ .../app/data/octanegg/OctaneGGRegionMapper.kt | 24 ++++ .../app/data/octanegg/dto/OctaneGGAccount.kt | 15 +++ .../octanegg/dto/OctaneGGAdvancedStats.kt | 17 +++ .../data/octanegg/dto/OctaneGGBallStats.kt | 15 +++ .../data/octanegg/dto/OctaneGGBoostStats.kt | 67 +++++++++++ .../data/octanegg/dto/OctaneGGCoreStats.kt | 38 +++++++ .../data/octanegg/dto/OctaneGGDemoStats.kt | 15 +++ .../app/data/octanegg/dto/OctaneGGEvent.kt | 105 ++++++++++++++++++ .../octanegg/dto/OctaneGGEventListResponse.kt | 19 ++++ .../octanegg/dto/OctaneGGEventParticipants.kt | 13 +++ .../app/data/octanegg/dto/OctaneGGFormat.kt | 26 +++++ .../app/data/octanegg/dto/OctaneGGGame.kt | 54 +++++++++ .../octanegg/dto/OctaneGGGameListResponse.kt | 13 +++ .../data/octanegg/dto/OctaneGGGameOverview.kt | 33 ++++++ .../octanegg/dto/OctaneGGGameTeamResult.kt | 36 ++++++ .../app/data/octanegg/dto/OctaneGGLocation.kt | 29 +++++ .../app/data/octanegg/dto/OctaneGGMap.kt | 15 +++ .../app/data/octanegg/dto/OctaneGGMatch.kt | 53 +++++++++ .../data/octanegg/dto/OctaneGGMatchFormat.kt | 18 +++ .../octanegg/dto/OctaneGGMatchListResponse.kt | 19 ++++ .../octanegg/dto/OctaneGGMatchTeamResult.kt | 39 +++++++ .../octanegg/dto/OctaneGGMovementStats.kt | 47 ++++++++ .../app/data/octanegg/dto/OctaneGGPlayer.kt | 48 ++++++++ .../dto/OctaneGGPlayerListResponse.kt | 19 ++++ .../data/octanegg/dto/OctaneGGPlayerStats.kt | 34 ++++++ .../octanegg/dto/OctaneGGPositioningStats.kt | 65 +++++++++++ .../app/data/octanegg/dto/OctaneGGPrize.kt | 26 +++++ .../app/data/octanegg/dto/OctaneGGStage.kt | 49 ++++++++ .../app/data/octanegg/dto/OctaneGGStats.kt | 32 ++++++ .../data/octanegg/dto/OctaneGGTeamDetail.kt | 23 ++++ .../octanegg/dto/OctaneGGTeamListResponse.kt | 19 ++++ .../data/octanegg/dto/OctaneGGTeamOverview.kt | 38 +++++++ .../data/octanegg/dto/OctaneGGTeamStats.kt | 15 +++ .../shared/app/data/remote/BaseKtorClient.kt | 90 +++++++++++++++ 40 files changed, 1234 insertions(+), 21 deletions(-) create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGEventTierMapper.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGKtorClient.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGRegionMapper.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAccount.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAdvancedStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBallStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBoostStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGCoreStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGDemoStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEvent.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventListResponse.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventParticipants.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGFormat.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGame.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameListResponse.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameOverview.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameTeamResult.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGLocation.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMap.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatch.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchFormat.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchListResponse.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchTeamResult.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMovementStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayer.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerListResponse.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPositioningStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPrize.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStage.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamDetail.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamListResponse.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamOverview.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamStats.kt create mode 100644 shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/remote/BaseKtorClient.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 30aa87ca..d3873181 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -132,4 +132,5 @@ cash-paparazzi = { id = "app.cash.paparazzi", version.ref = "paparazzi" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.compose", version.ref = "kmpCompose" } -kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter" } \ No newline at end of file +kotlinter = { id = "org.jmailen.kotlinter", version.ref = "kotlinter" } +kotlinx-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } \ No newline at end of file diff --git a/shared/app/build.gradle.kts b/shared/app/build.gradle.kts index f27303fe..a6456734 100644 --- a/shared/app/build.gradle.kts +++ b/shared/app/build.gradle.kts @@ -1,5 +1,6 @@ plugins { kotlin("multiplatform") + kotlin("plugin.serialization") id("com.android.library") alias(libs.plugins.compose.compiler) alias(libs.plugins.kotlin.compose) @@ -29,12 +30,24 @@ kotlin { implementation(libs.cketti.codepoints) implementation(libs.koin.core) implementation(libs.kotlinx.datetime) + implementation(libs.ktor.client.content.negotiation) + implementation(libs.ktor.client.core) + implementation(libs.ktor.client.logging) + implementation(libs.ktor.serialization.kotlinx.json) } commonTest.dependencies { implementation(kotlin("test")) implementation(libs.varabyte.truthish) } + + androidMain.dependencies { + implementation(libs.ktor.client.android) + } + + iosMain.dependencies { + implementation(libs.ktor.client.ios) + } } } diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/Event.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/Event.kt index 64dc6fa9..77a642de 100644 --- a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/Event.kt +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/Event.kt @@ -18,15 +18,15 @@ package com.adammcneilly.pocketleague.shared.app.core.models * @property[prize] The amount of money awarded to the winners of the event. */ data class Event( - val id: String, - val name: String, - val startDateUTC: String?, - val endDateUTC: String?, - val imageURL: String?, - val stages: List, - val tier: EventTier, - val mode: String, - val region: Region, - val lan: Boolean, - val prize: Prize?, + val id: String = "", + val name: String = "", + val startDateUTC: String? = null, + val endDateUTC: String? = null, + val imageURL: String? = null, + val stages: List = emptyList(), + val tier: EventTier = EventTier.Unknown, + val mode: String = "", + val region: Region = Region.Unknown, + val lan: Boolean = false, + val prize: Prize? = null, ) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/EventStage.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/EventStage.kt index 32afdab3..3e04888e 100644 --- a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/EventStage.kt +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/core/models/EventStage.kt @@ -14,13 +14,13 @@ package com.adammcneilly.pocketleague.shared.app.core.models * @property[location] The location of this stage if it is a [lan]. */ data class EventStage( - val id: String, - val name: String, - val region: String, - val startDateUTC: String?, - val endDateUTC: String?, - val liquipedia: String, - val qualifier: Boolean, - val lan: Boolean, - val location: Location?, + val id: String = "", + val name: String = "", + val region: String = "", + val startDateUTC: String? = null, + val endDateUTC: String? = null, + val liquipedia: String = "", + val qualifier: Boolean = false, + val lan: Boolean = false, + val location: Location? = null, ) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGEventTierMapper.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGEventTierMapper.kt new file mode 100644 index 00000000..80e2b20a --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGEventTierMapper.kt @@ -0,0 +1,21 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg + +import com.adammcneilly.pocketleague.shared.app.core.models.EventTier + +object OctaneGGEventTierMapper { + /** + * Converts a tier string from the Octane.gg api to an [EventTier] enum entry. + */ + fun fromString( + tier: String, + ): EventTier { + return when (tier) { + "S" -> EventTier.S + "A" -> EventTier.A + "B" -> EventTier.B + "C" -> EventTier.C + "D" -> EventTier.D + else -> EventTier.Unknown + } + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGKtorClient.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGKtorClient.kt new file mode 100644 index 00000000..63c03aad --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGKtorClient.kt @@ -0,0 +1,10 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg + +import com.adammcneilly.pocketleague.shared.app.data.remote.BaseKtorClient + +/** + * An instance of a [BaseKtorClient] that makes all requests to the octane.gg API. + */ +object OctaneGGKtorClient : BaseKtorClient( + baseURL = "https://zsr.octane.gg/", +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGRegionMapper.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGRegionMapper.kt new file mode 100644 index 00000000..28236b25 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/OctaneGGRegionMapper.kt @@ -0,0 +1,24 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg + +import com.adammcneilly.pocketleague.shared.app.core.models.Region + +object OctaneGGRegionMapper { + /** + * Converts a region string from the Octane.gg api to a [Region] enum entry. + */ + fun fromString( + region: String, + ): Region { + return when (region) { + "NA" -> Region.NA + "EU" -> Region.EU + "OCE" -> Region.OCE + "SAM" -> Region.SAM + "ASIA" -> Region.APAC + "ME" -> Region.MENA + "INT" -> Region.INT + "AF" -> Region.SSA + else -> Region.Unknown + } + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAccount.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAccount.kt new file mode 100644 index 00000000..e2eef3e7 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAccount.kt @@ -0,0 +1,15 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a player account within the octane.gg API. + */ +@Serializable +data class OctaneGGAccount( + @SerialName("id") + val id: String? = null, + @SerialName("platform") + val platform: String? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAdvancedStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAdvancedStats.kt new file mode 100644 index 00000000..a276d6e7 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGAdvancedStats.kt @@ -0,0 +1,17 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Advanced statistics for a player or a team. + */ +@Serializable +data class OctaneGGAdvancedStats( + @SerialName("goalParticipation") + val goalParticipation: Double? = null, + @SerialName("mvp") + val mvp: Boolean? = null, + @SerialName("rating") + val rating: Double? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBallStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBallStats.kt new file mode 100644 index 00000000..1c66e9d1 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBallStats.kt @@ -0,0 +1,15 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Ball and possession related stats for a player or team. + */ +@Serializable +data class OctaneGGBallStats( + @SerialName("possessionTime") + val possessionTime: Double? = null, + @SerialName("timeInSide") + val timeInSide: Double? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBoostStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBoostStats.kt new file mode 100644 index 00000000..de277a52 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGBoostStats.kt @@ -0,0 +1,67 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Statistics about boost management for a player or team. + */ +@Serializable +data class OctaneGGBoostStats( + @SerialName("amountCollected") + val amountCollected: Int? = null, + @SerialName("amountCollectedBig") + val amountCollectedBig: Int? = null, + @SerialName("amountCollectedSmall") + val amountCollectedSmall: Int? = null, + @SerialName("amountOverfill") + val amountOverfill: Int? = null, + @SerialName("amountOverfillStolen") + val amountOverfillStolen: Int? = null, + @SerialName("amountStolen") + val amountStolen: Int? = null, + @SerialName("amountStolenBig") + val amountStolenBig: Int? = null, + @SerialName("amountStolenSmall") + val amountStolenSmall: Int? = null, + @SerialName("amountUsedWhileSupersonic") + val amountUsedWhileSupersonic: Int? = null, + @SerialName("avgAmount") + val avgAmount: Double? = null, + @SerialName("bcpm") + val bcpm: Double? = null, + @SerialName("bpm") + val bpm: Int? = null, + @SerialName("countCollectedBig") + val countCollectedBig: Int? = null, + @SerialName("countCollectedSmall") + val countCollectedSmall: Int? = null, + @SerialName("countStolenBig") + val countStolenBig: Int? = null, + @SerialName("countStolenSmall") + val countStolenSmall: Int? = null, + @SerialName("percentBoost0To25") + val percentBoost0To25: Double? = null, + @SerialName("percentBoost25To50") + val percentBoost25To50: Double? = null, + @SerialName("percentBoost50To75") + val percentBoost50To75: Double? = null, + @SerialName("percentBoost75To100") + val percentBoost75To100: Double? = null, + @SerialName("percentFullBoost") + val percentFullBoost: Double? = null, + @SerialName("percentZeroBoost") + val percentZeroBoost: Double? = null, + @SerialName("timeBoost0To25") + val timeBoost0To25: Double? = null, + @SerialName("timeBoost25To50") + val timeBoost25To50: Double? = null, + @SerialName("timeBoost50To75") + val timeBoost50To75: Double? = null, + @SerialName("timeBoost75To100") + val timeBoost75To100: Double? = null, + @SerialName("timeFullBoost") + val timeFullBoost: Double? = null, + @SerialName("timeZeroBoost") + val timeZeroBoost: Double? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGCoreStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGCoreStats.kt new file mode 100644 index 00000000..966ffe83 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGCoreStats.kt @@ -0,0 +1,38 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.CoreStats +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * The core stats to track for a player or a team in any given match or stretch of time. + */ +@Serializable +data class OctaneGGCoreStats( + @SerialName("shots") + val shots: Int? = null, + @SerialName("goals") + val goals: Int? = null, + @SerialName("saves") + val saves: Int? = null, + @SerialName("assists") + val assists: Int? = null, + @SerialName("score") + val score: Int? = null, + @SerialName("shootingPercentage") + val shootingPercentage: Float? = null, +) { + /** + * Converts an [OctaneGGCoreStats] entity to a [CoreStats] entity. + */ + fun toCoreStats(): CoreStats { + return CoreStats( + shots = this.shots ?: 0, + goals = this.goals ?: 0, + saves = this.saves ?: 0, + assists = this.assists ?: 0, + score = this.score ?: 0, + shootingPercentage = this.shootingPercentage ?: 0F, + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGDemoStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGDemoStats.kt new file mode 100644 index 00000000..677355a0 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGDemoStats.kt @@ -0,0 +1,15 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Statistics for demolitions for a player or team. + */ +@Serializable +data class OctaneGGDemoStats( + @SerialName("inflicted") + val inflicted: Int? = null, + @SerialName("taken") + val taken: Int? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEvent.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEvent.kt new file mode 100644 index 00000000..cac79153 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEvent.kt @@ -0,0 +1,105 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Event +import com.adammcneilly.pocketleague.shared.app.core.models.EventStage +import com.adammcneilly.pocketleague.shared.app.core.models.Region +import com.adammcneilly.pocketleague.shared.app.data.octanegg.OctaneGGEventTierMapper +import com.adammcneilly.pocketleague.shared.app.data.octanegg.OctaneGGRegionMapper +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data class mapping to an Event from the octane.gg API. + */ +@Serializable +data class OctaneGGEvent( + @SerialName("_id") + val id: String? = null, + @SerialName("endDate") + val endDateUTC: String? = null, + @SerialName("image") + val imageURL: String? = null, + @SerialName("mode") + val mode: Int? = null, + @SerialName("name") + val name: String? = null, + @SerialName("prize") + val prize: OctaneGGPrize? = null, + @SerialName("region") + val region: String? = null, + @SerialName("slug") + val slug: String? = null, + @SerialName("stages") + val stages: List? = null, + @SerialName("startDate") + val startDateUTC: String? = null, + @SerialName("tier") + val tier: String? = null, + @SerialName("groups") + val groups: List? = null, + @SerialName("lan") + val lan: Boolean? = null, +) { + /** + * Convert an [OctaneGGEvent] to an [Event] in our domain. + */ + fun toEvent(): Event { + val stages = this.stages?.map(OctaneGGStage::toEventStage).orEmpty() + + val isLan = stages.any(EventStage::lan) + + val eventRegion = OctaneGGRegionMapper.fromString(this.region.orEmpty()) + + return Event( + id = this.id.orEmpty(), + name = remapEventName( + octaneEventName = this.name.orEmpty(), + region = eventRegion, + ), + startDateUTC = this.startDateUTC, + endDateUTC = this.endDateUTC, + imageURL = this.imageURL, + stages = stages, + tier = OctaneGGEventTierMapper.fromString(this.tier.orEmpty()), + mode = this.mode?.toString().orEmpty(), + region = eventRegion, + lan = isLan, + prize = this.prize?.toPrize(), + ) + } +} + +/** + * Given an [octaneEventName], check to see if this is a regional event, and if so, modify the event name to the Pocket League + * preferred format of regional event names. + * + * Octane provides a name in the format: RLCS 2022-23 Winter North America Regional 3 + * We want to store it as: NA Winter Invitational + * + * SIDE NOTE: Ideally we still keep track of season info somehow, in case we ever want to fetch events for a specific + * season. Removing it from the event name does create a little tech debt there. + */ +private fun remapEventName( + octaneEventName: String, + region: Region, +): String { + if (!octaneEventName.contains("regional", ignoreCase = true)) { + return octaneEventName + } + + val words = octaneEventName.split(" ") + + val splitName = words[2] + val regionalName = when (words.last()) { + "1" -> "Open" + "2" -> "Cup" + "3" -> "Invitational" + else -> { + println("Unable to properly parse regional: $octaneEventName") + return octaneEventName + } + } + val eventRegionAcronym = region.name + + return "$eventRegionAcronym $splitName $regionalName" +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventListResponse.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventListResponse.kt new file mode 100644 index 00000000..b8d221dc --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventListResponse.kt @@ -0,0 +1,19 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A DTO that maps to an event list response from the octane.gg API. + */ +@Serializable +data class OctaneGGEventListResponse( + @SerialName("events") + val events: List? = null, + @SerialName("page") + val page: Int? = null, + @SerialName("perPage") + val perPage: Int? = null, + @SerialName("pageSize") + val pageSize: Int? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventParticipants.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventParticipants.kt new file mode 100644 index 00000000..e724d233 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGEventParticipants.kt @@ -0,0 +1,13 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A list of [participants] within some event. + */ +@Serializable +data class OctaneGGEventParticipants( + @SerialName("participants") + val participants: List, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGFormat.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGFormat.kt new file mode 100644 index 00000000..601bc378 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGFormat.kt @@ -0,0 +1,26 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Format +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Defines the format of a match between two teams. + */ +@Serializable +data class OctaneGGFormat( + @SerialName("type") + val type: String? = null, + @SerialName("length") + val length: Int? = null, +) { + /** + * Convert a format from the octane.gg domain to ours. + */ + fun toFormat(): Format { + return Format( + type = this.type.orEmpty(), + length = this.length ?: 0, + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGame.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGame.kt new file mode 100644 index 00000000..bd90b589 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGame.kt @@ -0,0 +1,54 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Game +import com.adammcneilly.pocketleague.shared.app.core.models.GameTeamResult +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A game played within a [OctaneGGMatch]. + */ +@Serializable +data class OctaneGGGame( + @SerialName("ballchasing") + val ballchasing: String? = null, + @SerialName("blue") + val blue: OctaneGGGameTeamResult? = null, + @SerialName("date") + val date: String? = null, + @SerialName("duration") + val duration: Int? = null, + @SerialName("_id") + val id: String? = null, + @SerialName("map") + val map: OctaneGGMap? = null, + @SerialName("match") + val match: OctaneGGMatch? = null, + @SerialName("number") + val number: Int? = null, + @SerialName("octane_id") + val octaneId: String? = null, + @SerialName("orange") + val orange: OctaneGGGameTeamResult? = null, +) { + /** + * Converts an [OctaneGGGame] to a [Game] in our domain. + */ + fun toGame(): Game { + // Currently the octane.gg api does not include a map name for + // this map ID, so let's override it ourselves. + val mapName = when (this.map?.id) { + "outlaw_oasis_p" -> "Deadeye Canyon (Oasis)" + else -> this.map?.name.orEmpty() + } + + return Game( + id = this.id.orEmpty(), + blue = this.blue?.toGameTeamResult() ?: GameTeamResult(), + orange = this.orange?.toGameTeamResult() ?: GameTeamResult(), + map = mapName, + number = this.number ?: 0, + duration = this.duration ?: Game.GAME_DEFAULT_DURATION_SECONDS, + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameListResponse.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameListResponse.kt new file mode 100644 index 00000000..e8e4825c --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameListResponse.kt @@ -0,0 +1,13 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Returns a list of [games] from the API. + */ +@Serializable +data class OctaneGGGameListResponse( + @SerialName("games") + val games: List? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameOverview.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameOverview.kt new file mode 100644 index 00000000..6ab2a147 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameOverview.kt @@ -0,0 +1,33 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.GameOverview +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Provided by the octane.gg api to get overview + * information on a game. + */ +@Serializable +data class OctaneGGGameOverview( + @SerialName("_id") + val id: String? = null, + @SerialName("blue") + val blueScore: Int? = null, + @SerialName("orange") + val orangeScore: Int? = null, + @SerialName("duration") + val durationSeconds: Int? = null, +) { + /** + * Convert a game overview from octane domain to ours. + */ + fun toGameOverview(): GameOverview { + return GameOverview( + id = this.id.orEmpty(), + blueScore = this.blueScore ?: 0, + orangeScore = this.orangeScore ?: 0, + durationSeconds = this.durationSeconds ?: 0, + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameTeamResult.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameTeamResult.kt new file mode 100644 index 00000000..593fb9f9 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGGameTeamResult.kt @@ -0,0 +1,36 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.GameTeamResult +import com.adammcneilly.pocketleague.shared.app.core.models.Stats +import com.adammcneilly.pocketleague.shared.app.core.models.Team +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * The result of a [team] inside an [OctaneGGGame]. + */ +@Serializable +data class OctaneGGGameTeamResult( + @SerialName("winner") + val gameWinner: Boolean? = null, + @SerialName("matchWinner") + val matchWinner: Boolean? = null, + @SerialName("players") + val players: List? = null, + @SerialName("team") + val team: OctaneGGTeamStats? = null, +) { + /** + * Convert an [OctaneGGGameTeamResult] to a [GameTeamResult] in our domain. + */ + fun toGameTeamResult(): GameTeamResult { + return GameTeamResult( + goals = this.team?.stats?.core?.goals ?: 0, + winner = this.gameWinner == true, + team = this.team?.team?.toTeam() ?: Team(), + matchWinner = this.matchWinner == true, + teamStats = this.team?.stats?.toStats() ?: Stats(), + players = this.players?.map(OctaneGGPlayerStats::toGamePlayerResult).orEmpty(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGLocation.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGLocation.kt new file mode 100644 index 00000000..3af96387 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGLocation.kt @@ -0,0 +1,29 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Location +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data class mapping to a location from the octane.gg API. + */ +@Serializable +data class OctaneGGLocation( + @SerialName("city") + val city: String? = null, + @SerialName("country") + val countryCode: String? = null, + @SerialName("venue") + val venue: String? = null, +) { + /** + * Converts a location from the octane.gg domain to ours. + */ + fun toLocation(): Location { + return Location( + venue = this.venue.orEmpty(), + city = this.city.orEmpty(), + countryCode = this.countryCode.orEmpty(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMap.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMap.kt new file mode 100644 index 00000000..49895da4 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMap.kt @@ -0,0 +1,15 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Information about a map that a game was played on. + */ +@Serializable +data class OctaneGGMap( + @SerialName("id") + val id: String? = null, + @SerialName("name") + val name: String? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatch.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatch.kt new file mode 100644 index 00000000..50a028b9 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatch.kt @@ -0,0 +1,53 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Event +import com.adammcneilly.pocketleague.shared.app.core.models.EventStage +import com.adammcneilly.pocketleague.shared.app.core.models.Format +import com.adammcneilly.pocketleague.shared.app.core.models.Match +import com.adammcneilly.pocketleague.shared.app.core.models.MatchTeamResult +import com.adammcneilly.pocketleague.shared.app.core.models.StageRound +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a match between two teams, [blue] and [orange]. + */ +@Serializable +data class OctaneGGMatch( + @SerialName("_id") + val id: String? = null, + @SerialName("slug") + val slug: String? = null, + @SerialName("event") + val event: OctaneGGEvent? = null, + @SerialName("date") + val dateUTC: String? = null, + @SerialName("blue") + val blue: OctaneGGMatchTeamResult? = null, + @SerialName("orange") + val orange: OctaneGGMatchTeamResult? = null, + @SerialName("stage") + val stage: OctaneGGStage? = null, + @SerialName("format") + val format: OctaneGGFormat? = null, + @SerialName("games") + val games: List? = null, +) { + /** + * Converts an [OctaneGGMatch] to a [Match] in our domain. + */ + fun toMatch(): Match { + return Match( + id = this.id.orEmpty(), + event = this.event?.toEvent() ?: Event(), + dateUTC = this.dateUTC, + blueTeam = this.blue?.toMatchTeamResult() ?: MatchTeamResult(), + orangeTeam = this.orange?.toMatchTeamResult() ?: MatchTeamResult(), + stage = this.stage?.toEventStage() ?: EventStage(), + format = this.format?.toFormat() ?: Format(), + gameOverviews = this.games?.map(OctaneGGGameOverview::toGameOverview).orEmpty(), + // Octane.GG API has no concept of a stage round, so we'll just return a default here. + round = StageRound(0, ""), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchFormat.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchFormat.kt new file mode 100644 index 00000000..470bdd59 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchFormat.kt @@ -0,0 +1,18 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Defines a format of a match between two teams. + * + * @property[length] The length of the format, so in a best of 7, this value would be seven. + * @property[type] The type of format, for example "best" would mean a best of X series. + */ +@Serializable +data class OctaneGGMatchFormat( + @SerialName("length") + val length: Int? = null, + @SerialName("type") + val type: String? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchListResponse.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchListResponse.kt new file mode 100644 index 00000000..d8a05c41 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchListResponse.kt @@ -0,0 +1,19 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A DTO that maps to a match list response from the octane.gg API. + */ +@Serializable +data class OctaneGGMatchListResponse( + @SerialName("matches") + val matches: List? = null, + @SerialName("page") + val page: Int? = null, + @SerialName("perPage") + val perPage: Int? = null, + @SerialName("pageSize") + val pageSize: Int? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchTeamResult.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchTeamResult.kt new file mode 100644 index 00000000..09258261 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMatchTeamResult.kt @@ -0,0 +1,39 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.MatchTeamResult +import com.adammcneilly.pocketleague.shared.app.core.models.Team +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Shows the result of a specific [team] within a match. + * + * @property[score] The score for this [team] during the match. + * @property[winner] If true, this [team] is the winner of the match. + * @property[team] Information about the team and their stats. + * @property[players] The players and their stats for a given match. + */ +@Serializable +data class OctaneGGMatchTeamResult( + @SerialName("score") + val score: Int? = null, + @SerialName("winner") + val winner: Boolean? = null, + @SerialName("team") + val team: OctaneGGTeamStats? = null, + @SerialName("players") + val players: List? = null, +) { + /** + * Converts an [OctaneGGMatchTeamResult] to a [MatchTeamResult] in our domain. + */ + fun toMatchTeamResult(): MatchTeamResult { + return MatchTeamResult( + score = this.score ?: 0, + winner = this.winner ?: false, + team = this.team?.team?.toTeam() ?: Team(), + stats = this.team?.stats?.toStats(), + players = this.players?.map(OctaneGGPlayerStats::toGamePlayerResult).orEmpty(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMovementStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMovementStats.kt new file mode 100644 index 00000000..2bec7e33 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGMovementStats.kt @@ -0,0 +1,47 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Detailed statistics about the movement for a given player or team. + */ +@Serializable +data class OctaneGGMovementStats( + @SerialName("avgPowerslideDuration") + val avgPowerslideDuration: Double? = null, + @SerialName("avgSpeed") + val avgSpeed: Double? = null, + @SerialName("avgSpeedPercentage") + val avgSpeedPercentage: Double? = null, + @SerialName("countPowerslide") + val countPowerslide: Double? = null, + @SerialName("percentBoostSpeed") + val percentBoostSpeed: Double? = null, + @SerialName("percentGround") + val percentGround: Double? = null, + @SerialName("percentHighAir") + val percentHighAir: Double? = null, + @SerialName("percentLowAir") + val percentLowAir: Double? = null, + @SerialName("percentSlowSpeed") + val percentSlowSpeed: Double? = null, + @SerialName("percentSupersonicSpeed") + val percentSupersonicSpeed: Double? = null, + @SerialName("timeBoostSpeed") + val timeBoostSpeed: Double? = null, + @SerialName("timeGround") + val timeGround: Double? = null, + @SerialName("timeHighAir") + val timeHighAir: Double? = null, + @SerialName("timeLowAir") + val timeLowAir: Double? = null, + @SerialName("timePowerslide") + val timePowerslide: Double? = null, + @SerialName("timeSlowSpeed") + val timeSlowSpeed: Double? = null, + @SerialName("timeSupersonicSpeed") + val timeSupersonicSpeed: Double? = null, + @SerialName("totalDistance") + val totalDistance: Double? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayer.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayer.kt new file mode 100644 index 00000000..ddc1a4b0 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayer.kt @@ -0,0 +1,48 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Player +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a person who is a player for a Team in the octane.gg API. + */ +@Serializable +data class OctaneGGPlayer( + @SerialName("accounts") + val accounts: List? = null, + @SerialName("coach") + val coach: Boolean? = null, + @SerialName("country") + val country: String? = null, + @SerialName("_id") + val id: String? = null, + @SerialName("name") + val name: String? = null, + @SerialName("relevant") + val relevant: Boolean? = null, + @SerialName("slug") + val slug: String? = null, + @SerialName("substitute") + val substitute: Boolean? = null, + @SerialName("tag") + val tag: String? = null, + @SerialName("team") + val team: OctaneGGTeamOverview? = null, +) { + /** + * Converts an [OctaneGGPlayer] to a [Player]. + */ + fun toPlayer(): Player { + return Player( + id = this.id.orEmpty(), + slug = this.slug.orEmpty(), + tag = this.tag.orEmpty(), + countryCode = this.country.orEmpty(), + name = this.name.orEmpty(), + currentTeamId = team?.id.orEmpty(), + isCoach = this.coach == true, + isSubstitute = this.substitute == true, + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerListResponse.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerListResponse.kt new file mode 100644 index 00000000..a9549bcf --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerListResponse.kt @@ -0,0 +1,19 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data response for a list of players within the octane.gg API. + */ +@Serializable +data class OctaneGGPlayerListResponse( + @SerialName("players") + val players: List? = null, + @SerialName("page") + val page: Int? = null, + @SerialName("perPage") + val perPage: Int? = null, + @SerialName("pageSize") + val pageSize: Int? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerStats.kt new file mode 100644 index 00000000..084b0f8d --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPlayerStats.kt @@ -0,0 +1,34 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.GamePlayerResult +import com.adammcneilly.pocketleague.shared.app.core.models.Player +import com.adammcneilly.pocketleague.shared.app.core.models.Stats +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Contains the core [stats] and [advanced] stats for a [player]. + */ +@Serializable +data class OctaneGGPlayerStats( + @SerialName("advanced") + val advanced: OctaneGGAdvancedStats? = null, + @SerialName("player") + val player: OctaneGGPlayer? = null, + @SerialName("stats") + val stats: OctaneGGStats? = null, +) { + /** + * Maps an [OctaneGGPlayerStats] entity, which should represent a player within a game, + * to a [GamePlayerResult] entity. + * + * It is up to the caller to make sure this is used in the right domain, as [OctaneGGPlayerStats] + * can represent a player and their stats in any situation. + */ + fun toGamePlayerResult(): GamePlayerResult { + return GamePlayerResult( + player = this.player?.toPlayer() ?: Player(), + stats = this.stats?.toStats() ?: Stats(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPositioningStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPositioningStats.kt new file mode 100644 index 00000000..83bd5a4d --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPositioningStats.kt @@ -0,0 +1,65 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Detailed stats about the positioning of a player or team. + */ +@Serializable +data class OctaneGGPositioningStats( + @SerialName("avgDistanceToBall") + val avgDistanceToBall: Double? = null, + @SerialName("avgDistanceToBallNoPossession") + val avgDistanceToBallNoPossession: Double? = null, + @SerialName("avgDistanceToBallPossession") + val avgDistanceToBallPossession: Double? = null, + @SerialName("avgDistanceToMates") + val avgDistanceToMates: Double? = null, + @SerialName("goalsAgainstWhileLastDefender") + val goalsAgainstWhileLastDefender: Double? = null, + @SerialName("percentBehindBall") + val percentBehindBall: Double? = null, + @SerialName("percentClosestToBall") + val percentClosestToBall: Double? = null, + @SerialName("percentDefensiveHalf") + val percentDefensiveHalf: Double? = null, + @SerialName("percentDefensiveThird") + val percentDefensiveThird: Double? = null, + @SerialName("percentFarthestFromBall") + val percentFarthestFromBall: Double? = null, + @SerialName("percentInfrontBall") + val percentInfrontBall: Double? = null, + @SerialName("percentMostBack") + val percentMostBack: Double? = null, + @SerialName("percentMostForward") + val percentMostForward: Double? = null, + @SerialName("percentNeutralThird") + val percentNeutralThird: Double? = null, + @SerialName("percentOffensiveHalf") + val percentOffensiveHalf: Double? = null, + @SerialName("percentOffensiveThird") + val percentOffensiveThird: Double? = null, + @SerialName("timeBehindBall") + val timeBehindBall: Double? = null, + @SerialName("timeClosestToBall") + val timeClosestToBall: Double? = null, + @SerialName("timeDefensiveHalf") + val timeDefensiveHalf: Double? = null, + @SerialName("timeDefensiveThird") + val timeDefensiveThird: Double? = null, + @SerialName("timeFarthestFromBall") + val timeFarthestFromBall: Double? = null, + @SerialName("timeInfrontBall") + val timeInfrontBall: Double? = null, + @SerialName("timeMostBack") + val timeMostBack: Double? = null, + @SerialName("timeMostForward") + val timeMostForward: Double? = null, + @SerialName("timeNeutralThird") + val timeNeutralThird: Double? = null, + @SerialName("timeOffensiveHalf") + val timeOffensiveHalf: Double? = null, + @SerialName("timeOffensiveThird") + val timeOffensiveThird: Double? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPrize.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPrize.kt new file mode 100644 index 00000000..94cd08c9 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGPrize.kt @@ -0,0 +1,26 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Prize +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data class mapping to a prize entity from the octane.gg API. + */ +@Serializable +data class OctaneGGPrize( + @SerialName("amount") + val amount: Double? = null, + @SerialName("currency") + val currency: String? = null, +) { + /** + * Converts an [OctaneGGPrize] to a [Prize] in our domain. + */ + fun toPrize(): Prize { + return Prize( + amount = this.amount ?: 0.0, + currency = this.currency.orEmpty(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStage.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStage.kt new file mode 100644 index 00000000..8e285cc9 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStage.kt @@ -0,0 +1,49 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.EventStage +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data class mapping to a stage from the octane.gg API. + */ +@Serializable +data class OctaneGGStage( + @SerialName("_id") + val id: Int? = null, + @SerialName("endDate") + val endDateUTC: String? = null, + @SerialName("name") + val name: String? = null, + @SerialName("startDate") + val startDateUTC: String? = null, + @SerialName("prize") + val prize: OctaneGGPrize? = null, + @SerialName("location") + val location: OctaneGGLocation? = null, + @SerialName("lan") + val lan: Boolean? = null, + @SerialName("liquipedia") + val liquipedia: String? = null, + @SerialName("region") + val region: String? = null, + @SerialName("qualifier") + val qualifier: Boolean? = null, +) { + /** + * Converts an [OctaneGGStage] to an [EventStage] in our domain. + */ + fun toEventStage(): EventStage { + return EventStage( + id = this.id?.toString().orEmpty(), + name = this.name.orEmpty(), + region = this.region.orEmpty(), + startDateUTC = this.startDateUTC, + endDateUTC = this.endDateUTC, + liquipedia = this.liquipedia.orEmpty(), + qualifier = this.qualifier ?: false, + lan = this.lan ?: false, + location = this.location?.toLocation(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStats.kt new file mode 100644 index 00000000..1aacc460 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGStats.kt @@ -0,0 +1,32 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.CoreStats +import com.adammcneilly.pocketleague.shared.app.core.models.Stats +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Tracks any number of statistics relevant to a player or team. + */ +@Serializable +data class OctaneGGStats( + @SerialName("boost") + val boost: OctaneGGBoostStats? = null, + @SerialName("core") + val core: OctaneGGCoreStats? = null, + @SerialName("demo") + val demo: OctaneGGDemoStats? = null, + @SerialName("movement") + val movement: OctaneGGMovementStats? = null, + @SerialName("positioning") + val positioning: OctaneGGPositioningStats? = null, +) { + /** + * Converts an [OctaneGGStats] entity to a [Stats] entity. + */ + fun toStats(): Stats { + return Stats( + core = this.core?.toCoreStats() ?: CoreStats(), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamDetail.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamDetail.kt new file mode 100644 index 00000000..87aa00dd --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamDetail.kt @@ -0,0 +1,23 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Team +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Represents a team within the octane.gg API domain. + */ +@Serializable +data class OctaneGGTeamDetail( + @SerialName("players") + val players: List? = null, + @SerialName("team") + val team: OctaneGGTeamOverview? = null, +) { + /** + * Converts an [OctaneGGTeamDetail] entity to a [Team] in our domain. + */ + fun toTeam(): Team { + return this.team?.toTeam() ?: Team() + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamListResponse.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamListResponse.kt new file mode 100644 index 00000000..ee6ce53f --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamListResponse.kt @@ -0,0 +1,19 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * A data response for a list of teams within the octane.gg API. + */ +@Serializable +data class OctaneGGTeamListResponse( + @SerialName("teams") + val teams: List? = null, + @SerialName("page") + val page: Int? = null, + @SerialName("perPage") + val perPage: Int? = null, + @SerialName("pageSize") + val pageSize: Int? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamOverview.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamOverview.kt new file mode 100644 index 00000000..7ea6c67e --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamOverview.kt @@ -0,0 +1,38 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import com.adammcneilly.pocketleague.shared.app.core.models.Team +import com.adammcneilly.pocketleague.shared.app.data.octanegg.OctaneGGRegionMapper +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Overview information about a team, that does not include roster info. + */ +@Serializable +data class OctaneGGTeamOverview( + @SerialName("_id") + val id: String? = null, + @SerialName("image") + val image: String? = null, + @SerialName("name") + val name: String? = null, + @SerialName("region") + val region: String? = null, + @SerialName("relevant") + val relevant: Boolean? = null, + @SerialName("slug") + val slug: String? = null, +) { + /** + * Converts an [OctaneGGTeamDetail] entity to a [Team] in our domain. + */ + fun toTeam(): Team { + return Team( + id = this.id.orEmpty(), + // This is sus?? + name = this.name ?: "TBD", + lightThemeImageURL = this.image, + region = OctaneGGRegionMapper.fromString(this.region.orEmpty()), + ) + } +} diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamStats.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamStats.kt new file mode 100644 index 00000000..46d7cf92 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/octanegg/dto/OctaneGGTeamStats.kt @@ -0,0 +1,15 @@ +package com.adammcneilly.pocketleague.shared.app.data.octanegg.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +/** + * Combination of a [team] and their [stats] for a match or event. + */ +@Serializable +data class OctaneGGTeamStats( + @SerialName("team") + val team: OctaneGGTeamOverview? = null, + @SerialName("stats") + val stats: OctaneGGStats? = null, +) diff --git a/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/remote/BaseKtorClient.kt b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/remote/BaseKtorClient.kt new file mode 100644 index 00000000..2fde6e24 --- /dev/null +++ b/shared/app/src/commonMain/kotlin/com/adammcneilly/pocketleague/shared/app/data/remote/BaseKtorClient.kt @@ -0,0 +1,90 @@ +package com.adammcneilly.pocketleague.shared.app.data.remote + +import io.ktor.client.HttpClient +import io.ktor.client.call.body +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.logging.LogLevel +import io.ktor.client.plugins.logging.Logger +import io.ktor.client.plugins.logging.Logging +import io.ktor.client.plugins.logging.SIMPLE +import io.ktor.client.request.HttpRequestBuilder +import io.ktor.client.request.get +import io.ktor.client.request.parameter +import io.ktor.http.ContentType +import io.ktor.serialization.kotlinx.KotlinxSerializationConverter +import kotlinx.serialization.json.Json + +/** + * Whenever we want to add params to a request, we just return a map of param + * keys and values. The [BaseKtorClient] can map this to the request builder. + */ +private typealias RemoteParams = Map + +/** + * Creates a default [httpClient] that can make requests to the supplied [baseURL]. + * + * You can either subclass this with a specific client type, + * like `object GitHubClient : BaseKtorClient("https://api.github.com")`, + * or repurpose this class to represent a specific client instead. + */ +open class BaseKtorClient( + val baseURL: String, +) { + val httpClient = HttpClient { + install(ContentNegotiation) { + val converter = KotlinxSerializationConverter( + Json { + ignoreUnknownKeys = true + }, + ) + register(ContentType.Any, converter) + } + + install(Logging) { + logger = Logger.SIMPLE + level = LogLevel.ALL + } + } + + /** + * A helper function to build the [baseURL] and [endpoint] operation and performs a get request. + * Will also pass in the supplied [params] as necessary. + * + * NOTE that it is expected for endpoint to begin with a forward slash (/), it is not automatically + * included into the full URL. + * + * You can call this function to get a response typed to the given generic, like so: + * val eventResult: Result = apiClient.getResponse(endpoint = "/events/123") + */ + @Suppress("TooGenericExceptionCaught") + suspend inline fun getResponse( + endpoint: String, + params: RemoteParams = emptyMap(), + ): Result { + val url = "$baseURL$endpoint" + + return try { + val apiResult: T = httpClient + .get(url) { + addParams(params) + } + .body() + Result.success(apiResult) + } catch (e: Exception) { + Result.failure(e) + } + } +} + +/** + * Adds all of the [params] to this [HttpRequestBuilder] as long as they're not null. + */ +fun HttpRequestBuilder.addParams( + params: RemoteParams, +) { + params.forEach { (key, value) -> + if (value != null) { + this.parameter(key, value) + } + } +}