diff --git a/app/build.gradle b/app/build.gradle index 9c99d98..845a887 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,16 +1,32 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' + id 'dagger.hilt.android.plugin' } + + +kotlin { + jvmToolchain { + (System.getenv("JAVA_HOME") ?: "/path/to/jdk11") as File + } +} + +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) + + +def dDataTokin = properties.getProperty('D_DATA_TOKIN', '') + android { namespace 'ru.otus.basicarchitecture' - compileSdk 33 + compileSdk 34 defaultConfig { applicationId "ru.otus.basicarchitecture" - minSdk 24 - targetSdk 33 + minSdk 28 + targetSdk 34 versionCode 1 versionName "1.0" @@ -19,9 +35,14 @@ android { buildTypes { release { + buildConfigField 'String', 'D_DATA_TOKIN', "\"${dDataTokin}\"" minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + debug { + buildConfigField 'String', 'D_DATA_TOKIN', "\"${dDataTokin}\"" + } + } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -30,15 +51,40 @@ android { kotlinOptions { jvmTarget = '1.8' } + + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions.jvmTarget = '1.8' + } + + buildFeatures { + viewBinding true + buildConfig = true + } + } dependencies { - - implementation 'androidx.core:core-ktx:1.8.0' + implementation 'androidx.core:core-ktx:1.10.1' 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.navigation:navigation-fragment-ktx:2.7.0' + testImplementation 'org.mockito:mockito-inline:3.8.0' + testImplementation "androidx.arch.core:core-testing:2.1.0" 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.dagger:hilt-android:2.47' + kapt 'com.google.dagger:hilt-compiler:2.47' + kapt("org.jetbrains.kotlinx:kotlinx-metadata-jvm:0.5.0") + implementation 'com.google.android.flexbox:flexbox:3.0.0' + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' + implementation 'androidx.databinding:databinding-runtime:8.1.0' + implementation "androidx.navigation:navigation-fragment-ktx:2.7.0" + implementation "androidx.navigation:navigation-ui-ktx:2.7.0" + implementation 'com.squareup.retrofit2:retrofit:2.9.0' + implementation 'com.squareup.retrofit2:converter-gson:2.9.0' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e81fea..e974a3d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,9 +1,11 @@ + + android:exported="true"> + + + + + + \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/CustomArrayAdapter.kt b/app/src/main/java/ru/otus/basicarchitecture/CustomArrayAdapter.kt new file mode 100644 index 0000000..36b760a --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/CustomArrayAdapter.kt @@ -0,0 +1,55 @@ +package ru.otus.basicarchitecture + +import android.content.Context +import android.widget.ArrayAdapter +import android.widget.Filter + +class CustomArrayAdapter( + context: Context, + private val items: List +) : ArrayAdapter(context, android.R.layout.simple_dropdown_item_1line, items) { + + override fun getFilter(): Filter { + return object : Filter() { + override fun performFiltering(constraint: CharSequence?): FilterResults { + val results = FilterResults() + + if (constraint.isNullOrEmpty()) { + results.values = items + results.count = items.size + } else { + val searchText = constraint.toString().lowercase().cleanForSearch() + val matchedItems = items.filter { + it.lowercase().cleanForSearch().contains(searchText) + } + results.values = matchedItems + results.count = matchedItems.size + } + + return results + } + + private fun String.cleanForSearch(): String { + return this.replace("г ", "") + .replace(", ", " ") + .replace(" пер", "") + .replace(" ул", "") + .replace(" д", "") + .replace(" б-р", "") + .replace(" р-н", "") + } + + override fun publishResults(constraint: CharSequence?, results: FilterResults?) { + if (results != null && results.count > 0) { + notifyDataSetChanged() + } else { + notifyDataSetInvalidated() + } + } + + override fun convertResultToString(resultValue: Any?): CharSequence { + return resultValue as? String ?: "" + } + } + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/DaDataRequest.kt b/app/src/main/java/ru/otus/basicarchitecture/DaDataRequest.kt new file mode 100644 index 0000000..3b49bb1 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/DaDataRequest.kt @@ -0,0 +1,5 @@ +package ru.otus.basicarchitecture + +data class DaDataRequest( + val query: String, +) diff --git a/app/src/main/java/ru/otus/basicarchitecture/DaDataService.kt b/app/src/main/java/ru/otus/basicarchitecture/DaDataService.kt new file mode 100644 index 0000000..a0398b8 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/DaDataService.kt @@ -0,0 +1,31 @@ +package ru.otus.basicarchitecture + +import okhttp3.MediaType +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.Body +import retrofit2.http.POST + + +interface DaDataService { + @POST("suggestions/api/4_1/rs/suggest/address") + suspend fun getSuggestions(@Body request: DaDataRequest): Response + +} + +class FakeDaDataService : DaDataService { + var shouldReturnError = false + + override suspend fun getSuggestions(request: DaDataRequest): Response { + return if (shouldReturnError) { + Response.error(400, "Bad Request".toResponseBody()) + } else { + val fakeSuggestions = listOf(Suggestion("1","2",Data("1","2","3","4","5"))) + val fakeResponse = SuggestionsResponse(fakeSuggestions) + Response.success(fakeResponse) + } + } + + fun String.toResponseBody(): ResponseBody = + ResponseBody.create(MediaType.parse("text/plain"), this) +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt b/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt index 623aba9..2876204 100644 --- a/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt +++ b/app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt @@ -1,11 +1,28 @@ package ru.otus.basicarchitecture -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.setupActionBarWithNavController +import dagger.hilt.android.AndroidEntryPoint +@AndroidEntryPoint class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) + + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val navController = navHostFragment.navController + setupActionBarWithNavController(navController) + } + + override fun onSupportNavigateUp(): Boolean { + val navHostFragment = + supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment + val navController = navHostFragment.navController + return navController.navigateUp() || super.onSupportNavigateUp() } } \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/MyApplication.kt b/app/src/main/java/ru/otus/basicarchitecture/MyApplication.kt new file mode 100644 index 0000000..e078a8e --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/MyApplication.kt @@ -0,0 +1,7 @@ +package ru.otus.basicarchitecture + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApplication : Application() \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/NetworkModule.kt b/app/src/main/java/ru/otus/basicarchitecture/NetworkModule.kt new file mode 100644 index 0000000..02c1d1a --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/NetworkModule.kt @@ -0,0 +1,50 @@ +package ru.otus.basicarchitecture + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import javax.inject.Singleton + + +private const val BASE_URL = "https://suggestions.dadata.ru/" +private const val D_DATA_TOKIN = BuildConfig.D_DATA_TOKIN + +@Module +@InstallIn(SingletonComponent::class) +object NetworkModule { + + @Provides + @Singleton + fun provideOkHttpClient(): OkHttpClient { + return OkHttpClient.Builder() + .addInterceptor { chain -> + val request = chain.request().newBuilder() + .addHeader("Content-Type", "application/json") + .addHeader("Accept", "application/json") + .addHeader("Authorization", "Token $D_DATA_TOKIN") + .build() + chain.proceed(request) + } + .build() + } + + @Provides + @Singleton + fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit { + return Retrofit.Builder() + .baseUrl(BASE_URL) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build() + } + + @Provides + @Singleton + fun provideDaDataService(retrofit: Retrofit): DaDataService { + return retrofit.create(DaDataService::class.java) + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/Suggestion.kt b/app/src/main/java/ru/otus/basicarchitecture/Suggestion.kt new file mode 100644 index 0000000..0895e08 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/Suggestion.kt @@ -0,0 +1,23 @@ +package ru.otus.basicarchitecture + +import com.google.gson.annotations.SerializedName + +data class Suggestion( + val value: String, + @SerializedName("unrestricted_value") + val unrestrictedValue: String, + val data: Data, +) + +data class Data( + @SerializedName("region_kladr_id") + val regionKladrId: String, + @SerializedName("city_kladr_id") + val cityKladrId: String, + @SerializedName("kladr_id") + val kladrId: String, + @SerializedName("geo_lat") + val geoLat: String, + @SerializedName("geo_lon") + val geoLon: String, +) diff --git a/app/src/main/java/ru/otus/basicarchitecture/SuggestionsResponse.kt b/app/src/main/java/ru/otus/basicarchitecture/SuggestionsResponse.kt new file mode 100644 index 0000000..699f09b --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/SuggestionsResponse.kt @@ -0,0 +1,5 @@ +package ru.otus.basicarchitecture + +data class SuggestionsResponse( + val suggestions: List +) diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputAddressViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputAddressViewModel.kt new file mode 100644 index 0000000..4f836e0 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputAddressViewModel.kt @@ -0,0 +1,79 @@ +package ru.otus.basicarchitecture + +import android.util.Log +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class UserInputAddressViewModel @Inject constructor( + private val wizardCache: WizardCache, + private val daDataService: DaDataService +) : ViewModel() { + val validateState = MutableLiveData() + val viewState: MutableLiveData = MutableLiveData(ViewState()) + val suggestions = MutableLiveData>() + + private val viewModelJob = Job() + private val viewModelScope = CoroutineScope(Dispatchers.Main + viewModelJob) + + fun search(query: String) { + viewModelScope.launch { + try { + val response = daDataService.getSuggestions(DaDataRequest(query)) + if (response.isSuccessful) { + val data = response.body() + suggestions.value = data?.suggestions + Log.d("log----------------", "onResponse: ${data?.suggestions}") + } else { + Log.d("log----------------", response.toString()) + } + } catch (e: Exception) { + e.printStackTrace() + Log.d("log----------------", "Exception: $e") + } + } + } + + override fun onCleared() { + super.onCleared() + viewModelJob.cancel() + } + + + + fun validateAndSaveAddress(): ValidateState { + val stats = viewState.value!! + val checkFiles = isValidFields( + listOf( + listOf(stats.address, "Адрес") + )) + + val res = if (checkFiles.isNotEmpty()) { + ValidateState.LoseFiled(checkFiles) + } + else { + wizardCache.address = stats.address + ValidateState.Ok + } + validateState.value = res + + return res + + + } + + + private fun isValidFields(fields: List>): String { + for (field in fields) + if (field[0].isEmpty()) { + return field[1] + } + return "" + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragment.kt new file mode 100644 index 0000000..72ca1c3 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragment.kt @@ -0,0 +1,86 @@ +package ru.otus.basicarchitecture + +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.Toast +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.databinding.FragmentUserInputBinding + +@AndroidEntryPoint +class UserInputFragment : Fragment(R.layout.fragment_user_input) { + private val viewModel: UserInputViewModel by viewModels() + private var _binding: FragmentUserInputBinding? = null + + + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentUserInputBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) +// + binding.dateOfBirthEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + binding.nextButton.isEnabled = true + if (start != s.length && (s.length == 2 || s.length == 5)) { + binding.dateOfBirthEditText.setText("${s}.") + binding.dateOfBirthEditText.setSelection(s.length + 1) + } + } + + override fun afterTextChanged(s: Editable) { + viewModel.viewState.value = viewModel.viewState.value!!.copy(dateOfBirth = binding.dateOfBirthEditText.text.toString()) + } + }) + + binding.firstNameEditText.addTextChangedListener { text -> + viewModel.viewState.value = viewModel.viewState.value!!.copy(firstName = text.toString()) + } + binding.lastNameEditText.addTextChangedListener { text -> + viewModel.viewState.value = viewModel.viewState.value!!.copy(lastName = text.toString()) + } + + + binding.nextButton.setOnClickListener { + viewModel.validateAndSaveFirst() + val result = viewModel.validateState.value + var outText = "" + when (result) { + is ValidateState.BedFiled -> outText = "Некорректное поле " + result.filed + is ValidateState.LoseFiled -> outText = "Пустое поле " + result.filed + ValidateState.Not18 -> { + outText = "Нет 18 лет" + it.isEnabled = false + } + ValidateState.Ok -> { + findNavController().navigate(R.id.action_userInputFragment_to_userInputFragment2) + } + null -> outText = "Ошибка" + } + if (outText.isNotEmpty()) + Toast.makeText(context, outText, Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentAddress.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentAddress.kt new file mode 100644 index 0000000..682601f --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentAddress.kt @@ -0,0 +1,64 @@ +package ru.otus.basicarchitecture + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.databinding.FragmentUserInput2Binding + +@AndroidEntryPoint +class UserInputFragmentAddress : Fragment(R.layout.fragment_user_input2) { + private val viewModel: UserInputAddressViewModel by viewModels() + private var _binding: FragmentUserInput2Binding? = null + + + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentUserInput2Binding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + viewModel.suggestions.observe(viewLifecycleOwner) { suggestions -> + val adapter = CustomArrayAdapter(requireContext(), suggestions.map { it.value }) + binding.myAutoCompleteTextView.setAdapter(adapter) + } + + binding.myAutoCompleteTextView.addTextChangedListener { text -> + viewModel.search(text.toString()) + viewModel.viewState.value = viewModel.viewState.value!!.copy(address = text.toString()) + } + + binding.nextButton2.setOnClickListener { + viewModel.validateAndSaveAddress() + val result = viewModel.validateState.value + var outText = "" + when (result) { + is ValidateState.LoseFiled -> outText = "Пустое поле " + result.filed + ValidateState.Ok -> { + findNavController().navigate(R.id.action_userInputFragment2_to_userInputFragment3) + } + else -> outText = "Ошибка" + } + if (outText.isNotEmpty()) + Toast.makeText(context, outText, Toast.LENGTH_SHORT).show() + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentLast.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentLast.kt new file mode 100644 index 0000000..a0f0d33 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentLast.kt @@ -0,0 +1,65 @@ +package ru.otus.basicarchitecture + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.google.android.flexbox.FlexboxLayout +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.databinding.FragmentUserInput4Binding +import java.text.SimpleDateFormat +import java.util.Locale + +@AndroidEntryPoint +class UserInputFragmentLast : Fragment(R.layout.fragment_user_input4) { + private val viewModel: UserInputLastViewModel by viewModels() + private var _binding: FragmentUserInput4Binding? = null + + + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentUserInput4Binding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.firstNameTextView.text = viewModel.wizardCache.firstName + binding.lastNameTextView.text = viewModel.wizardCache.lastName + val format = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()) + binding.dateOfBirthTextView.text = viewModel.wizardCache.dateOfBirth?.let { format.format(it) } + binding.addressTextView.text = viewModel.wizardCache.address + + val flexboxLayout = binding.tagContainer + val tags = viewModel.wizardCache.selectedTags + tags.forEach { tag -> + val textView = TextView(context).apply { + text = tag + textSize = 32f + setTextColor(Color.WHITE) + setPadding(30, 18, 30, 18) + setBackgroundResource(R.drawable.tag_background2) + } + val lp = FlexboxLayout.LayoutParams( + FlexboxLayout.LayoutParams.WRAP_CONTENT, + FlexboxLayout.LayoutParams.WRAP_CONTENT + ) + lp.setMargins(48, 48, 48, 48) + flexboxLayout.addView(textView, lp) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentTags.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentTags.kt new file mode 100644 index 0000000..896d12d --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentTags.kt @@ -0,0 +1,77 @@ +package ru.otus.basicarchitecture + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.google.android.flexbox.FlexboxLayout +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.databinding.FragmentUserInput3Binding + +@AndroidEntryPoint +class UserInputFragmentTags : Fragment(R.layout.fragment_user_input3) { + private val viewModel: UserInputTagsViewModel by viewModels() + private var _binding: FragmentUserInput3Binding? = null + + + private val binding get() = _binding!! + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentUserInput3Binding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val flexboxLayout = binding.tagContainer + val tags = listOf("Android", "Kotlin", "Fragment", "Tag", "Cloud") + tags.forEach { tag -> + val textView = TextView(context).apply { + text = tag + textSize = 32f + setTextColor(Color.WHITE) + setPadding(30, 18, 30, 18) + setBackgroundResource(R.drawable.tag_background) + isSelected = tag in viewModel.wizardCache.selectedTags + setOnClickListener { + it.isSelected = !it.isSelected + if (it.isSelected) { + val selectedTags = viewModel.viewState.value!!.selectedTags + selectedTags.add(tag) + viewModel.viewState.value = viewModel.viewState.value!!.copy(selectedTags = selectedTags) + } else { + val selectedTags = viewModel.viewState.value!!.selectedTags + selectedTags.remove(tag) + viewModel.viewState.value = viewModel.viewState.value!!.copy(selectedTags = selectedTags) + } + } + } + val lp = FlexboxLayout.LayoutParams( + FlexboxLayout.LayoutParams.WRAP_CONTENT, + FlexboxLayout.LayoutParams.WRAP_CONTENT + ) + lp.setMargins(48, 48, 48, 48) + flexboxLayout.addView(textView, lp) + } + + + binding.nextButton3.setOnClickListener { + viewModel.saveTags() + findNavController().navigate(R.id.action_userInputFragment3_to_userInputFragment4) + } + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputLastViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputLastViewModel.kt new file mode 100644 index 0000000..fb6778a --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputLastViewModel.kt @@ -0,0 +1,10 @@ +package ru.otus.basicarchitecture + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class UserInputLastViewModel @Inject constructor( + val wizardCache: WizardCache +) : ViewModel() \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputTagsViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputTagsViewModel.kt new file mode 100644 index 0000000..889d1b9 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputTagsViewModel.kt @@ -0,0 +1,17 @@ +package ru.otus.basicarchitecture + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class UserInputTagsViewModel @Inject constructor( + val wizardCache: WizardCache +) : ViewModel() { + val viewState: MutableLiveData = MutableLiveData(ViewState()) + + fun saveTags() { + wizardCache.selectedTags = viewState.value!!.selectedTags + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/UserInputViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputViewModel.kt new file mode 100644 index 0000000..6fdeab2 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputViewModel.kt @@ -0,0 +1,78 @@ +package ru.otus.basicarchitecture + +import android.icu.util.Calendar +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import javax.inject.Inject + +@HiltViewModel +class UserInputViewModel @Inject constructor( + private val wizardCache: WizardCache +) : ViewModel() { + val validateState = MutableLiveData() + val viewState: MutableLiveData = MutableLiveData(ViewState()) + + fun validateAndSaveFirst() { + validateState.value = isValidFirst(viewState.value!!.dateOfBirth, + listOf(listOf(viewState.value!!.firstName, "Имя"), listOf(viewState.value!!.lastName, "Фамилия")) + ) + } + fun isValidFirst(dateOfBirth: String, fields: List>): ValidateState { + val stats = viewState.value!! + val checkFiles = isValidFields(fields) + if (checkFiles.isNotEmpty()) + return ValidateState.LoseFiled(checkFiles) + + if (dateOfBirth.isEmpty()) { + return ValidateState.LoseFiled("Дата рождения") + } + val format = SimpleDateFormat("dd.MM.yyyy", Locale.US) + try { + val date = format.parse(dateOfBirth) +// val calendar = Calendar.getInstance() +// calendar.time = date +// val year = calendar.get(Calendar.YEAR) +// val month = calendar.get(Calendar.MONTH) + 1 +// val day = calendar.get(Calendar.DAY_OF_MONTH) +// +// val currentYear = Calendar.getInstance().get(Calendar.YEAR) +// +// +// if (!(day in 1..31 && month in 1..12 && year in 1900..currentYear)) { +// return ValidateState.BedFiled("Дата рождения") +// } + +// val cal = Calendar.getInstance() +// cal.time = date +// cal.add(Calendar.YEAR, 18) +// return if (cal.time.before(Date())){ + return if (2023 - dateOfBirth.split(".")[2].toInt() > 18){ + wizardCache.firstName = stats.firstName + wizardCache.lastName = stats.lastName + wizardCache.dateOfBirth = date + ValidateState.Ok + } + else + ValidateState.Not18 + + } catch (e: ParseException) { + e.printStackTrace() + } + + return ValidateState.BedFiled("Дата рождения") + } + + private fun isValidFields(fields: List>): String { + for (field in fields) + if (field[0].isEmpty()) { + return field[1] + } + return "" + } + +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ValidateState.kt b/app/src/main/java/ru/otus/basicarchitecture/ValidateState.kt new file mode 100644 index 0000000..6e5a7b4 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ValidateState.kt @@ -0,0 +1,8 @@ +package ru.otus.basicarchitecture + +sealed class ValidateState { + object Ok : ValidateState() + object Not18 : ValidateState() + data class BedFiled(val filed: String) : ValidateState() + data class LoseFiled(val filed: String) : ValidateState() +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ViewState.kt b/app/src/main/java/ru/otus/basicarchitecture/ViewState.kt new file mode 100644 index 0000000..ea0981b --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ViewState.kt @@ -0,0 +1,9 @@ +package ru.otus.basicarchitecture + +data class ViewState ( + val firstName: String = "", + val lastName: String = "", + val dateOfBirth: String = "", + val address: String = "", + val selectedTags: MutableList = mutableListOf() +) \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt b/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt new file mode 100644 index 0000000..f686279 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt @@ -0,0 +1,26 @@ +package ru.otus.basicarchitecture + +import dagger.hilt.android.scopes.ActivityRetainedScoped +import java.util.Date +import javax.inject.Inject + +@ActivityRetainedScoped +open class WizardCache @Inject constructor() { + open var firstName: String? = null + open var lastName: String? = null + open var dateOfBirth: Date? = null + open var country: String? = null + open var city: String? = null + open var address: String? = null + open var selectedTags: MutableList = mutableListOf() +} + +class FakeWizardCache : WizardCache() { + override var firstName: String? = null + override var lastName: String? = null + override var dateOfBirth: Date? = null + override var country: String? = null + override var city: String? = null + override var address: String? = null + override var selectedTags: MutableList = mutableListOf() +} \ No newline at end of file diff --git a/app/src/main/res/drawable/tag_background.xml b/app/src/main/res/drawable/tag_background.xml new file mode 100644 index 0000000..4ecb1f2 --- /dev/null +++ b/app/src/main/res/drawable/tag_background.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/tag_background2.xml b/app/src/main/res/drawable/tag_background2.xml new file mode 100644 index 0000000..6e4a4d8 --- /dev/null +++ b/app/src/main/res/drawable/tag_background2.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0b15a20..b0b89ea 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,9 +1,17 @@ - + android:layout_height="match_parent"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_user_input.xml b/app/src/main/res/layout/fragment_user_input.xml new file mode 100644 index 0000000..723f935 --- /dev/null +++ b/app/src/main/res/layout/fragment_user_input.xml @@ -0,0 +1,38 @@ + + + + + + + + + +