diff --git a/.idea/misc.xml b/.idea/misc.xml index 2a4d5b5..f23a13b 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,5 +1,17 @@ + + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..71ce279 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index eb6b307..034a26f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,15 +3,16 @@ plugins { id 'org.jetbrains.kotlin.android' id 'kotlin-parcelize' id 'kotlin-kapt' + id 'androidx.navigation.safeargs' } android { - compileSdk 31 + compileSdk 32 defaultConfig { applicationId "ru.itis.karakurik.androidLab2" minSdk 23 - targetSdk 31 + targetSdk 32 versionCode 1 versionName "1.0" @@ -19,7 +20,7 @@ android { } buildFeatures { - viewBinding true + viewBinding = true } buildTypes { @@ -38,6 +39,7 @@ android { } dependencies { + implementation 'com.google.android.gms:play-services-location:19.0.1' def room_version = "2.4.2" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" @@ -46,10 +48,22 @@ dependencies { implementation 'androidx.appcompat:appcompat:1.4.1' implementation 'com.google.android.material:material:1.5.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.3' + implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1" + + //SwipeRefreshLayout + implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0" + + //Timber + implementation 'com.jakewharton.timber:timber:5.0.1' + + //Coil + implementation "io.coil-kt:coil:1.1.1" + + def navigation = "2.4.1" + implementation "androidx.navigation:navigation-fragment-ktx:$navigation" + implementation "androidx.navigation:navigation-ui-ktx:$navigation" + implementation "androidx.navigation:navigation-runtime-ktx:$navigation" - def nav_version = "2.4.1" - implementation "androidx.navigation:navigation-fragment:$nav_version" - implementation "androidx.navigation:navigation-ui:$nav_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 52ea1ca..f82e4e2 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,10 @@ + package="ru.itis.karakurik.androidLab2"> + + + + + android:theme="@style/Theme.Androidlab2" + android:usesCleartextTraffic="true"> + android:name=".presentation.activities.MainActivity" + android:exported="true"> diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/MainActivity.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/MainActivity.kt deleted file mode 100644 index 0e7574a..0000000 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/MainActivity.kt +++ /dev/null @@ -1,12 +0,0 @@ -package ru.itis.karakurik.androidLab2 - -import androidx.appcompat.app.AppCompatActivity -import android.os.Bundle - -class MainActivity : AppCompatActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - } -} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/Api.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/Api.kt new file mode 100644 index 0000000..84c2972 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/Api.kt @@ -0,0 +1,21 @@ +package ru.itis.karakurik.androidLab2.data.api + +import retrofit2.http.GET +import retrofit2.http.Query +import ru.itis.karakurik.androidLab2.data.api.response.citiesResponse.CitiesResponse +import ru.itis.karakurik.androidLab2.data.api.response.weatherResponse.WeatherResponse + +interface Api { + @GET("weather") + suspend fun getWeather(@Query("q") city: String): WeatherResponse + + @GET("weather") + suspend fun getWeather(@Query("id") id: Int): WeatherResponse + + @GET("find") + suspend fun getWeathers( + @Query("lat") lat: Double, + @Query("lon") lon: Double, + @Query("cnt") cnt: Int + ) : CitiesResponse +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WeatherIconUrlMapper.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WeatherIconUrlMapper.kt new file mode 100644 index 0000000..9ad9452 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WeatherIconUrlMapper.kt @@ -0,0 +1,7 @@ +package ru.itis.karakurik.androidLab2.data.api.mapper + +class WeatherIconUrlMapper { + fun map(iconId: String): String { + return "http://openweathermap.org/img/wn/${iconId}@2x.png" + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WeatherMapper.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WeatherMapper.kt new file mode 100644 index 0000000..c8d249c --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WeatherMapper.kt @@ -0,0 +1,40 @@ +package ru.itis.karakurik.androidLab2.data.api.mapper + +import ru.itis.karakurik.androidLab2.data.api.response.citiesResponse.City +import ru.itis.karakurik.androidLab2.data.api.response.weatherResponse.WeatherResponse +import ru.itis.karakurik.androidLab2.domain.entity.Weather + +class WeatherMapper( + private val windDegMapper: WindDegMapper, + private val weatherIconUrlMapper: WeatherIconUrlMapper +) { + fun map(weatherResponse: WeatherResponse) : Weather = Weather( + id = weatherResponse.id, + name = weatherResponse.name, + lat = weatherResponse.coord.lat, + lon = weatherResponse.coord.lon, + temp = weatherResponse.main.temp, + tempMin = weatherResponse.main.tempMin, + tempMax = weatherResponse.main.tempMax, + humidity = weatherResponse.main.humidity, + windDeg = windDegMapper.map(weatherResponse.wind.deg), + windSpeed = weatherResponse.wind.speed, + pressure = weatherResponse.main.pressure, + iconUrl = weatherIconUrlMapper.map(weatherResponse.weather[0].icon) + ) + + fun map(city: City) : Weather = Weather( + id = city.id, + name = city.name, + lat = city.coord.lat, + lon = city.coord.lon, + temp = city.main.temp, + tempMin = city.main.tempMin, + tempMax = city.main.tempMax, + humidity = city.main.humidity, + windDeg = windDegMapper.map(city.wind.deg), + windSpeed = city.wind.speed, + pressure = city.main.pressure, + iconUrl = weatherIconUrlMapper.map(city.weather[0].icon) + ) +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WindDegMapper.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WindDegMapper.kt new file mode 100644 index 0000000..9360b16 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/mapper/WindDegMapper.kt @@ -0,0 +1,18 @@ +package ru.itis.karakurik.androidLab2.data.api.mapper + +import ru.itis.karakurik.androidLab2.domain.enum.WindDeg + +class WindDegMapper { + fun map(deg: Int) : WindDeg { + return when ((deg / 45 + 2* (deg%45) / 45) * 45 % 360) { + 0 -> WindDeg.N + 45 -> WindDeg.NE + 90 -> WindDeg.E + 135 -> WindDeg.SE + 180 -> WindDeg.S + 225 -> WindDeg.SW + 270 -> WindDeg.W + else -> WindDeg.NW + } + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/CitiesResponse.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/CitiesResponse.kt new file mode 100644 index 0000000..d98186c --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/CitiesResponse.kt @@ -0,0 +1,15 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class CitiesResponse( + @SerializedName("cod") + val cod: String, + @SerializedName("count") + val count: Int, + @SerializedName("list") + val list: List, + @SerializedName("message") + val message: String +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/City.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/City.kt new file mode 100644 index 0000000..799bca3 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/City.kt @@ -0,0 +1,29 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class City ( + @SerializedName("clouds") + val clouds: Clouds, + @SerializedName("coord") + val coord: Coord, + @SerializedName("dt") + val dt: Int, + @SerializedName("id") + val id: Int, + @SerializedName("main") + val main: Main, + @SerializedName("name") + val name: String, + @SerializedName("rain") + val rain: Any, + @SerializedName("snow") + val snow: Any, + @SerializedName("sys") + val sys: Sys, + @SerializedName("weather") + val weather: List, + @SerializedName("wind") + val wind: Wind +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Clouds.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Clouds.kt new file mode 100644 index 0000000..44c6324 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Clouds.kt @@ -0,0 +1,9 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class Clouds( + @SerializedName("all") + val all: Int +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Coord.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Coord.kt new file mode 100644 index 0000000..7cdb5c3 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Coord.kt @@ -0,0 +1,11 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class Coord( + @SerializedName("lat") + val lat: Double, + @SerializedName("lon") + val lon: Double +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Main.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Main.kt new file mode 100644 index 0000000..763b83d --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Main.kt @@ -0,0 +1,23 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class Main( + @SerializedName("feels_like") + val feelsLike: Double, + @SerializedName("grnd_level") + val grndLevel: Int, + @SerializedName("humidity") + val humidity: Int, + @SerializedName("pressure") + val pressure: Int, + @SerializedName("sea_level") + val seaLevel: Int, + @SerializedName("temp") + val temp: Double, + @SerializedName("temp_max") + val tempMax: Double, + @SerializedName("temp_min") + val tempMin: Double +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Sys.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Sys.kt new file mode 100644 index 0000000..f96c00b --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Sys.kt @@ -0,0 +1,9 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class Sys( + @SerializedName("country") + val country: String +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Weather.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Weather.kt new file mode 100644 index 0000000..a07bbb0 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Weather.kt @@ -0,0 +1,15 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class Weather( + @SerializedName("description") + val description: String, + @SerializedName("icon") + val icon: String, + @SerializedName("id") + val id: Int, + @SerializedName("main") + val main: String +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Wind.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Wind.kt new file mode 100644 index 0000000..2e77cf2 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/citiesResponse/Wind.kt @@ -0,0 +1,11 @@ +package ru.itis.karakurik.androidLab2.data.api.response.citiesResponse + + +import com.google.gson.annotations.SerializedName + +data class Wind( + @SerializedName("deg") + val deg: Int, + @SerializedName("speed") + val speed: Double +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Clouds.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Clouds.kt new file mode 100644 index 0000000..a8c2d26 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Clouds.kt @@ -0,0 +1,9 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class Clouds( + @SerializedName("all") + val all: Int +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Coord.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Coord.kt new file mode 100644 index 0000000..da00620 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Coord.kt @@ -0,0 +1,11 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class Coord( + @SerializedName("lat") + val lat: Double, + @SerializedName("lon") + val lon: Double +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Main.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Main.kt new file mode 100644 index 0000000..0330e2e --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Main.kt @@ -0,0 +1,23 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class Main( + @SerializedName("feels_like") + val feelsLike: Double, + @SerializedName("grnd_level") + val grndLevel: Int, + @SerializedName("humidity") + val humidity: Int, + @SerializedName("pressure") + val pressure: Int, + @SerializedName("sea_level") + val seaLevel: Int, + @SerializedName("temp") + val temp: Double, + @SerializedName("temp_max") + val tempMax: Double, + @SerializedName("temp_min") + val tempMin: Double +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Sys.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Sys.kt new file mode 100644 index 0000000..825469f --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Sys.kt @@ -0,0 +1,17 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class Sys( + @SerializedName("country") + val country: String, + @SerializedName("id") + val id: Int, + @SerializedName("sunrise") + val sunrise: Int, + @SerializedName("sunset") + val sunset: Int, + @SerializedName("type") + val type: Int +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Weather.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Weather.kt new file mode 100644 index 0000000..5de74ff --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Weather.kt @@ -0,0 +1,15 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class Weather( + @SerializedName("description") + val description: String, + @SerializedName("icon") + val icon: String, + @SerializedName("id") + val id: Int, + @SerializedName("main") + val main: String +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/WeatherResponse.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/WeatherResponse.kt new file mode 100644 index 0000000..66a32ce --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/WeatherResponse.kt @@ -0,0 +1,33 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class WeatherResponse( + @SerializedName("base") + val base: String, + @SerializedName("clouds") + val clouds: Clouds, + @SerializedName("cod") + val cod: Int, + @SerializedName("coord") + val coord: Coord, + @SerializedName("dt") + val dt: Int, + @SerializedName("id") + val id: Int, + @SerializedName("main") + val main: Main, + @SerializedName("name") + val name: String, + @SerializedName("sys") + val sys: Sys, + @SerializedName("timezone") + val timezone: Int, + @SerializedName("visibility") + val visibility: Int, + @SerializedName("weather") + val weather: List, + @SerializedName("wind") + val wind: Wind +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Wind.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Wind.kt new file mode 100644 index 0000000..282cc9a --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/response/weatherResponse/Wind.kt @@ -0,0 +1,13 @@ +package ru.itis.karakurik.androidLab2.data.api.response.weatherResponse + + +import com.google.gson.annotations.SerializedName + +data class Wind( + @SerializedName("deg") + val deg: Int, + @SerializedName("gust") + val gust: Double, + @SerializedName("speed") + val speed: Double +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/DiContainer.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/DiContainer.kt new file mode 100644 index 0000000..978150c --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/DiContainer.kt @@ -0,0 +1,69 @@ +package ru.itis.karakurik.androidLab2.di + +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import ru.itis.karakurik.androidLab2.BuildConfig +import ru.itis.karakurik.androidLab2.data.api.Api +import ru.itis.karakurik.androidLab2.data.api.mapper.WeatherIconUrlMapper +import ru.itis.karakurik.androidLab2.data.api.mapper.WeatherMapper +import ru.itis.karakurik.androidLab2.data.api.mapper.WindDegMapper +import ru.itis.karakurik.androidLab2.di.interceptors.ApiKeyInterceptor +import ru.itis.karakurik.androidLab2.di.interceptors.LangInterceptor +import ru.itis.karakurik.androidLab2.di.interceptors.UnitsInterceptor +import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository +import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepositoryImpl + +private const val BASE_URL = "https://api.openweathermap.org/data/2.5/" + +object DiContainer { + private val okhttp: OkHttpClient by lazy { + OkHttpClient.Builder() + .addInterceptor(ApiKeyInterceptor()) + .addInterceptor(UnitsInterceptor()) + .addInterceptor(LangInterceptor()) + .also { + if (BuildConfig.DEBUG) { + it.addInterceptor( + HttpLoggingInterceptor() + .setLevel( + HttpLoggingInterceptor.Level.BODY + ) + ) + } + } + .build() + } + + val api: Api by lazy { + Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okhttp) + .addConverterFactory(GsonConverterFactory.create()) + .build() + .create(Api::class.java) + } + + private val windDegMapper: WindDegMapper by lazy { + WindDegMapper() + } + + private val weatherIconUrlMapper by lazy { + WeatherIconUrlMapper() + } + + val weatherMapper: WeatherMapper by lazy { + WeatherMapper( + windDegMapper, + weatherIconUrlMapper + ) + } + + val weatherRepository: WeatherRepository by lazy { + WeatherRepositoryImpl( + api, + weatherMapper + ) + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/ApiKeyInterceptor.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/ApiKeyInterceptor.kt new file mode 100644 index 0000000..3c36c4a --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/ApiKeyInterceptor.kt @@ -0,0 +1,22 @@ +package ru.itis.karakurik.androidLab2.di.interceptors + +import okhttp3.Interceptor +import okhttp3.Response + +private const val API_KEY = "56fc6c6cb76c0864b4cd055080568268" +private const val QUERY_API_KEY = "appid" + +class ApiKeyInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val newUrl = request.url.newBuilder() + .addQueryParameter(QUERY_API_KEY, API_KEY) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build() + ) + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/LangInterceptor.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/LangInterceptor.kt new file mode 100644 index 0000000..f604354 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/LangInterceptor.kt @@ -0,0 +1,22 @@ +package ru.itis.karakurik.androidLab2.di.interceptors + +import okhttp3.Interceptor +import okhttp3.Response + +private const val QUERY_LANG = "lang" +private const val LANG = "RU" + +class LangInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val newUrl = request.url.newBuilder() + .addQueryParameter(QUERY_LANG, LANG) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build() + ) + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/UnitsInterceptor.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/UnitsInterceptor.kt new file mode 100644 index 0000000..bc6a739 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/UnitsInterceptor.kt @@ -0,0 +1,22 @@ +package ru.itis.karakurik.androidLab2.di.interceptors + +import okhttp3.Interceptor +import okhttp3.Response + +private const val QUERY_UNITS = "units" +private const val UNITS = "metric" + +class UnitsInterceptor : Interceptor { + override fun intercept(chain: Interceptor.Chain): Response { + val request = chain.request() + val newUrl = request.url.newBuilder() + .addQueryParameter(QUERY_UNITS, UNITS) + .build() + + return chain.proceed( + request.newBuilder() + .url(newUrl) + .build() + ) + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/entity/Weather.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/entity/Weather.kt new file mode 100644 index 0000000..82c9d88 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/entity/Weather.kt @@ -0,0 +1,18 @@ +package ru.itis.karakurik.androidLab2.domain.entity + +import ru.itis.karakurik.androidLab2.domain.enum.WindDeg + +data class Weather( + val id: Int, + val name: String, + val lat: Double, + val lon: Double, + val temp: Double, + val tempMin: Double, + val tempMax: Double, + val humidity: Int, + val windDeg: WindDeg, + val windSpeed: Double, + val pressure: Int, + val iconUrl: String +) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/enum/WindDeg.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/enum/WindDeg.kt new file mode 100644 index 0000000..272cb4b --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/enum/WindDeg.kt @@ -0,0 +1,13 @@ +package ru.itis.karakurik.androidLab2.domain.enum +enum class WindDeg ( + val deg: Int +) { + N(0), + NE(45), + E(90), + SE(135), + S(180), + SW(225), + W(270), + NW(315) +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepository.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepository.kt new file mode 100644 index 0000000..8000977 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepository.kt @@ -0,0 +1,9 @@ +package ru.itis.karakurik.androidLab2.domain.repository + +import ru.itis.karakurik.androidLab2.domain.entity.Weather + +interface WeatherRepository { + suspend fun getWeather(city: String): Weather + suspend fun getWeather(id: Int): Weather + suspend fun getWeatherList(lat: Double, lon: Double, cnt: Int): MutableList +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepositoryImpl.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepositoryImpl.kt new file mode 100644 index 0000000..48ec3ec --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepositoryImpl.kt @@ -0,0 +1,28 @@ +package ru.itis.karakurik.androidLab2.domain.repository + +import ru.itis.karakurik.androidLab2.data.api.Api +import ru.itis.karakurik.androidLab2.data.api.mapper.WeatherMapper +import ru.itis.karakurik.androidLab2.domain.entity.Weather + +class WeatherRepositoryImpl( + private val api: Api, + private val weatherMapper: WeatherMapper +) : WeatherRepository { + + override suspend fun getWeather(city: String): Weather { + return weatherMapper.map(api.getWeather(city)) + } + + override suspend fun getWeather(id: Int): Weather { + return weatherMapper.map(api.getWeather(id)) + } + + override suspend fun getWeatherList(lat: Double, lon: Double, cnt: Int): MutableList { + val citiesResponse = api.getWeathers(lat, lon, cnt) + val list = ArrayList(cnt) + for (city in citiesResponse.list) { + list.add(weatherMapper.map(city)) + } + return list + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCase.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCase.kt new file mode 100644 index 0000000..a0c0ce6 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCase.kt @@ -0,0 +1,25 @@ +package ru.itis.karakurik.androidLab2.domain.usecase + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ru.itis.karakurik.androidLab2.domain.entity.Weather +import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository + +class GetWeatherUseCase( + private val weatherRepository: WeatherRepository, + private val dispatcher: CoroutineDispatcher = Dispatchers.Main +) { + + suspend operator fun invoke(city: String): Weather { + return withContext(dispatcher) { + weatherRepository.getWeather(city) + } + } + + suspend operator fun invoke(id: Int): Weather { + return withContext(dispatcher) { + weatherRepository.getWeather(id) + } + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeathersUseCase.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeathersUseCase.kt new file mode 100644 index 0000000..62b2d81 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeathersUseCase.kt @@ -0,0 +1,18 @@ +package ru.itis.karakurik.androidLab2.domain.usecase + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import ru.itis.karakurik.androidLab2.domain.entity.Weather +import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository + +class GetWeathersUseCase( + private val weatherRepository: WeatherRepository, + private val dispatcher: CoroutineDispatcher = Dispatchers.Main +) { + suspend operator fun invoke(lat: Double, lon: Double, cnt: Int): MutableList { + return withContext(dispatcher) { + weatherRepository.getWeatherList(lat, lon, cnt) + } + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/extentions/ActivityExt.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/extentions/ActivityExt.kt new file mode 100644 index 0000000..61a4dd6 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/extentions/ActivityExt.kt @@ -0,0 +1,9 @@ +package ru.itis.karakurik.androidLab2.extentions + +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import androidx.navigation.fragment.NavHostFragment + +fun AppCompatActivity.findController (id: Int) : NavController { + return (supportFragmentManager.findFragmentById(id) as NavHostFragment).navController +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt new file mode 100644 index 0000000..ef3f1a7 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt @@ -0,0 +1,26 @@ +package ru.itis.karakurik.androidLab2.presentation.activities + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.NavController +import ru.itis.karakurik.androidLab2.databinding.ActivityMainBinding +import ru.itis.karakurik.androidLab2.extentions.findController + +class MainActivity : AppCompatActivity() { + private var binding: ActivityMainBinding? = null + private var controller: NavController? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityMainBinding.inflate(layoutInflater).also { + setContentView(it.root) + } + controller = binding?.navHostFragmentMain?.id?.let { findController(it) } + } + + override fun onDestroy() { + super.onDestroy() + binding = null + controller = null + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/convertors/TempColorConverter.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/convertors/TempColorConverter.kt new file mode 100644 index 0000000..365a004 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/convertors/TempColorConverter.kt @@ -0,0 +1,16 @@ +package ru.itis.karakurik.androidLab2.presentation.convertors + +import ru.itis.karakurik.androidLab2.R + +object TempColorConverter { + fun getColor(temp: Double): Int { + return when { + temp < -20 -> R.color.temp_less_minus20 + temp < 10 -> R.color.temp_minus20_minus10 + temp < 0 -> R.color.temp_minus10_0 + temp < 10 -> R.color.temp_0_10 + temp < 20 -> R.color.temp_10_20 + else -> R.color.temp_more20 + } + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt new file mode 100644 index 0000000..bfb668b --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt @@ -0,0 +1,75 @@ +package ru.itis.karakurik.androidLab2.presentation.fragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import coil.imageLoader +import coil.load +import coil.request.CachePolicy +import coil.request.ImageRequest +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import ru.itis.karakurik.androidLab2.R +import ru.itis.karakurik.androidLab2.databinding.FragmentDetailsBinding +import ru.itis.karakurik.androidLab2.di.DiContainer +import ru.itis.karakurik.androidLab2.domain.entity.Weather +import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase + +class DetailsFragment : Fragment(R.layout.fragment_details) { + private var cityId: Int? = null + private var weather: Weather? = null + + private val getWeatherUseCase: GetWeatherUseCase by lazy { + GetWeatherUseCase(DiContainer.weatherRepository, Dispatchers.Default) + } + + private var binding: FragmentDetailsBinding? = null + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_details, container, false) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentDetailsBinding.bind(view) + + arguments?.let { + cityId = it.getInt("city_id") + } + + lifecycleScope.launch { + weather = cityId?.let { getWeatherUseCase(it) } + + binding?.run { + weather?.let { + tvCity.text = it.name + tvTemp.text = "${it.temp}°C" + tvTempMin.text = "${it.tempMin}°C" + tvTempMax.text = "${it.tempMax}°C" + tvWindDeg.text = it.windDeg.toString() + tvWindSpeed.text = "${it.windSpeed}km/h" + tvHumidity.text = "${it.humidity}%" + tvPressure.text = "${it.pressure}P" + ivAir.context.imageLoader.execute( + ImageRequest.Builder(ivAir.context) + .data(it.iconUrl) + .memoryCachePolicy(CachePolicy.DISABLED) + .placeholder(R.drawable.air) + .target(ivAir) + .build() + ) + } + } + } + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt new file mode 100644 index 0000000..bcd0a06 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt @@ -0,0 +1,233 @@ +package ru.itis.karakurik.androidLab2.presentation.fragments.list + +import android.Manifest +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.provider.Settings +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.appcompat.widget.SearchView +import androidx.core.app.ActivityCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.navigation.fragment.findNavController +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.material.snackbar.Snackbar +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch +import ru.itis.karakurik.androidLab2.R +import ru.itis.karakurik.androidLab2.databinding.FragmentSearchBinding +import ru.itis.karakurik.androidLab2.di.DiContainer +import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase +import ru.itis.karakurik.androidLab2.domain.usecase.GetWeathersUseCase +import ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler.ListRecyclerAdapter +import timber.log.Timber + +private const val COUNT_OF_CITIES_IN_LIST = 20 +private const val DEFAULT_LAT = 55.7887 +private const val DEFAULT_LON = 49.1221 +private const val REQUEST_CODE_100 = 100 + +class SearchFragment : Fragment(R.layout.fragment_search) { + private var binding: FragmentSearchBinding? = null + private var listRecyclerAdapter: ListRecyclerAdapter? = null + + private var userLat: Double = DEFAULT_LAT + private var userLon: Double = DEFAULT_LON + private var userLocation: FusedLocationProviderClient? = null + + private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) + + private val getWeatherUseCase: GetWeatherUseCase by lazy { + GetWeatherUseCase(DiContainer.weatherRepository, Dispatchers.Default) + } + + private val getWeatherListUseCase: GetWeathersUseCase by lazy { + GetWeathersUseCase(DiContainer.weatherRepository, Dispatchers.Default) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return inflater.inflate(R.layout.fragment_search, container, false) + } + + override fun onViewCreated( + view: View, + savedInstanceState: Bundle? + ) { + super.onViewCreated(view, savedInstanceState) + binding = FragmentSearchBinding.bind(view) + + getUserLocation() + initRecyclerView() + + handleSearchQuery() + + initSwipeRefreshList() + } + + private fun initSwipeRefreshList() { + binding?.swipeRefreshLayout?.let { + it.setOnRefreshListener { + getUserLocation() + it.isRefreshing = false + } + } + } + + private fun getUserLocation() { + context?.let { + if (ActivityCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_DENIED || + ActivityCompat.checkSelfPermission( + it, + Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_DENIED + ) { + val permissions = arrayOf( + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + Timber.d("Request location permissions") + requestPermissions(permissions, REQUEST_CODE_100) + } else { + Timber.d("Get user location") + userLocation = LocationServices.getFusedLocationProviderClient(requireContext()) + userLocation?.lastLocation?.addOnSuccessListener { location -> + if (location != null) { + userLon = location.longitude + userLat = location.latitude + Timber.d("Location found") + binding?.let { it -> + Snackbar.make(it.root, "Местоположение найдено", Snackbar.LENGTH_LONG) + .show() + } + getWeatherList(userLat, userLon, COUNT_OF_CITIES_IN_LIST) + } else { + Timber.d("Location not found") + binding?.let { it -> + Snackbar.make( + it.root, + "Местоположение не найдено\nВключите геолокацию", + Snackbar.LENGTH_LONG + ).run { + setAction("Включить местоположение") { + startActivity( + Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + ) + } + show() + } + } + } + } + } + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + when (requestCode) { + REQUEST_CODE_100 -> { + if (grantResults.isNotEmpty() + && grantResults[0] == PackageManager.PERMISSION_GRANTED + && grantResults[1] == PackageManager.PERMISSION_GRANTED + ) { + Timber.d("Permissions granted") + getUserLocation() + } else { + Timber.d("Permissions denied") + binding?.let { + Snackbar.make(it.root, "Разрешения не даны", Snackbar.LENGTH_SHORT) + .show() + } + } + } + } + } + + + private fun handleSearchQuery() { + binding?.svSearch?.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(query: String): Boolean { + lifecycleScope.launch { + Timber.d("Pressed query button") + getCityId(query)?.let { + showDetailsFragment(it) + } ?: run { + Toast.makeText( + context, + "Не удалось найти такой город", + Toast.LENGTH_LONG + ).show() + Timber.d("Do not found city") + } + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean { + Timber.d("Query text changed") + return false + } + }) + } + + private suspend fun getCityId( + city: String + ) = kotlin.runCatching { + getWeatherUseCase(city).id + }.getOrNull() + + + private fun initRecyclerView() { + binding?.rvSearch?.run { + listRecyclerAdapter = ListRecyclerAdapter { id -> + showDetailsFragment(id) + } + adapter = listRecyclerAdapter + } + + getWeatherList(userLat, userLon, COUNT_OF_CITIES_IN_LIST) + } + + private fun getWeatherList(lat: Double, lon: Double, cnt: Int) { + Timber.d("Get weathers list") + lifecycleScope.launch { + try { + val weatherList = getWeatherListUseCase(lat, lon, cnt) + listRecyclerAdapter?.submitList(weatherList) + binding?.rvSearch?.layoutManager?.scrollToPosition(0) + } catch (ex: Exception) { + Timber.e("Error due to get weathers") + Timber.e(ex.message.toString()) + Toast.makeText( + context, + "Не удалось найти", + Toast.LENGTH_LONG + ).show() + } + } + } + + private fun showDetailsFragment(id: Int) = findNavController().navigate( + SearchFragmentDirections.actionFragmentSearchToFragmentDetails( + id + ) + ) +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/CityWeatherDiffCallback.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/CityWeatherDiffCallback.kt new file mode 100644 index 0000000..a732bab --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/CityWeatherDiffCallback.kt @@ -0,0 +1,15 @@ +package ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler + +import androidx.recyclerview.widget.DiffUtil +import ru.itis.karakurik.androidLab2.domain.entity.Weather + +object CityWeatherDiffCallback : DiffUtil.ItemCallback(){ + override fun areItemsTheSame(oldItem: Weather, newItem: Weather): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: Weather, newItem: Weather): Boolean { + return oldItem == newItem + } + +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt new file mode 100644 index 0000000..503d9e2 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt @@ -0,0 +1,48 @@ +package ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler; + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.annotation.ColorRes +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import ru.itis.karakurik.androidLab2.databinding.ListItemCityBinding +import ru.itis.karakurik.androidLab2.domain.entity.Weather +import ru.itis.karakurik.androidLab2.presentation.convertors.TempColorConverter.getColor + +class ListItemViewHolder( + private val binding: ListItemCityBinding, + private val onItemClick: (id: Int) -> Unit +) : RecyclerView.ViewHolder(binding.root) { + + fun bind(weather: Weather) { + with(binding) { + tvCityItem.text = weather.name + tvTempItem.text = weather.temp.toString() + tvTempItem.setTextColor( + ContextCompat.getColor( + tvTempItem.context, + getColor(weather.temp) + ) + ) + + root.setOnClickListener { + onItemClick(weather.id) + } + } + } + + companion object { + fun create( + parent: ViewGroup, + onItemClick: (id: Int) -> Unit + ) = ListItemViewHolder( + ListItemCityBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ), + onItemClick + ) + } + +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt new file mode 100644 index 0000000..fd468f5 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt @@ -0,0 +1,29 @@ +package ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler; + +import android.view.ViewGroup +import androidx.recyclerview.widget.ListAdapter +import ru.itis.karakurik.androidLab2.domain.entity.Weather + +class ListRecyclerAdapter( + private val onItemClick: (id: Int) -> Unit +) : ListAdapter(CityWeatherDiffCallback) { + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ) = ListItemViewHolder.create( + parent, + onItemClick + ) + + override fun onBindViewHolder( + holder: ListItemViewHolder, + position: Int + ) = holder.bind( + getItem(position) + ) + + /*override fun submitList(list: MutableList?) { + super.submitList(if (list == null) null else ArrayList(list)) + }*/ + +} diff --git a/app/src/main/res/drawable/air.xml b/app/src/main/res/drawable/air.xml new file mode 100644 index 0000000..634be77 --- /dev/null +++ b/app/src/main/res/drawable/air.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/font/font_black.otf b/app/src/main/res/font/font_black.otf new file mode 100644 index 0000000..298fe13 Binary files /dev/null and b/app/src/main/res/font/font_black.otf differ diff --git a/app/src/main/res/font/font_bold.otf b/app/src/main/res/font/font_bold.otf new file mode 100644 index 0000000..fc9fc62 Binary files /dev/null and b/app/src/main/res/font/font_bold.otf differ diff --git a/app/src/main/res/font/font_book.otf b/app/src/main/res/font/font_book.otf new file mode 100644 index 0000000..1d30d9d Binary files /dev/null and b/app/src/main/res/font/font_book.otf differ diff --git a/app/src/main/res/font/font_light.otf b/app/src/main/res/font/font_light.otf new file mode 100644 index 0000000..4f078f9 Binary files /dev/null and b/app/src/main/res/font/font_light.otf differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 16f09a2..bc0b2ce 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,18 +1,18 @@ - - + android:orientation="vertical" + tools:context=".presentation.activities.MainActivity"> + + - + diff --git a/app/src/main/res/layout/fragment_details.xml b/app/src/main/res/layout/fragment_details.xml new file mode 100644 index 0000000..bb2c9c5 --- /dev/null +++ b/app/src/main/res/layout/fragment_details.xml @@ -0,0 +1,239 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 0000000..fb07d20 --- /dev/null +++ b/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/list_item_city.xml b/app/src/main/res/layout/list_item_city.xml new file mode 100644 index 0000000..8a2e615 --- /dev/null +++ b/app/src/main/res/layout/list_item_city.xml @@ -0,0 +1,37 @@ + + + + + + + + + + diff --git a/app/src/main/res/navigation/nav_graph_main.xml b/app/src/main/res/navigation/nav_graph_main.xml new file mode 100644 index 0000000..7ad4887 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph_main.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 9189b75..db0a143 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -1,6 +1,6 @@ -