diff --git a/app/build.gradle b/app/build.gradle index e515992..32862f4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,9 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'org.jetbrains.kotlin.plugin.serialization' version "2.1.0" + id 'com.google.devtools.ksp' version '2.1.0-1.0.29' + id 'com.google.dagger.hilt.android' version '2.57.1' } android { @@ -15,6 +18,13 @@ android { versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + def properties = new Properties() + properties.load(rootProject.file("local.properties").newDataInputStream()) + + def apiKey = properties.getProperty("API_KEY") ?: "" + buildConfigField "String", "API_KEY", apiKey + } buildTypes { @@ -23,6 +33,11 @@ android { proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } + + buildFeatures { + buildConfig = true + } + compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 @@ -41,4 +56,24 @@ dependencies { testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + + //Retrofit + implementation 'com.squareup.retrofit2:retrofit:2.11.0' + + //Serialization + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3' + implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3' + implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0' + + //OkHttp (MediaType & Interceptor) + implementation 'com.squareup.okhttp3:okhttp:4.12.0' + + //ViewModel + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7' + implementation 'androidx.activity:activity-ktx:1.10.0' + implementation 'androidx.fragment:fragment-ktx:1.8.9' + + //Hilt + implementation 'com.google.dagger:hilt-android:2.57.1' + ksp 'com.google.dagger:hilt-compiler:2.57.1' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea17fa5..4818c95 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,7 +2,10 @@ + + = emptyList() +) \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCache.kt b/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCache.kt new file mode 100644 index 0000000..199b454 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCache.kt @@ -0,0 +1,30 @@ +package ru.otus.basicarchitecture.data.localbase + +import ru.otus.basicarchitecture.domain.Interests +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WizardCache @Inject constructor() { + private var user = UserData() + + fun updateUserPersonalInformation(name: String, surname: String, age: Int?) { + user = user.copy(name = name, surname = surname, age = age) + } + + fun updateUserAddress(address: String) { + user = user.copy(address = address) + } + + fun updateUserInterests(interests: List) { + user = user.copy(interests = interests) + } + + fun getUserInfo(): UserData { + return user + } + + fun clearUserInfo() { + user = UserData() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCacheModule.kt b/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCacheModule.kt new file mode 100644 index 0000000..650a025 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCacheModule.kt @@ -0,0 +1,15 @@ +package ru.otus.basicarchitecture.data.localbase + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ru.otus.basicarchitecture.domain.WizardCacheService + +@Module +@InstallIn(SingletonComponent::class) +abstract class WizardCacheModule { + + @Binds + abstract fun bindWizardCacheService(impl: WizardCacheServiceImpl): WizardCacheService +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCacheServiceImpl.kt b/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCacheServiceImpl.kt new file mode 100644 index 0000000..a450859 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/localbase/WizardCacheServiceImpl.kt @@ -0,0 +1,31 @@ +package ru.otus.basicarchitecture.data.localbase + +import ru.otus.basicarchitecture.domain.Interests +import ru.otus.basicarchitecture.domain.WizardCacheService +import javax.inject.Inject + +class WizardCacheServiceImpl @Inject constructor(private val wizardCache: WizardCache) : + WizardCacheService { + + override fun updateUserPersonalInformation( + name: String, + surname: String, + age: Int? + ) { + wizardCache.updateUserPersonalInformation(name = name, surname = surname, age = age) + } + + override fun updateUserAddress(address: String) { + wizardCache.updateUserAddress(address = address) + } + + override fun updateUserInterests(interests: List) { + wizardCache.updateUserInterests(interests = interests) + } + + override fun getUserInfo(): UserData = wizardCache.getUserInfo() + + override fun clearUserInfo() { + wizardCache.clearUserInfo() + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressApi.kt b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressApi.kt new file mode 100644 index 0000000..2acc9c7 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressApi.kt @@ -0,0 +1,11 @@ +package ru.otus.basicarchitecture.data.network + +import retrofit2.http.Body +import retrofit2.http.POST + +interface AddressApi { + @POST("api/4_1/rs/suggest/address") + suspend fun getHints( + @Body query: AddressQuery + ): AddressResponse +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressQuery.kt b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressQuery.kt new file mode 100644 index 0000000..12d8c5d --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressQuery.kt @@ -0,0 +1,8 @@ +package ru.otus.basicarchitecture.data.network + +import kotlinx.serialization.Serializable + +@Serializable +data class AddressQuery( + val query: String +) \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressResponse.kt b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressResponse.kt new file mode 100644 index 0000000..6245be8 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressResponse.kt @@ -0,0 +1,13 @@ +package ru.otus.basicarchitecture.data.network + +import kotlinx.serialization.Serializable + +@Serializable +data class AddressResponse( + val suggestions: List +) + +@Serializable +data class AddressSuggestion( + val value: String +) diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressServiceImpl.kt b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressServiceImpl.kt new file mode 100644 index 0000000..fbf12ce --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressServiceImpl.kt @@ -0,0 +1,10 @@ +package ru.otus.basicarchitecture.data.network + +import ru.otus.basicarchitecture.domain.AddressService +import javax.inject.Inject + +class AddressServiceImpl @Inject constructor(private val addressApi: AddressApi) : AddressService { + override suspend fun getHints(query: AddressQuery): AddressResponse { + return addressApi.getHints(query = query) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressServiceModule.kt b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressServiceModule.kt new file mode 100644 index 0000000..65392a8 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/network/AddressServiceModule.kt @@ -0,0 +1,15 @@ +package ru.otus.basicarchitecture.data.network + +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import ru.otus.basicarchitecture.domain.AddressService + +@Module +@InstallIn(SingletonComponent::class) +abstract class AddressServiceModule { + + @Binds + abstract fun bindAddressService(impl: AddressServiceImpl): AddressService +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/data/network/NetworkModule.kt b/app/src/main/java/ru/otus/basicarchitecture/data/network/NetworkModule.kt new file mode 100644 index 0000000..a6cfa2c --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/data/network/NetworkModule.kt @@ -0,0 +1,62 @@ +package ru.otus.basicarchitecture.data.network + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.Interceptor +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.Request +import retrofit2.Retrofit +import ru.otus.basicarchitecture.BuildConfig +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + private const val apiKey = BuildConfig.API_KEY + + private const val BASE_URL = "https://suggestions.dadata.ru/suggestions/" + + private val json = Json { + ignoreUnknownKeys = true + } + + //TODO api key вынести + private fun okHttpClient( + apiKey: String = NetworkModule.apiKey + ) = OkHttpClient().newBuilder() + .addInterceptor( + Interceptor { chain -> + val request: Request = chain.request() + .newBuilder() + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("Authorization", "Token $apiKey") + .build() + chain.proceed(request) + } + ) + + @Provides + @Singleton + fun providesRetrofit(): Retrofit { + return Retrofit.Builder() + .baseUrl(BASE_URL) + .client(okHttpClient().build()) + .addConverterFactory( + json.asConverterFactory("application/json".toMediaType()) + ) + .build() + } + + @Provides + @Singleton + fun provideAddressHintService(retrofit: Retrofit): AddressApi { + return retrofit.create(AddressApi::class.java) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/domain/AddressService.kt b/app/src/main/java/ru/otus/basicarchitecture/domain/AddressService.kt new file mode 100644 index 0000000..45a9952 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/domain/AddressService.kt @@ -0,0 +1,10 @@ +package ru.otus.basicarchitecture.domain + +import ru.otus.basicarchitecture.data.network.AddressQuery +import ru.otus.basicarchitecture.data.network.AddressResponse + +interface AddressService { + suspend fun getHints( + query: AddressQuery + ): AddressResponse +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/domain/Interests.kt b/app/src/main/java/ru/otus/basicarchitecture/domain/Interests.kt new file mode 100644 index 0000000..527b277 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/domain/Interests.kt @@ -0,0 +1,16 @@ +package ru.otus.basicarchitecture.domain + +enum class Interests { + MUSIC, + MUSEUMS, + MODERN_ART, + CARS, + BICYCLES, + PARTIES, + THEATRE, + MOVIES, + GARDENING, + HIKING, + LITERATURE, + CRAFT +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/domain/WizardCacheService.kt b/app/src/main/java/ru/otus/basicarchitecture/domain/WizardCacheService.kt new file mode 100644 index 0000000..4f09cb3 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/domain/WizardCacheService.kt @@ -0,0 +1,11 @@ +package ru.otus.basicarchitecture.domain + +import ru.otus.basicarchitecture.data.localbase.UserData + +interface WizardCacheService { + fun updateUserPersonalInformation(name: String, surname: String, age: Int?) + fun updateUserAddress(address: String) + fun updateUserInterests(interests: List) + fun getUserInfo(): UserData + fun clearUserInfo() +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/AddressFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/AddressFragment.kt new file mode 100644 index 0000000..546eca6 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/AddressFragment.kt @@ -0,0 +1,105 @@ +package ru.otus.basicarchitecture.ui + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.Button +import androidx.appcompat.widget.AppCompatAutoCompleteTextView +import androidx.core.widget.doOnTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.google.android.material.textfield.TextInputLayout +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import ru.otus.basicarchitecture.R + +@AndroidEntryPoint +class AddressFragment : Fragment() { + + private val viewModel: AddressViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return inflater.inflate(R.layout.fragment_address, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val addressLayout = view.findViewById(R.id.address_layout) + + val addressTextView = + view.findViewById(R.id.address_text_view) + + val nextBtn = view.findViewById