diff --git a/app/build.gradle b/app/build.gradle
index 9c99d98..81479e5 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,12 +1,18 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
+ id 'com.google.dagger.hilt.android'
+ id 'kotlin-kapt'
}
android {
namespace 'ru.otus.basicarchitecture'
compileSdk 33
+ buildFeatures {
+ buildConfig = true
+ }
+
defaultConfig {
applicationId "ru.otus.basicarchitecture"
minSdk 24
@@ -15,6 +21,8 @@ android {
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ buildConfigField("String", "DADATA_API_KEY", "\"${project.DADATA_API_KEY}\"")
}
buildTypes {
@@ -24,11 +32,11 @@ android {
}
}
compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
+ sourceCompatibility JavaVersion.VERSION_17
+ targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
- jvmTarget = '1.8'
+ jvmTarget = '17'
}
}
@@ -38,7 +46,32 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
+ implementation 'androidx.fragment:fragment-ktx:1.5.6'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
+
+ implementation 'com.google.android.flexbox:flexbox:3.0.0'
+
+
+ // Hilt
+ implementation 'com.google.dagger:hilt-android:2.52'
+ kapt 'com.google.dagger:hilt-compiler:2.52'
+
+ def lifecycle_version = "2.6.1"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version"
+ implementation 'androidx.activity:activity-ktx:1.7.2'
+
+ // Retrofit2
+ implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
+ implementation "com.squareup.retrofit2:retrofit:2.9.0"
+ implementation "com.squareup.retrofit2:converter-gson:2.9.0"
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
+}
+
+kapt {
+ correctErrorTypes true
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e81fea..19a3ac8 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,8 +1,10 @@
+
+ android:name=".ui.MainActivity"
+ android:exported="true">
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt b/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt
deleted file mode 100644
index 623aba9..0000000
--- a/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package ru.otus.basicarchitecture
-
-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)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/di/MyApplication.kt b/app/src/main/java/ru/otus/basicarchitecture/di/MyApplication.kt
new file mode 100644
index 0000000..f829e8e
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/di/MyApplication.kt
@@ -0,0 +1,23 @@
+package ru.otus.basicarchitecture.di
+
+import android.app.Application
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.HiltAndroidApp
+import dagger.hilt.components.SingletonComponent
+import ru.otus.basicarchitecture.repository.WizardCache
+import javax.inject.Singleton
+
+@HiltAndroidApp
+class MyApplication : Application()
+
+@InstallIn(SingletonComponent::class)
+@Module
+object CacheModule {
+ @Provides
+ @Singleton
+ fun provideWizardCache(): WizardCache {
+ return WizardCache()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/network/DadataApi.kt b/app/src/main/java/ru/otus/basicarchitecture/network/DadataApi.kt
new file mode 100644
index 0000000..24cc7cb
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/network/DadataApi.kt
@@ -0,0 +1,16 @@
+package ru.otus.basicarchitecture.network
+
+import retrofit2.http.Body
+import retrofit2.http.Headers
+import retrofit2.http.POST
+
+interface DadataApi {
+ @Headers("Content-Type: application/json", "Accept: application/json")
+ @POST("suggestions/api/4_1/rs/suggest/address")
+ suspend fun getAddressSuggestions(@Body request: AddressSuggestionRequest): DadataResponse
+}
+
+data class AddressSuggestionRequest(val query: String)
+
+data class DadataResponse(val suggestions: List)
+data class AddressSuggestion(val value: String)
diff --git a/app/src/main/java/ru/otus/basicarchitecture/network/DadataApiService.kt b/app/src/main/java/ru/otus/basicarchitecture/network/DadataApiService.kt
new file mode 100644
index 0000000..596e587
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/network/DadataApiService.kt
@@ -0,0 +1,42 @@
+package ru.otus.basicarchitecture.network
+
+import okhttp3.Interceptor
+import okhttp3.OkHttpClient
+import okhttp3.logging.HttpLoggingInterceptor
+import retrofit2.Retrofit
+import retrofit2.converter.gson.GsonConverterFactory
+import ru.otus.basicarchitecture.BuildConfig
+import java.util.concurrent.TimeUnit
+
+object DadataApiService {
+ private const val BASE_URL = "https://suggestions.dadata.ru/"
+ private const val API_KEY = BuildConfig.DADATA_API_KEY
+
+ private val client: OkHttpClient by lazy {
+ val loggingInterceptor = HttpLoggingInterceptor().apply {
+ level = HttpLoggingInterceptor.Level.BODY
+ }
+ val headerInterceptor = Interceptor { chain ->
+ val request = chain.request().newBuilder()
+ .addHeader("Authorization", "Token $API_KEY")
+ .build()
+ chain.proceed(request)
+ }
+
+ OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .addInterceptor(headerInterceptor)
+ .connectTimeout(30, TimeUnit.SECONDS)
+ .readTimeout(30, TimeUnit.SECONDS)
+ .build()
+ }
+
+ val api: DadataApi by lazy {
+ Retrofit.Builder()
+ .baseUrl(BASE_URL)
+ .client(client)
+ .addConverterFactory(GsonConverterFactory.create())
+ .build()
+ .create(DadataApi::class.java)
+ }
+}
diff --git a/app/src/main/java/ru/otus/basicarchitecture/repository/WizardCache.kt b/app/src/main/java/ru/otus/basicarchitecture/repository/WizardCache.kt
new file mode 100644
index 0000000..dbc988a
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/repository/WizardCache.kt
@@ -0,0 +1,18 @@
+package ru.otus.basicarchitecture.repository
+
+import javax.inject.Inject
+
+class WizardCache @Inject constructor() {
+ var firstName: String? = null
+ var lastName: String? = null
+ var birthDate: String? = null
+ var country: String? = null
+ var city: String? = null
+ var address: String? = null
+ var interests: List = emptyList()
+
+ fun setInterests(interests: Set) {
+ this.interests = interests.toList()
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/MainActivity.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/MainActivity.kt
new file mode 100644
index 0000000..b1a5120
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/ui/MainActivity.kt
@@ -0,0 +1,25 @@
+package ru.otus.basicarchitecture.ui
+
+import androidx.appcompat.app.AppCompatActivity
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import dagger.hilt.android.AndroidEntryPoint
+import ru.otus.basicarchitecture.ui.fragments.Fragment1
+import ru.otus.basicarchitecture.R
+@AndroidEntryPoint
+class MainActivity : AppCompatActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ loadFragment(Fragment1())
+ }
+
+ private fun loadFragment(fragment: Fragment) {
+ val transaction = supportFragmentManager.beginTransaction()
+ transaction.replace(R.id.fragment_container, fragment)
+ transaction.addToBackStack("null")
+ transaction.commit()
+
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment1.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment1.kt
new file mode 100644
index 0000000..7692722
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment1.kt
@@ -0,0 +1,107 @@
+package ru.otus.basicarchitecture.ui.fragments
+
+import android.os.Build
+import androidx.fragment.app.viewModels
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.EditText
+import androidx.annotation.RequiresApi
+import androidx.core.widget.addTextChangedListener
+import com.google.android.material.snackbar.Snackbar
+import dagger.hilt.android.AndroidEntryPoint
+import ru.otus.basicarchitecture.R
+import ru.otus.basicarchitecture.viewmodels.Fragment1ViewModel
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoUnit
+
+@AndroidEntryPoint
+class Fragment1 : Fragment() {
+ private val viewModel: Fragment1ViewModel by viewModels()
+
+ private lateinit var firstNameEditText: EditText
+ private lateinit var lastNameEditText: EditText
+ private lateinit var birthDateEditText: EditText
+ private lateinit var nextBtn: Button
+
+ private lateinit var toast: View
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_fragment1, container, false)
+ }
+
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ firstNameEditText = view.findViewById(R.id.name)
+ lastNameEditText = view.findViewById(R.id.surname)
+ birthDateEditText = view.findViewById(R.id.birthdate)
+ nextBtn = view.findViewById(R.id.fragment1Btn)
+ toast = view.findViewById(R.id.toast)
+
+ viewModel.firstName.observe(viewLifecycleOwner) {
+ if (it == null || it.isBlank()) {
+ firstNameEditText.error = "Поле не должно быть пустым"
+ } else {
+ firstNameEditText.error = null
+ }
+ }
+
+ viewModel.lastName.observe(viewLifecycleOwner) {
+ if (it == null || it.isBlank()) {
+ lastNameEditText.error = "Поле не должно быть пустым"
+ } else {
+ lastNameEditText.error = null
+ }
+ }
+
+ viewModel.birthDate.observe(viewLifecycleOwner) {
+ try {
+ val formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy")
+ val dob = LocalDate.parse(it, formatter)
+ val years = ChronoUnit.YEARS.between(dob, LocalDate.now())
+ if (years < 18) {
+ birthDateEditText.error = "Вам не исполнилось 18"
+ } else {
+ birthDateEditText.error = null
+ }
+ } catch (e: Exception) {
+ birthDateEditText.error = "Неверный формат даты"
+ }
+ }
+
+ viewModel.isFormValid.observe(viewLifecycleOwner) { isValid ->
+ nextBtn.isEnabled = isValid
+ }
+
+ firstNameEditText.addTextChangedListener { viewModel.firstName.value = it.toString() }
+ lastNameEditText.addTextChangedListener { viewModel.lastName.value = it.toString() }
+ birthDateEditText.addTextChangedListener { viewModel.birthDate.value = it.toString() }
+
+ nextBtn.setOnClickListener {
+ viewModel.firstName.value = firstNameEditText.text.toString()
+ viewModel.lastName.value = lastNameEditText.text.toString()
+ viewModel.birthDate.value = birthDateEditText.text.toString()
+
+ if (nextBtn.isEnabled) {
+ viewModel.saveData()
+ parentFragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, Fragment2())
+ .addToBackStack(null)
+ .commit()
+ } else {
+ Snackbar.make(toast, "Валидация не пройдена", Snackbar.LENGTH_SHORT).show()
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment2.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment2.kt
new file mode 100644
index 0000000..878347b
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment2.kt
@@ -0,0 +1,78 @@
+package ru.otus.basicarchitecture.ui.fragments
+
+import android.os.Build
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import android.widget.AutoCompleteTextView
+import android.widget.Button
+import androidx.annotation.RequiresApi
+import androidx.core.widget.addTextChangedListener
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.viewModels
+import com.google.android.material.snackbar.Snackbar
+import dagger.hilt.android.AndroidEntryPoint
+import ru.otus.basicarchitecture.R
+import ru.otus.basicarchitecture.viewmodels.Fragment2ViewModel
+
+@AndroidEntryPoint
+class Fragment2 : Fragment() {
+ private val viewModel: Fragment2ViewModel by viewModels()
+
+ private lateinit var addressEditText: AutoCompleteTextView
+ private lateinit var nextBtn: Button
+
+ private lateinit var toast: View
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_fragment2, container, false)
+ }
+
+ @RequiresApi(Build.VERSION_CODES.O)
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ addressEditText = view.findViewById(R.id.address)
+ nextBtn = view.findViewById(R.id.fragment2Btn)
+ toast = view.findViewById(R.id.toast)
+
+ viewModel.isFormValid.observe(viewLifecycleOwner) { isValid ->
+ nextBtn.isEnabled = isValid
+ }
+
+ viewModel.addressSuggestions.observe(viewLifecycleOwner) { suggestions ->
+ val adapter = ArrayAdapter(
+ requireContext(),
+ android.R.layout.simple_dropdown_item_1line,
+ suggestions
+ )
+ addressEditText.setAdapter(adapter)
+ if (suggestions.isNotEmpty()) {
+ addressEditText.showDropDown()
+ }
+ }
+
+ addressEditText.addTextChangedListener {
+ viewModel.address.value = it.toString()
+ viewModel.fetchAddressSuggestions(it.toString())
+ }
+
+ nextBtn.setOnClickListener {
+ viewModel.address.value = addressEditText.text.toString()
+ if (nextBtn.isEnabled) {
+ viewModel.saveData()
+ parentFragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, Fragment3())
+ .addToBackStack(null)
+ .commit()
+ } else {
+ Snackbar.make(toast, "Валидация не пройдена", Snackbar.LENGTH_SHORT).show()
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment3.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment3.kt
new file mode 100644
index 0000000..ab0dabb
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment3.kt
@@ -0,0 +1,70 @@
+package ru.otus.basicarchitecture.ui.fragments
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import android.widget.TextView
+import androidx.fragment.app.viewModels
+import com.google.android.flexbox.FlexboxLayout
+import dagger.hilt.android.AndroidEntryPoint
+import ru.otus.basicarchitecture.R
+import ru.otus.basicarchitecture.viewmodels.Fragment3ViewModel
+
+@AndroidEntryPoint
+class Fragment3 : Fragment() {
+ private val viewModel: Fragment3ViewModel by viewModels()
+
+ private lateinit var tagFlexboxLayout: FlexboxLayout
+ private lateinit var nextBtn: Button
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_fragment3, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ tagFlexboxLayout = view.findViewById(R.id.tagFlexboxLayout)
+ nextBtn = view.findViewById(R.id.fragment3Btn)
+
+ viewModel.selectedInterests.observe(viewLifecycleOwner) { selectedInterests ->
+ updateTags(selectedInterests)
+ }
+
+ nextBtn.setOnClickListener {
+ viewModel.saveSelectedInterests()
+ parentFragmentManager.beginTransaction()
+ .replace(R.id.fragment_container, Fragment4())
+ .addToBackStack(null)
+ .commit()
+ }
+
+ viewModel.interests.forEach { interest ->
+ val tagView = createTagView(interest)
+ tagView.setOnClickListener {
+ viewModel.toggleInterest(interest)
+ }
+ tagFlexboxLayout.addView(tagView)
+ }
+ }
+
+ private fun createTagView(interest: String): TextView {
+ val tagView = LayoutInflater.from(context).inflate(R.layout.tag_item, tagFlexboxLayout, false) as TextView
+ tagView.text = interest
+ return tagView
+ }
+
+ private fun updateTags(selectedInterests: Set) {
+ for (i in 0 until tagFlexboxLayout.childCount) {
+ val tagView = tagFlexboxLayout.getChildAt(i) as TextView
+ val interest = tagView.text.toString()
+ tagView.isSelected = selectedInterests.contains(interest)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment4.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment4.kt
new file mode 100644
index 0000000..e84f59b
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/ui/fragments/Fragment4.kt
@@ -0,0 +1,57 @@
+package ru.otus.basicarchitecture.ui.fragments
+
+import android.os.Bundle
+import androidx.fragment.app.Fragment
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.fragment.app.viewModels
+import com.google.android.flexbox.FlexboxLayout
+import dagger.hilt.android.AndroidEntryPoint
+import ru.otus.basicarchitecture.R
+import ru.otus.basicarchitecture.viewmodels.Fragment4ViewModel
+
+@AndroidEntryPoint
+class Fragment4 : Fragment() {
+ private val viewModel: Fragment4ViewModel by viewModels()
+
+ private lateinit var firstNameTextView: TextView
+ private lateinit var lastNameTextView: TextView
+ private lateinit var birthDateTextView: TextView
+ private lateinit var addressTextView: TextView
+ private lateinit var tagFlexboxLayout: FlexboxLayout
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ return inflater.inflate(R.layout.fragment_fragment4, container, false)
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ firstNameTextView = view.findViewById(R.id.tvFirstName)
+ lastNameTextView = view.findViewById(R.id.tvLastName)
+ birthDateTextView = view.findViewById(R.id.tvBirthDate)
+ addressTextView = view.findViewById(R.id.tvAddress)
+ tagFlexboxLayout = view.findViewById(R.id.tagFlexboxLayout)
+
+ firstNameTextView.text = viewModel.firstName ?: "N/A"
+ lastNameTextView.text = viewModel.lastName ?: "N/A"
+ birthDateTextView.text = viewModel.birthDate ?: "N/A"
+ addressTextView.text = viewModel.fullAddress
+
+ viewModel.interests.forEach { interest ->
+ val tagView = createTagView(interest)
+ tagFlexboxLayout.addView(tagView)
+ }
+ }
+
+ private fun createTagView(interest: String): TextView {
+ val tagView = LayoutInflater.from(context).inflate(R.layout.tag_item, tagFlexboxLayout, false) as TextView
+ tagView.text = interest
+ return tagView
+ }
+}
diff --git a/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment1ViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment1ViewModel.kt
new file mode 100644
index 0000000..ad1b8aa
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment1ViewModel.kt
@@ -0,0 +1,53 @@
+package ru.otus.basicarchitecture.viewmodels
+
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import ru.otus.basicarchitecture.repository.WizardCache
+import java.time.LocalDate
+import java.time.format.DateTimeFormatter
+import java.time.temporal.ChronoUnit
+import javax.inject.Inject
+@RequiresApi(Build.VERSION_CODES.O)
+@HiltViewModel
+class Fragment1ViewModel @Inject constructor(private val wizardCache: WizardCache) : ViewModel() {
+
+ val firstName = MutableLiveData()
+ val lastName = MutableLiveData()
+ val birthDate = MutableLiveData()
+
+ private val _isFormValid = MutableLiveData()
+ val isFormValid: LiveData get() = _isFormValid
+
+ init {
+ firstName.observeForever { validateForm() }
+ lastName.observeForever { validateForm() }
+ birthDate.observeForever { validateForm() }
+ }
+
+ private fun validateForm() {
+ val isNameValid = !firstName.value.isNullOrBlank()
+ val isSurnameValid = !lastName.value.isNullOrBlank()
+ val isDateOfBirthValid = birthDate.value?.let {
+ try {
+ val dob = LocalDate.parse(it, DateTimeFormatter.ofPattern("dd.MM.yyyy"))
+ val years = ChronoUnit.YEARS.between(dob, LocalDate.now())
+ years >= 18
+ } catch (e: Exception) {
+ false
+ }
+ } ?: false
+
+ _isFormValid.value = isNameValid && isSurnameValid && isDateOfBirthValid
+ }
+
+ fun saveData() {
+ wizardCache.firstName = firstName.value
+ wizardCache.lastName = lastName.value
+ wizardCache.birthDate = birthDate.value
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment2ViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment2ViewModel.kt
new file mode 100644
index 0000000..7048e9f
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment2ViewModel.kt
@@ -0,0 +1,55 @@
+package ru.otus.basicarchitecture.viewmodels
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.launch
+import ru.otus.basicarchitecture.network.AddressSuggestionRequest
+import ru.otus.basicarchitecture.network.DadataApiService
+import ru.otus.basicarchitecture.repository.WizardCache
+import javax.inject.Inject
+
+@HiltViewModel
+class Fragment2ViewModel @Inject constructor(private val wizardCache: WizardCache) : ViewModel() {
+
+ val address = MutableLiveData()
+ private val _isFormValid = MutableLiveData()
+ val isFormValid: LiveData get() = _isFormValid
+
+ private val _addressSuggestions = MutableLiveData>()
+ val addressSuggestions: LiveData> get() = _addressSuggestions
+
+ init {
+ address.observeForever { validateForm() }
+ }
+
+ private fun validateForm() {
+ val isAddressValid = !address.value.isNullOrBlank()
+ _isFormValid.value = isAddressValid
+ }
+
+ fun fetchAddressSuggestions(query: String) {
+ if (query.isBlank()) {
+ _addressSuggestions.value = emptyList()
+ return
+ }
+
+ viewModelScope.launch {
+ try {
+ val response = DadataApiService.api.getAddressSuggestions(AddressSuggestionRequest(query))
+ _addressSuggestions.value = response.suggestions.map { it.value }
+ } catch (e: Exception) {
+ _addressSuggestions.value = emptyList()
+ }
+ }
+ }
+
+ fun saveData() {
+ val addressParts = address.value?.split(",")?.map { it.trim() }
+ wizardCache.country = addressParts?.getOrNull(0)
+ wizardCache.city = addressParts?.getOrNull(1)
+ wizardCache.address = addressParts?.getOrNull(2)
+ }
+}
diff --git a/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment3ViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment3ViewModel.kt
new file mode 100644
index 0000000..a61f023
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment3ViewModel.kt
@@ -0,0 +1,43 @@
+package ru.otus.basicarchitecture.viewmodels
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import ru.otus.basicarchitecture.repository.WizardCache
+import javax.inject.Inject
+
+@HiltViewModel
+class Fragment3ViewModel @Inject constructor(
+ private val wizardCache: WizardCache
+) : ViewModel() {
+
+ private val _interests = listOf(
+ "Sport", "Music", "Travel", "Reading", "Movies", "Games", "Cooking", "Art"
+ )
+
+ private val _selectedInterests = MutableLiveData>()
+ val selectedInterests: LiveData> get() = _selectedInterests
+
+ val interests: List get() = _interests
+
+ init {
+ _selectedInterests.value = emptySet()
+ }
+
+ fun toggleInterest(interest: String) {
+ _selectedInterests.value = _selectedInterests.value?.let {
+ if (it.contains(interest)) {
+ it - interest
+ } else {
+ it + interest
+ }
+ }
+ }
+
+ fun saveSelectedInterests() {
+ _selectedInterests.value?.let {
+ wizardCache.setInterests(it)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment4ViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment4ViewModel.kt
new file mode 100644
index 0000000..73946a8
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/Fragment4ViewModel.kt
@@ -0,0 +1,23 @@
+package ru.otus.basicarchitecture.viewmodels
+
+import androidx.lifecycle.ViewModel
+import dagger.hilt.android.lifecycle.HiltViewModel
+import ru.otus.basicarchitecture.repository.WizardCache
+import javax.inject.Inject
+
+@HiltViewModel
+class Fragment4ViewModel @Inject constructor(
+ private val wizardCache: WizardCache
+) : ViewModel() {
+
+ val firstName: String? get() = wizardCache.firstName
+ val lastName: String? get() = wizardCache.lastName
+ val birthDate: String? get() = wizardCache.birthDate
+ val country: String? get() = wizardCache.country
+ val city: String? get() = wizardCache.city
+ val address: String? get() = wizardCache.address
+ val interests: List get() = wizardCache.interests
+
+ val fullAddress: String
+ get() = listOfNotNull(country, city, address).joinToString(" ")
+}
diff --git a/app/src/main/java/ru/otus/basicarchitecture/viewmodels/MainViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/MainViewModel.kt
new file mode 100644
index 0000000..1b62a0c
--- /dev/null
+++ b/app/src/main/java/ru/otus/basicarchitecture/viewmodels/MainViewModel.kt
@@ -0,0 +1,6 @@
+package ru.otus.basicarchitecture.viewmodels
+
+import androidx.lifecycle.ViewModel
+import javax.inject.Inject
+
+class MainViewModel @Inject constructor() : ViewModel()
\ No newline at end of file
diff --git a/app/src/main/res/drawable/selector_tag_background.xml b/app/src/main/res/drawable/selector_tag_background.xml
new file mode 100644
index 0000000..0a7bff2
--- /dev/null
+++ b/app/src/main/res/drawable/selector_tag_background.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/tag_default_background.xml b/app/src/main/res/drawable/tag_default_background.xml
new file mode 100644
index 0000000..2598f24
--- /dev/null
+++ b/app/src/main/res/drawable/tag_default_background.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0b15a20..0510d5b 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -4,6 +4,11 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:context=".MainActivity">
+ tools:context=".ui.MainActivity">
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_fragment1.xml b/app/src/main/res/layout/fragment_fragment1.xml
new file mode 100644
index 0000000..ca0fb18
--- /dev/null
+++ b/app/src/main/res/layout/fragment_fragment1.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_fragment2.xml b/app/src/main/res/layout/fragment_fragment2.xml
new file mode 100644
index 0000000..dc78764
--- /dev/null
+++ b/app/src/main/res/layout/fragment_fragment2.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_fragment3.xml b/app/src/main/res/layout/fragment_fragment3.xml
new file mode 100644
index 0000000..603456c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_fragment3.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_fragment4.xml b/app/src/main/res/layout/fragment_fragment4.xml
new file mode 100644
index 0000000..9c3bfdf
--- /dev/null
+++ b/app/src/main/res/layout/fragment_fragment4.xml
@@ -0,0 +1,120 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/tag_item.xml b/app/src/main/res/layout/tag_item.xml
new file mode 100644
index 0000000..4282488
--- /dev/null
+++ b/app/src/main/res/layout/tag_item.xml
@@ -0,0 +1,12 @@
+
+
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127..f2e6ebe 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,4 +7,6 @@
#FF018786
#FF000000
#FFFFFFFF
+ #FF6200EE
+ #DDDDDD
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index 0ab4563..c333b04 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,6 +1,6 @@
-