Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ dependencies {
implementation("io.arrow-kt:arrow-core:2.0.1")
implementation("io.arrow-kt:arrow-fx-coroutines:2.0.1")
implementation("io.insert-koin:koin-core:4.0.2")
implementation("com.github.doyaaaaaken:kotlin-csv-jvm:1.9.0")
testImplementation("org.jetbrains.kotlin:kotlin-test")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0-M1")
testImplementation("io.mockk:mockk:1.13.16")
Expand All @@ -37,8 +36,7 @@ dependencies {
implementation("io.ktor:ktor-client-content-negotiation:$ktor_version")
implementation("io.ktor:ktor-serialization-kotlinx-json:$ktor_version")
implementation(kotlin("stdlib-jdk8"))


implementation("org.json:json:20210307")
}

tasks.test {
Expand Down
29 changes: 29 additions & 0 deletions src/main/kotlin/Main.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import di.presentationModule
import di.repositoryModule
import di.useCaseModule
import kotlinx.coroutines.*
import org.koin.core.context.startKoin
import org.koin.core.context.stopKoin
import org.koin.mp.KoinPlatform.getKoin
import presentation.ClothesSuggesterConsoleUI

fun main() {
var exit = true
GlobalScope.launch {
startKoin {
modules(repositoryModule, useCaseModule, presentationModule)
}

val mainUi: ClothesSuggesterConsoleUI = getKoin().get()
mainUi.start()
mainUi.uiFlow.collect {
exit = true
stopKoin()
}
}
runBlocking {
while (exit) {
delay(1000)
}
}
}
4 changes: 2 additions & 2 deletions src/main/kotlin/data/repository/LocationRepositoryImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import domain.models.Location
import domain.repository.LocationRepository
import domain.util.location_getter.LocationFetcher

class LocationRepositoryImpl(private val locationFetcher: LocationFetcher) : LocationRepository {
override suspend fun getLocation(): Location {
class LocationRepositoryImpl() : LocationRepository {
override suspend fun getLocation(locationFetcher: LocationFetcher): Location {
return locationFetcher.getLocation()
}
}
26 changes: 18 additions & 8 deletions src/main/kotlin/data/repository/WeatherRepositoryImpl.kt
Original file line number Diff line number Diff line change
@@ -1,33 +1,43 @@
package data.repository

import WeatherRepository
import domain.models.CurrentWeather
import domain.models.exceptions.FailedFetchWeatherDataException
import domain.models.FailedFetchWeatherDataException
import domain.models.NoLocationRetrieved
import domain.repository.WeatherRepository
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.json.Json
import org.json.JSONObject

const val OPEN_METO_API = "https://api.open-meteo.com/v1/forecast"

class WeatherRepositoryImpl(private val httpClient: HttpClient) : WeatherRepository {
override suspend fun getCurrentWeather(latitude: Double, longitude: Double): CurrentWeather {
val response: HttpResponse = httpClient.get(OPEN_METO_API) {
val httpResponse: HttpResponse = httpClient.get(OPEN_METO_API) {
parameter("latitude", latitude)
parameter("longitude", longitude)
parameter(
"current",
"temperature_2m,relative_humidity_2m,apparent_temperature,is_day,wind_speed_10m,snowfall,rain,weather_code,cloud_cover"
"temperature_2m,relative_humidity_2m,apparent_temperature,is_day,rain,showers,snowfall,wind_speed_10m,cloud_cover"
)
}

if (!response.status.isSuccess()) {
if (httpResponse.status != HttpStatusCode.OK) {
throw FailedFetchWeatherDataException(
"Weather API returned error status: ${response.status}"
"Weather API returned error status: ${httpResponse.status}"
)
}

return response.body()
try {
val response = JSONObject(httpResponse.bodyAsText()).get("current").toString()
val json = Json {
ignoreUnknownKeys = true
}
return json.decodeFromString<CurrentWeather>(response)
} catch (e: Exception) {
throw NoLocationRetrieved()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json

const val IP_API_URL = "http://ip-api.com/json"

class CurrentLocationFetcher(private val httpClient: HttpClient) : LocationFetcher {
override suspend fun getLocation(): Location {
val httpResponse = httpClient.get(IP_API_URL)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import org.json.JSONObject

const val POSITION_STACK_URL = "https://positionstack.com/geo_api.php?query=%s"

Expand All @@ -19,7 +19,7 @@ class NamedLocationFetcher(private val placeName: String, private val httpClient
throw NoLocationRetrieved()

try {
val response = httpResponse.bodyAsText()
val response = JSONObject(httpResponse.bodyAsText()).getJSONArray("data")[0].toString()
val json = Json {
ignoreUnknownKeys = true
}
Expand Down
15 changes: 15 additions & 0 deletions src/main/kotlin/di/presentationModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package di

import org.koin.dsl.module
import presentation.ClothesSuggesterConsoleUI
import presentation.io.ConsoleIO
import presentation.io.ConsoleIOImpl
import java.util.*

val presentationModule = module {
single { Scanner(System.`in`) }
single<ConsoleIO> { ConsoleIOImpl(get()) }

// Authentication UI
single { ClothesSuggesterConsoleUI(get(), get(), get(), get(), get()) }
}
15 changes: 15 additions & 0 deletions src/main/kotlin/di/repositoryModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package di

import data.repository.LocationRepositoryImpl
import data.repository.WeatherRepositoryImpl
import domain.repository.LocationRepository
import domain.repository.WeatherRepository
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import org.koin.dsl.module

val repositoryModule = module {
single { HttpClient(CIO) }
single<LocationRepository> { LocationRepositoryImpl() }
single<WeatherRepository> { WeatherRepositoryImpl(get()) }
}
12 changes: 12 additions & 0 deletions src/main/kotlin/di/useCaseModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package di

import domain.use_cases.GetCurrentWeatherUseCase
import domain.use_cases.GetLocationUseCase
import domain.use_cases.SuggestClothesBasedOnWeatherUseCase
import org.koin.dsl.module

val useCaseModule = module {
single { GetCurrentWeatherUseCase(get()) }
single { GetLocationUseCase() }
single { SuggestClothesBasedOnWeatherUseCase() }
}
5 changes: 4 additions & 1 deletion src/main/kotlin/domain/models/exceptions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,7 @@ import io.ktor.utils.io.errors.*

class NoLocationRetrieved(message: String = "No location retrieved") : IOException(message)

class ErrorOnRetrieveLocation(message: String) : IOException(message)
class FailedFetchWeatherDataException(
message: String,
cause: Throwable? = null
) : Exception(message, cause)

This file was deleted.

3 changes: 2 additions & 1 deletion src/main/kotlin/domain/repository/LocationRepository.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package domain.repository

import domain.models.Location
import domain.util.location_getter.LocationFetcher

interface LocationRepository {
suspend fun getLocation(): Location
suspend fun getLocation(locationFetcher: LocationFetcher): Location
}
2 changes: 2 additions & 0 deletions src/main/kotlin/domain/repository/WeatherRepository.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
package domain.repository

import domain.models.CurrentWeather

interface WeatherRepository {
Expand Down
2 changes: 2 additions & 0 deletions src/main/kotlin/domain/use_cases/GetCurrentWeatherUseCase.kt
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
package domain.use_cases
import domain.models.CurrentWeather
import domain.repository.WeatherRepository

class GetCurrentWeatherUseCase(private val repository: WeatherRepository) {
suspend fun getCurrentWeather(latitude: Double, longitude: Double): CurrentWeather {
Expand Down
4 changes: 2 additions & 2 deletions src/main/kotlin/domain/use_cases/GetLocationUseCase.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package domain.use_cases
import domain.models.Location
import domain.util.location_getter.LocationFetcher

class GetLocationUseCase(private val locationGetter: LocationFetcher) {
suspend fun getLocation(): Location {
class GetLocationUseCase {
suspend fun getLocation(locationGetter: LocationFetcher): Location {
return locationGetter.getLocation()
}
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,35 @@
package domain.use_cases

import WeatherRepository
import domain.models.CurrentWeather

class SuggestClothesBasedOnWeatherUseCase(
private val weatherRepository: WeatherRepository
) {
suspend fun getSuggestClothesByWeather(latitude: Double, longitude: Double): List<String>{
val weather = weatherRepository.getCurrentWeather(latitude, longitude)
class SuggestClothesBasedOnWeatherUseCase {
suspend fun getSuggestClothesByWeather(weather: CurrentWeather): List<String> {
val suggestions = mutableListOf<String>()
val temperature = weather.temperature2m.toFloat()
val windSpeed = weather.windSpeed10m.toFloat()
val cloudCover = weather.cloudCover.toInt()
val isDay = weather.isDay == 1
val weatherCode = weather.weatherCode.toInt()
val rain = weather.rain.toFloat()
val snowfall = weather.snowfall.toFloat()

// Temperature-based suggestions
when {
temperature > 30 -> {
suggestions.addAll(listOf("very light clothes", "tank tops", "shorts"))
temperature < 0 -> {
suggestions.addAll(listOf("heavy winter coat", "gloves", "scarf"))
}
temperature in 25f..30f -> {

temperature <= 29 -> {
suggestions.addAll(listOf("light T-shirt", "shorts", "breathable wear"))
}
temperature < 0 -> {
suggestions.addAll(listOf("heavy winter coat", "gloves", "scarf"))

else -> {
suggestions.addAll(listOf("very light clothes", "tank tops", "shorts"))
}
}

// Wind-based suggestions
when {
windSpeed > 15 -> suggestions.add("windbreaker")
windSpeed in 8f..15f -> suggestions.add("light jacket")
windSpeed > 8 -> suggestions.add("light jacket")
}

// Precipitation-based suggestions
Expand All @@ -42,7 +39,7 @@ class SuggestClothesBasedOnWeatherUseCase(
}

// Nighttime suggestion
if (!isDay && temperature < 18) {
if (!isDay) {
suggestions.add("reflective clothing")
}

Expand Down
Loading
Loading