diff --git a/app/build.gradle b/app/build.gradle index 4d45170..07e8fa5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ plugins { id 'org.jetbrains.kotlin.android' id 'kotlin-parcelize' id 'kotlin-kapt' - id 'androidx.navigation.safeargs' + id 'androidx.navigation.safeargs.kotlin' } android { @@ -24,7 +24,6 @@ android { dataBinding true } - buildTypes { release { minifyEnabled false @@ -90,6 +89,12 @@ dependencies { debugImplementation "com.squareup.okhttp3:logging-interceptor:$okhttp" // endregion + def dagger_version = "2.41" + implementation "com.google.dagger:dagger:$dagger_version" + implementation "com.google.dagger:dagger-android-support:$dagger_version" + kapt "com.google.dagger:dagger-compiler:$dagger_version" + kapt "com.google.dagger:dagger-android-processor:$dagger_version" + testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 013d722..f74a81e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,10 +4,11 @@ - + diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/WeatherApp.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/WeatherApp.kt new file mode 100644 index 0000000..3c6dbc3 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/WeatherApp.kt @@ -0,0 +1,19 @@ +package ru.itis.karakurik.androidLab2 + +import android.app.Application +import ru.itis.karakurik.androidLab2.di.AppComponent +import ru.itis.karakurik.androidLab2.di.DaggerAppComponent + +class WeatherApp : Application() { + + lateinit var appComponent: AppComponent + + override fun onCreate() { + super.onCreate() + appComponent = DaggerAppComponent + .builder() +// .context(context = this) + .application(this) + .build() + } +} 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 index 84c2972..a770337 100644 --- 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 @@ -6,6 +6,7 @@ import ru.itis.karakurik.androidLab2.data.api.response.citiesResponse.CitiesResp import ru.itis.karakurik.androidLab2.data.api.response.weatherResponse.WeatherResponse interface Api { + @GET("weather") suspend fun getWeather(@Query("q") city: String): WeatherResponse 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 index 77f6917..e80f2ea 100644 --- 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 @@ -1,6 +1,11 @@ package ru.itis.karakurik.androidLab2.data.api.mapper -class WeatherIconUrlMapper { +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WeatherIconUrlMapper @Inject constructor() { + fun mapToLargeIcon(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 index f10e35c..d4fd093 100644 --- 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 @@ -3,11 +3,15 @@ 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 +import javax.inject.Inject +import javax.inject.Singleton -class WeatherMapper( +@Singleton +class WeatherMapper @Inject constructor( private val windDegMapper: WindDegMapper, private val weatherIconUrlMapper: WeatherIconUrlMapper ) { + fun map(weatherResponse: WeatherResponse) : Weather = Weather( id = weatherResponse.id, name = weatherResponse.name, 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 index 9360b16..f1290e2 100644 --- 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 @@ -1,8 +1,11 @@ package ru.itis.karakurik.androidLab2.data.api.mapper import ru.itis.karakurik.androidLab2.domain.enum.WindDeg +import javax.inject.Inject +import javax.inject.Singleton -class WindDegMapper { +@Singleton +class WindDegMapper @Inject constructor() { fun map(deg: Int) : WindDeg { return when ((deg / 45 + 2* (deg%45) / 45) * 45 % 360) { 0 -> WindDeg.N diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepositoryImpl.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/repository/WeatherRepositoryImpl.kt similarity index 77% rename from app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepositoryImpl.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/data/api/repository/WeatherRepositoryImpl.kt index d109d54..b8497c8 100644 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/repository/WeatherRepositoryImpl.kt +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/data/api/repository/WeatherRepositoryImpl.kt @@ -1,11 +1,14 @@ -package ru.itis.karakurik.androidLab2.domain.repository +package ru.itis.karakurik.androidLab2.data.api.repository -import ru.itis.karakurik.androidLab2.BuildConfig 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 +import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository +import javax.inject.Inject +import javax.inject.Singleton -class WeatherRepositoryImpl( +@Singleton +class WeatherRepositoryImpl @Inject constructor( private val api: Api, private val weatherMapper: WeatherMapper ) : WeatherRepository { diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/AppComponent.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/AppComponent.kt new file mode 100644 index 0000000..9098c3f --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/AppComponent.kt @@ -0,0 +1,37 @@ +package ru.itis.karakurik.androidLab2.di + +import dagger.BindsInstance +import dagger.Component +import ru.itis.karakurik.androidLab2.WeatherApp +import ru.itis.karakurik.androidLab2.di.module.AppModule +import ru.itis.karakurik.androidLab2.di.module.NetModule +import ru.itis.karakurik.androidLab2.di.module.RepoModule +import ru.itis.karakurik.androidLab2.di.module.ViewModelModule +import ru.itis.karakurik.androidLab2.presentation.MainActivity +import ru.itis.karakurik.androidLab2.presentation.fragments.cities.SearchFragment +import ru.itis.karakurik.androidLab2.presentation.fragments.weather.DetailsFragment +import javax.inject.Singleton + +@Singleton +@Component( + modules = [ + AppModule::class, + NetModule::class, + RepoModule::class, + ViewModelModule::class + ] +) +interface AppComponent { + + fun inject(mainActivity: MainActivity) + fun inject(searchFragment: SearchFragment) + fun inject(detailsFragment: DetailsFragment) + + @Component.Builder + interface Builder { + fun build(): AppComponent + + @BindsInstance + fun application(application: WeatherApp): Builder + } +} 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 deleted file mode 100644 index 4569eca..0000000 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/di/DiContainer.kt +++ /dev/null @@ -1,80 +0,0 @@ -package ru.itis.karakurik.androidLab2.di - -import kotlinx.coroutines.Dispatchers -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 -import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherListUseCase -import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase - -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() - } - - private val weatherMapper: WeatherMapper by lazy { - WeatherMapper( - windDegMapper, - weatherIconUrlMapper - ) - } - - val weatherRepository: WeatherRepository by lazy { - WeatherRepositoryImpl( - api, - weatherMapper - ) - } - - val getWeatherUseCase: GetWeatherUseCase by lazy { - GetWeatherUseCase(weatherRepository, Dispatchers.Default) - } - - val getWeatherListUseCase: GetWeatherListUseCase by lazy { - GetWeatherListUseCase(weatherRepository, Dispatchers.Default) - } -} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/InterceptorAnnotation.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/InterceptorAnnotation.kt new file mode 100644 index 0000000..cab92aa --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/InterceptorAnnotation.kt @@ -0,0 +1,19 @@ +package ru.itis.karakurik.androidLab2.di.annotation + +import javax.inject.Qualifier + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class ApiKey + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class Units + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class Lang + +@Qualifier +@Retention(AnnotationRetention.RUNTIME) +annotation class Logger diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/InterceptorKey.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/InterceptorKey.kt new file mode 100644 index 0000000..a3744be --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/InterceptorKey.kt @@ -0,0 +1,14 @@ +package ru.itis.karakurik.androidLab2.di.annotation + +import dagger.MapKey +import okhttp3.Interceptor +import kotlin.reflect.KClass + +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER +) +@Retention(AnnotationRetention.RUNTIME) +@MapKey +annotation class InterceptorKey(val value: KClass) diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/ViewModelKey.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/ViewModelKey.kt new file mode 100644 index 0000000..f93d8b3 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/annotation/ViewModelKey.kt @@ -0,0 +1,14 @@ +package ru.itis.karakurik.androidLab2.di.annotation + +import androidx.lifecycle.ViewModel +import dagger.MapKey +import kotlin.reflect.KClass + +@Target( + AnnotationTarget.FUNCTION, + AnnotationTarget.PROPERTY_GETTER, + AnnotationTarget.PROPERTY_SETTER +) +@Retention(AnnotationRetention.RUNTIME) +@MapKey +annotation class ViewModelKey(val value: KClass) 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 deleted file mode 100644 index 3c36c4a..0000000 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/ApiKeyInterceptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index f604354..0000000 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/LangInterceptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -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 deleted file mode 100644 index bc6a739..0000000 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/di/interceptors/UnitsInterceptor.kt +++ /dev/null @@ -1,22 +0,0 @@ -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/di/module/AppModule.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/AppModule.kt new file mode 100644 index 0000000..9b81fa3 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/AppModule.kt @@ -0,0 +1,57 @@ +package ru.itis.karakurik.androidLab2.di.module + +import android.content.Context +import androidx.recyclerview.widget.LinearSmoothScroller +import androidx.recyclerview.widget.RecyclerView +import com.google.android.gms.location.LocationServices +import dagger.Module +import dagger.Provides +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import ru.itis.karakurik.androidLab2.WeatherApp +import javax.inject.Qualifier + +@Module +class AppModule { + + @Provides + fun provideContext(weatherApp: WeatherApp): Context = weatherApp.applicationContext + + @Provides + @DefaultDispatcher + fun provideDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default + + @Qualifier + @Retention(AnnotationRetention.RUNTIME) + annotation class DefaultDispatcher + + @Provides + @MainDispatcher + fun provideMainDispatcher(): CoroutineDispatcher = Dispatchers.Main + + @Qualifier + @Retention(AnnotationRetention.RUNTIME) + annotation class MainDispatcher + + @Provides + @IODispatcher + fun provideIODispatcher(): CoroutineDispatcher = Dispatchers.IO + + @Qualifier + @Retention(AnnotationRetention.RUNTIME) + annotation class IODispatcher + + @Provides + fun provideSmoothScroller( + context: Context + ): RecyclerView.SmoothScroller = object : LinearSmoothScroller(context) { + override fun getVerticalSnapPreference(): Int { + return SNAP_TO_START + } + } + + @Provides + fun provideFusedLocationProviderClient( + context: Context + ) = LocationServices.getFusedLocationProviderClient(context) +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/NetModule.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/NetModule.kt new file mode 100644 index 0000000..f72778f --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/NetModule.kt @@ -0,0 +1,147 @@ +package ru.itis.karakurik.androidLab2.di.module + +import dagger.Module +import dagger.Provides +import okhttp3.Cache +import okhttp3.Interceptor +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.di.annotation.ApiKey +import ru.itis.karakurik.androidLab2.di.annotation.Lang +import ru.itis.karakurik.androidLab2.di.annotation.Logger +import ru.itis.karakurik.androidLab2.di.annotation.Units +import java.io.File +import javax.inject.Qualifier + +private const val BASE_URL = "https://api.openweathermap.org/data/2.5/" +private const val API_KEY = "56fc6c6cb76c0864b4cd055080568268" +private const val QUERY_API_KEY = "appid" +private const val QUERY_LANG = "lang" +private const val LANG = "RU" +private const val QUERY_UNITS = "units" +private const val UNITS = "metric" + +@Module +class NetModule { + + @Provides + @ApiKey + fun apiKeyInterceptor(): Interceptor = Interceptor { chain -> + val request = chain.request() + val newUrl = request.url.newBuilder() + .addQueryParameter(QUERY_API_KEY, API_KEY) + .build() + + chain.proceed( + request.newBuilder() + .url(newUrl) + .build() + ) + } + + @Provides + @Lang + fun langInterceptor(): Interceptor = Interceptor { chain -> + val request = chain.request() + val newUrl = request.url.newBuilder() + .addQueryParameter(QUERY_LANG, LANG) + .build() + + chain.proceed( + request.newBuilder() + .url(newUrl) + .build() + ) + } + + @Provides + @Units + fun unitsInterceptor(): Interceptor = Interceptor { chain -> + val request = chain.request() + val newUrl = request.url.newBuilder() + .addQueryParameter(QUERY_UNITS, UNITS) + .build() + + chain.proceed( + request.newBuilder() + .url(newUrl) + .build() + ) + } + + @Provides + @Logger + fun provideLoggingInterceptor(): Interceptor { + return HttpLoggingInterceptor() + .setLevel( + HttpLoggingInterceptor.Level.BODY + ) + } + + @Provides + fun provideCacheDirectory(): File { + return File("cache"); + } + + @Qualifier + annotation class CacheMaxSize + + @CacheMaxSize + @Provides + fun provideCacheMaxSize(): Long { + return 50 * 1024 * 1024; + } + + @Provides + fun provideCache( + cacheDirectory: File, + @CacheMaxSize cacheMaxSize: Long + ): Cache { + return Cache(cacheDirectory, cacheMaxSize) + } + + @Provides + fun provideOkhttpClient( + cache: Cache, + @ApiKey apiKeyInterceptor: Interceptor, + @Lang langInterceptor: Interceptor, + @Units unitsInterceptor: Interceptor, + @Logger loggingInterceptor: Interceptor, + ): OkHttpClient = + OkHttpClient.Builder() + .cache(cache) + .addInterceptor(apiKeyInterceptor) + .addInterceptor(langInterceptor) + .addInterceptor(unitsInterceptor) + .also { + if (BuildConfig.DEBUG) { + it.addInterceptor( + loggingInterceptor + ) + } + } + .build() + + @Provides + fun provideApi( + okHttpClient: OkHttpClient, + baseUrl: String, + gsonConverter: GsonConverterFactory, + ): Api = + Retrofit.Builder() + .baseUrl(baseUrl) + .client(okHttpClient) + .addConverterFactory(gsonConverter) + .build() + .create(Api::class.java) + + @Provides + fun provideBaseUrl(): String = BASE_URL + + @Provides + fun provideGsonConverter(): GsonConverterFactory = GsonConverterFactory.create() +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/RepoModule.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/RepoModule.kt new file mode 100644 index 0000000..d2516ac --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/RepoModule.kt @@ -0,0 +1,17 @@ +package ru.itis.karakurik.androidLab2.di.module + +import dagger.Binds +import dagger.Module +import ru.itis.karakurik.androidLab2.data.api.repository.WeatherRepositoryImpl +import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository +import javax.inject.Singleton + +@Module +interface RepoModule { + + @Singleton + @Binds + fun bindWeatherRepository( + impl: WeatherRepositoryImpl, + ): WeatherRepository +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/ViewModelModule.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/ViewModelModule.kt new file mode 100644 index 0000000..8c8f0df --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/di/module/ViewModelModule.kt @@ -0,0 +1,34 @@ +package ru.itis.karakurik.androidLab2.di.module + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import ru.itis.karakurik.androidLab2.di.annotation.ViewModelKey +import ru.itis.karakurik.androidLab2.presentation.common.utils.AppViewModelFactory +import ru.itis.karakurik.androidLab2.presentation.fragments.cities.CityListViewModel +import ru.itis.karakurik.androidLab2.presentation.fragments.weather.WeatherViewModel + +@Module +interface ViewModelModule { + + @Binds + fun bindViewModelFactory( + factory: AppViewModelFactory + ): ViewModelProvider.Factory + + @Binds + @IntoMap + @ViewModelKey(CityListViewModel::class) + fun bindCityListViewModel( + viewModel: CityListViewModel + ): ViewModel + + @Binds + @IntoMap + @ViewModelKey(WeatherViewModel::class) + fun bindWeatherViewModel( + viewModel: WeatherViewModel + ): ViewModel +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCase.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCase.kt index 4058eee..8385944 100644 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCase.kt +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherListUseCase.kt @@ -3,13 +3,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.di.module.AppModule import ru.itis.karakurik.androidLab2.domain.entity.Weather import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository +import javax.inject.Inject +import javax.inject.Singleton -class GetWeatherListUseCase( +@Singleton +class GetWeatherListUseCase @Inject constructor( private val weatherRepository: WeatherRepository, - private val dispatcher: CoroutineDispatcher = Dispatchers.Main + @AppModule.DefaultDispatcher 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/domain/usecase/GetWeatherUseCase.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/domain/usecase/GetWeatherUseCase.kt index a0c0ce6..e36d6fd 100644 --- 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 @@ -3,12 +3,17 @@ package ru.itis.karakurik.androidLab2.domain.usecase import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import ru.itis.karakurik.androidLab2.di.module.AppModule import ru.itis.karakurik.androidLab2.domain.entity.Weather import ru.itis.karakurik.androidLab2.domain.repository.WeatherRepository +import javax.inject.Inject +import javax.inject.Named +import javax.inject.Singleton -class GetWeatherUseCase( +@Singleton +class GetWeatherUseCase @Inject constructor( private val weatherRepository: WeatherRepository, - private val dispatcher: CoroutineDispatcher = Dispatchers.Main + @AppModule.DefaultDispatcher private val dispatcher: CoroutineDispatcher = Dispatchers.Main ) { suspend operator fun invoke(city: String): Weather { 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/MainActivity.kt similarity index 83% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/MainActivity.kt index 403f339..e63d555 100644 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/activities/MainActivity.kt +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/MainActivity.kt @@ -1,10 +1,11 @@ -package ru.itis.karakurik.androidLab2.presentation.activities +package ru.itis.karakurik.androidLab2.presentation import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.navigation.NavController import by.kirich1409.viewbindingdelegate.viewBinding import ru.itis.karakurik.androidLab2.R +import ru.itis.karakurik.androidLab2.WeatherApp import ru.itis.karakurik.androidLab2.databinding.ActivityMainBinding import ru.itis.karakurik.androidLab2.extentions.findController @@ -13,6 +14,7 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) { private var controller: NavController? = null override fun onCreate(savedInstanceState: Bundle?) { + (application as WeatherApp).appComponent.inject(this) super.onCreate(savedInstanceState) controller = binding.navHostFragmentMain.id.let { findController(it) } } 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/common/convertors/TempColorConverter.kt similarity index 85% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/convertors/TempColorConverter.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/convertors/TempColorConverter.kt index 365a004..38a656f 100644 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/convertors/TempColorConverter.kt +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/convertors/TempColorConverter.kt @@ -1,8 +1,9 @@ -package ru.itis.karakurik.androidLab2.presentation.convertors +package ru.itis.karakurik.androidLab2.presentation.common.convertors import ru.itis.karakurik.androidLab2.R object TempColorConverter { + fun getColor(temp: Double): Int { return when { temp < -20 -> R.color.temp_less_minus20 diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/utils/AppViewModelFactory.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/utils/AppViewModelFactory.kt new file mode 100644 index 0000000..3df0820 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/utils/AppViewModelFactory.kt @@ -0,0 +1,19 @@ +package ru.itis.karakurik.androidLab2.presentation.common.utils + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import javax.inject.Inject +import javax.inject.Provider +import javax.inject.Singleton + +class AppViewModelFactory @Inject constructor( + private val viewModelMap: Map, @JvmSuppressWildcards Provider> +) : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + val result = viewModelMap[modelClass] ?: viewModelMap.entries.firstOrNull { + modelClass.isAssignableFrom(it.key) + }?.value ?: throw IllegalArgumentException("Unknown model class $modelClass") + @Suppress("UNCHECKED_CAST") + return result.get() as T + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/Event.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/utils/Event.kt similarity index 100% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/Event.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/common/utils/Event.kt diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/MainViewModel.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/CityListViewModel.kt similarity index 61% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/MainViewModel.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/CityListViewModel.kt index 7f3e983..173ac7e 100644 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/MainViewModel.kt +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/CityListViewModel.kt @@ -1,6 +1,5 @@ -package ru.itis.karakurik.androidLab2.presentation +package ru.itis.karakurik.androidLab2.presentation.fragments.cities -import Event import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -9,32 +8,18 @@ import kotlinx.coroutines.launch import ru.itis.karakurik.androidLab2.domain.entity.Weather import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherListUseCase import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase +import javax.inject.Inject -class MainViewModel( +class CityListViewModel @Inject constructor( private val getWeatherUseCase: GetWeatherUseCase, private val getWeatherListUseCase: GetWeatherListUseCase ) : ViewModel() { - private val _weather: MutableLiveData> = MutableLiveData() - val weather: LiveData> = _weather - private val _weatherList: MutableLiveData>> = MutableLiveData() val weatherList: LiveData>> = _weatherList - private val _cityId: MutableLiveData>> = MutableLiveData() - val cityId: LiveData>> = _cityId - - fun onGetWeather(cityId : Int) { - viewModelScope.launch { - kotlin.runCatching { - getWeatherUseCase(cityId) - }.onSuccess { - _weather.value = Result.success(it) - }.onFailure { - _weather.value = Result.failure(it) - } - } - } + private val _cityId: MutableLiveData> = MutableLiveData() + val cityId: LiveData> = _cityId fun onGetWeatherList(lat: Double, lon: Double, cnt: Int) { viewModelScope.launch { @@ -53,9 +38,9 @@ class MainViewModel( kotlin.runCatching { getWeatherUseCase(cityName).id }.onSuccess { - _cityId.value = Event(Result.success(it)) + _cityId.value = Result.success(it) }.onFailure { - _cityId.value = Event(Result.failure(it)) + _cityId.value = Result.failure(it) } } } 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/cities/SearchFragment.kt similarity index 78% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/SearchFragment.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/SearchFragment.kt index 24e22c1..03f64ee 100644 --- 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/cities/SearchFragment.kt @@ -1,4 +1,4 @@ -package ru.itis.karakurik.androidLab2.presentation.fragments.list +package ru.itis.karakurik.androidLab2.presentation.fragments.cities import android.Manifest.permission.* import android.content.Intent @@ -13,25 +13,21 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.widget.SearchView import androidx.core.app.ActivityCompat import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.findNavController -import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import com.google.android.gms.location.FusedLocationProviderClient -import com.google.android.gms.location.LocationServices import com.google.android.material.snackbar.Snackbar -import ru.itis.karakurik.androidLab2.R +import ru.itis.karakurik.androidLab2.WeatherApp import ru.itis.karakurik.androidLab2.databinding.FragmentSearchBinding -import ru.itis.karakurik.androidLab2.di.DiContainer -import ru.itis.karakurik.androidLab2.presentation.MainViewModel -import ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler.ListRecyclerAdapter -import ru.itis.karakurik.androidLab2.presentation.utils.ViewModelFactory +import ru.itis.karakurik.androidLab2.presentation.fragments.cities.recycler.ListRecyclerAdapter import timber.log.Timber +import javax.inject.Inject 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 TRANSITION_NAME = "transition_name" class SearchFragment : Fragment() { @@ -48,20 +44,22 @@ class SearchFragment : Fragment() { private var listRecyclerAdapter: ListRecyclerAdapter? = null private var userLat: Double = DEFAULT_LAT private var userLon: Double = DEFAULT_LON - private var userLocation: FusedLocationProviderClient? = null - private val viewModel by lazy { - ViewModelProvider( - requireActivity(), - ViewModelFactory( - DiContainer.getWeatherUseCase, - DiContainer.getWeatherListUseCase - ) - )[MainViewModel::class.java] + @Inject + lateinit var factory: ViewModelProvider.Factory + + private val viewModel: CityListViewModel by viewModels { + factory } + @Inject + lateinit var smoothScroller: RecyclerView.SmoothScroller + + @Inject + lateinit var userLocation: FusedLocationProviderClient + private val requestPermissions = - registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { it -> + registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { var allPermissionsGranted = true for (granted in it.values) { allPermissionsGranted = allPermissionsGranted and granted @@ -77,12 +75,9 @@ class SearchFragment : Fragment() { } } - private val smoothScroller: RecyclerView.SmoothScroller by lazy { - object : LinearSmoothScroller(context) { - override fun getVerticalSnapPreference(): Int { - return SNAP_TO_START - } - } + override fun onCreate(savedInstanceState: Bundle?) { + (activity?.application as WeatherApp).appComponent.inject(this) + super.onCreate(savedInstanceState) } override fun onCreateView( @@ -109,22 +104,20 @@ class SearchFragment : Fragment() { private fun initObservers() { with(viewModel) { - cityId.observe(viewLifecycleOwner) { event -> - event.getContentIfNotHandled()?.let { result -> - result.fold( - onSuccess = { - showDetailsFragment(it) - }, - onFailure = { - Toast.makeText( - context, - "Не удалось найти такой город", - Toast.LENGTH_LONG - ).show() - Timber.d("Do not found city") - } - ) - } + cityId.observe(viewLifecycleOwner) { result -> + result.fold( + onSuccess = { + showDetailsFragment(it) + }, + onFailure = { + Toast.makeText( + context, + "Не удалось найти такой город", + Toast.LENGTH_LONG + ).show() + Timber.d("Do not found city") + } + ) } weatherList.observe(viewLifecycleOwner) { result -> @@ -176,8 +169,7 @@ class SearchFragment : Fragment() { requestPermissions.launch(permissions) } else { Timber.d("Get user location") - userLocation = LocationServices.getFusedLocationProviderClient(requireContext()) - userLocation?.lastLocation?.addOnSuccessListener { location -> + userLocation.lastLocation.addOnSuccessListener { location -> if (location != null) { userLon = location.longitude userLat = location.latitude 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/cities/recycler/CityWeatherDiffCallback.kt similarity index 83% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/CityWeatherDiffCallback.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/recycler/CityWeatherDiffCallback.kt index a732bab..bcede50 100644 --- 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/cities/recycler/CityWeatherDiffCallback.kt @@ -1,9 +1,10 @@ -package ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler +package ru.itis.karakurik.androidLab2.presentation.fragments.cities.recycler import androidx.recyclerview.widget.DiffUtil import ru.itis.karakurik.androidLab2.domain.entity.Weather -object CityWeatherDiffCallback : DiffUtil.ItemCallback(){ +object CityWeatherDiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: Weather, newItem: Weather): Boolean { return oldItem.id == newItem.id } @@ -11,5 +12,4 @@ object CityWeatherDiffCallback : DiffUtil.ItemCallback(){ 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/cities/recycler/ListItemViewHolder.kt similarity index 80% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListItemViewHolder.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/recycler/ListItemViewHolder.kt index 6f96bf5..a9554a2 100644 --- 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/cities/recycler/ListItemViewHolder.kt @@ -1,14 +1,14 @@ -package ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler; +package ru.itis.karakurik.androidLab2.presentation.fragments.cities.recycler; import android.view.LayoutInflater import android.view.ViewGroup -import androidx.annotation.ColorRes import androidx.core.content.ContextCompat import androidx.recyclerview.widget.RecyclerView import coil.load +import ru.itis.karakurik.androidLab2.R import ru.itis.karakurik.androidLab2.databinding.ListItemCityBinding import ru.itis.karakurik.androidLab2.domain.entity.Weather -import ru.itis.karakurik.androidLab2.presentation.convertors.TempColorConverter.getColor +import ru.itis.karakurik.androidLab2.presentation.common.convertors.TempColorConverter.getColor class ListItemViewHolder( private val binding: ListItemCityBinding, @@ -16,10 +16,9 @@ class ListItemViewHolder( ) : RecyclerView.ViewHolder(binding.root) { fun bind(weather: Weather) { - with(binding) { tvCityItem.text = weather.name - tvTempItem.text = weather.temp.toString() + tvTempItem.text = itemView.context.resources.getString(R.string.temp_with_symbol, weather.temp) tvTempItem.setTextColor( ContextCompat.getColor( tvTempItem.context, @@ -50,5 +49,4 @@ class ListItemViewHolder( 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/cities/recycler/ListRecyclerAdapter.kt similarity index 88% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/list/recycler/ListRecyclerAdapter.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/cities/recycler/ListRecyclerAdapter.kt index 97ae06d..368ca7b 100644 --- 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/cities/recycler/ListRecyclerAdapter.kt @@ -1,4 +1,4 @@ -package ru.itis.karakurik.androidLab2.presentation.fragments.list.recycler; +package ru.itis.karakurik.androidLab2.presentation.fragments.cities.recycler; import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter @@ -7,6 +7,7 @@ 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 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/weather/DetailsFragment.kt similarity index 65% rename from app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt rename to app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/DetailsFragment.kt index 9ee987e..39cb4be 100644 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/DetailsFragment.kt +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/DetailsFragment.kt @@ -1,4 +1,4 @@ -package ru.itis.karakurik.androidLab2.presentation.fragments +package ru.itis.karakurik.androidLab2.presentation.fragments.weather import android.annotation.SuppressLint import android.os.Bundle @@ -7,29 +7,31 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider +import androidx.fragment.app.viewModels import coil.load import ru.itis.karakurik.androidLab2.R +import ru.itis.karakurik.androidLab2.WeatherApp 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.presentation.MainViewModel -import ru.itis.karakurik.androidLab2.presentation.convertors.TempColorConverter -import ru.itis.karakurik.androidLab2.presentation.utils.ViewModelFactory +import ru.itis.karakurik.androidLab2.presentation.common.convertors.TempColorConverter +import ru.itis.karakurik.androidLab2.presentation.common.utils.AppViewModelFactory import timber.log.Timber +import javax.inject.Inject class DetailsFragment : Fragment() { private var _binding: FragmentDetailsBinding? = null private val binding get() = _binding!! - private val viewModel by lazy { - ViewModelProvider( - this, - ViewModelFactory( - DiContainer.getWeatherUseCase, - DiContainer.getWeatherListUseCase - ) - )[MainViewModel::class.java] + @Inject + lateinit var factory: AppViewModelFactory + + private val viewModel: WeatherViewModel by viewModels { + factory + } + + override fun onCreate(savedInstanceState: Bundle?) { + (activity?.application as WeatherApp).appComponent.inject(this) + super.onCreate(savedInstanceState) } override fun onCreateView( @@ -70,13 +72,13 @@ class DetailsFragment : Fragment() { weather.run { setWeather(weather) tvCity.text = name - tvTemp.text = "${temp}°C" - tvTempMin.text = "${tempMin}°C" - tvTempMax.text = "${tempMax}°C" + tvTemp.text = getString(R.string.temp_with_symbol, temp) + tvTempMin.text = getString(R.string.temp_with_symbol, tempMin) + tvTempMax.text = getString(R.string.temp_with_symbol, tempMax) tvWindDeg.text = windDeg.toString() - tvWindSpeed.text = "${windSpeed}km/h" - tvHumidity.text = "${humidity}%" - tvPressure.text = "${pressure}P" + tvWindSpeed.text = getString(R.string.wind_speed_with_symbol, windSpeed) + tvHumidity.text = getString(R.string.humidity_with_symbol, humidity) + tvPressure.text = getString(R.string.pressure_with_symbol, pressure) ivAir.load(iconUrl) arguments?.getString(R.string.transition_name.toString())?.let { diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt new file mode 100644 index 0000000..1dcdfb6 --- /dev/null +++ b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/fragments/weather/WeatherViewModel.kt @@ -0,0 +1,29 @@ +package ru.itis.karakurik.androidLab2.presentation.fragments.weather + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch +import ru.itis.karakurik.androidLab2.domain.entity.Weather +import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase +import javax.inject.Inject + +class WeatherViewModel @Inject constructor( + private val getWeatherUseCase: GetWeatherUseCase +) : ViewModel() { + private var _weather: MutableLiveData> = MutableLiveData() + val weather: LiveData> get() = _weather + + fun onGetWeather(cityId: Int) { + viewModelScope.launch { + kotlin.runCatching { + getWeatherUseCase(cityId) + }.onSuccess { + _weather.value = Result.success(it) + }.onFailure { + _weather.value = Result.failure(it) + } + } + } +} diff --git a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/ViewModelFactory.kt b/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/ViewModelFactory.kt deleted file mode 100644 index 1ccf37f..0000000 --- a/app/src/main/java/ru/itis/karakurik/androidLab2/presentation/utils/ViewModelFactory.kt +++ /dev/null @@ -1,26 +0,0 @@ -package ru.itis.karakurik.androidLab2.presentation.utils - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.ViewModelProvider -import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherListUseCase -import ru.itis.karakurik.androidLab2.domain.usecase.GetWeatherUseCase -import ru.itis.karakurik.androidLab2.presentation.MainViewModel -import java.lang.IllegalArgumentException - -class ViewModelFactory( - private val getWeatherUseCase: GetWeatherUseCase, - private val getWeatherListUseCase: GetWeatherListUseCase -) : ViewModelProvider.Factory { - - override fun create(modelClass: Class): T { - return when { - modelClass.isAssignableFrom(MainViewModel::class.java) -> - MainViewModel( - getWeatherUseCase, - getWeatherListUseCase - ) as? T ?: throw IllegalArgumentException("Unknown ViewModel class") - - else -> throw IllegalArgumentException("Unknown ViewModel class") - } - } -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index bc0b2ce..9a47ad7 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" - tools:context=".presentation.activities.MainActivity"> + tools:context=".presentation.MainActivity"> + tools:context=".presentation.fragments.weather.DetailsFragment"> @@ -18,7 +18,7 @@ diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57cbd11..000b771 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,4 +13,9 @@ transactionName city_id Температура + + %1$.2f°C + %1$.2fkm/h + %1$d%% + %1$dP diff --git a/build.gradle b/build.gradle index 4079b88..5c5efc3 100644 --- a/build.gradle +++ b/build.gradle @@ -3,11 +3,13 @@ buildscript { repositories { google() mavenCentral() + gradlePluginPortal() } dependencies { classpath 'com.android.tools.build:gradle:7.1.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.4.1" + classpath "com.google.dagger:hilt-android-gradle-plugin:2.37" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -17,6 +19,28 @@ buildscript { plugins { id "io.gitlab.arturbosch.detekt" version "1.18.1" id 'org.jetbrains.kotlin.android' version '1.6.10' apply false + id "scabbard.gradle" version "0.5.0" +} + +scabbard { + enabled true + outputFormat "svg" +} + +allprojects { + /*repositories { + google() + mavenCentral() + }*/ + + configurations.all { + resolutionStrategy.eachDependency { + if (requested.group == "com.github.kittinunf.result" && requested.name == "result" && requested.version == "3.0.0") { + useVersion("3.0.1") + because("Transitive dependency of Scabbard, currently not available on mavenCentral()") + } + } + } } task clean(type: Delete) {