From b06d1c6991ac58f99a42efe4ef36a1d3c4aab9bf Mon Sep 17 00:00:00 2001 From: LeonZhuk Date: Fri, 7 Jul 2023 23:46:48 +0300 Subject: [PATCH 1/8] DZ --- .../ru/otus/basicarchitecture/WizardCache.kt | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt 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..ad88156 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/WizardCache.kt @@ -0,0 +1,16 @@ +package ru.otus.basicarchitecture + +import java.util.Date +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WizardCache @Inject constructor() { + var firstName: String? = null + var lastName: String? = null + var dateOfBirth: Date? = null + var country: String? = null + var city: String? = null + var address: String? = null + var selectedTags: MutableList = mutableListOf() +} \ No newline at end of file From 0f8ef3dd1dd2a0d0d5567397c5a8d76260e0665f Mon Sep 17 00:00:00 2001 From: LeonZhuk Date: Fri, 7 Jul 2023 23:47:47 +0300 Subject: [PATCH 2/8] DZ --- app/build.gradle | 34 +++++- app/src/main/AndroidManifest.xml | 9 +- .../ru/otus/basicarchitecture/MainActivity.kt | 19 ++- .../otus/basicarchitecture/MyApplication.java | 9 ++ .../basicarchitecture/UserInputFragment.kt | 86 ++++++++++++++ .../UserInputFragmentAddress.kt | 65 ++++++++++ .../UserInputFragmentLast.kt | 80 +++++++++++++ .../UserInputFragmentTags.kt | 72 +++++++++++ .../basicarchitecture/UserInputViewModel.kt | 112 ++++++++++++++++++ .../otus/basicarchitecture/ValidateState.kt | 8 ++ app/src/main/res/drawable/tag_background.xml | 15 +++ app/src/main/res/drawable/tag_background2.xml | 15 +++ app/src/main/res/layout/activity_main.xml | 14 ++- .../main/res/layout/fragment_user_input.xml | 38 ++++++ .../main/res/layout/fragment_user_input2.xml | 37 ++++++ .../main/res/layout/fragment_user_input3.xml | 31 +++++ .../main/res/layout/fragment_user_input4.xml | 76 ++++++++++++ app/src/main/res/navigation/nav_graph.xml | 46 +++++++ app/src/main/res/values/strings.xml | 18 +++ build.gradle | 5 +- 20 files changed, 780 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/ru/otus/basicarchitecture/MyApplication.java create mode 100644 app/src/main/java/ru/otus/basicarchitecture/UserInputFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentAddress.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentLast.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentTags.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/UserInputViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ValidateState.kt create mode 100644 app/src/main/res/drawable/tag_background.xml create mode 100644 app/src/main/res/drawable/tag_background2.xml create mode 100644 app/src/main/res/layout/fragment_user_input.xml create mode 100644 app/src/main/res/layout/fragment_user_input2.xml create mode 100644 app/src/main/res/layout/fragment_user_input3.xml create mode 100644 app/src/main/res/layout/fragment_user_input4.xml create mode 100644 app/src/main/res/navigation/nav_graph.xml diff --git a/app/build.gradle b/app/build.gradle index 9c99d98..2effdd1 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,16 @@ 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 + } } android { @@ -30,15 +40,35 @@ android { kotlinOptions { jvmTarget = '1.8' } + + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions.jvmTarget = '1.8' + } + + buildFeatures { + viewBinding true + } + } dependencies { - - implementation 'androidx.core:core-ktx:1.8.0' + def nav_version = "2.6.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:2.6.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.46.1' + kapt 'com.google.dagger:hilt-compiler:2.46.1' + 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.0.2' + implementation "androidx.navigation:navigation-fragment-ktx:$nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$nav_version" } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1e81fea..e69d4db 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,6 +4,7 @@ + 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 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.java b/app/src/main/java/ru/otus/basicarchitecture/MyApplication.java new file mode 100644 index 0000000..2ee755c --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/MyApplication.java @@ -0,0 +1,9 @@ +package ru.otus.basicarchitecture; + +import android.app.Application; + +import dagger.hilt.android.HiltAndroidApp; + +@HiltAndroidApp +public class MyApplication extends Application { +} 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..cff6766 --- /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.dateOfBirth.value = binding.dateOfBirthEditText.text.toString() + } + }) + + binding.firstNameEditText.addTextChangedListener { text -> + viewModel.firstName.value = text.toString() + } + binding.lastNameEditText.addTextChangedListener { text -> + viewModel.lastName.value = text.toString() + } + + binding.nextButton.setOnClickListener { + viewModel.validateAndSaveFirst() + val result = viewModel._state.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 = "Ошибка" + } + 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..8b07d37 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentAddress.kt @@ -0,0 +1,65 @@ +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: UserInputViewModel 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) + + binding.countryEditText.addTextChangedListener { text -> + viewModel.country.value = text.toString() + } + binding.cityEditText.addTextChangedListener { text -> + viewModel.city.value = text.toString() + } + binding.addressEditText.addTextChangedListener { text -> + viewModel.address.value = text.toString() + } + + binding.nextButton2.setOnClickListener { + viewModel.validateAndSaveAddress() + val result = viewModel._state.value + var outText = ""; + when (result) { + is ValidateState.BedFiled -> {} + is ValidateState.LoseFiled -> outText = "Пустое поле " + result.filed + ValidateState.Not18 -> {} + ValidateState.Ok -> { + findNavController().navigate(R.id.action_userInputFragment2_to_userInputFragment3) + } + null -> outText = "Ошибка" + } + 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..0f0c295 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentLast.kt @@ -0,0 +1,80 @@ +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: UserInputViewModel 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) + val wizardCache = viewModel.getWizardCache() + + binding.firstNameTextView.text = wizardCache.firstName + binding.lastNameTextView.text = wizardCache.lastName + val format = SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()) + binding.dateOfBirthTextView.text = wizardCache.dateOfBirth?.let { format.format(it) } + val addressString = getString( + R.string.address_format, + wizardCache.country, + wizardCache.city, + wizardCache.address + ) + binding.addressTextView.text = addressString + + val flexboxLayout = binding.tagContainer + val tags = 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) + setOnClickListener { + it.isSelected = !it.isSelected + if (it.isSelected) { + viewModel.selectedTags.add(tag) + } else { + viewModel.selectedTags.remove(tag) + } + } + } + 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..907737e --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputFragmentTags.kt @@ -0,0 +1,72 @@ +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: UserInputViewModel 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) + setOnClickListener { + it.isSelected = !it.isSelected + if (it.isSelected) { + viewModel.selectedTags.add(tag) + } else { + viewModel.selectedTags.remove(tag) + } + } + } + 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/UserInputViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/UserInputViewModel.kt new file mode 100644 index 0000000..ea76cc9 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/UserInputViewModel.kt @@ -0,0 +1,112 @@ +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 _state = MutableLiveData() + val firstName = MutableLiveData() + val lastName = MutableLiveData() + val dateOfBirth = MutableLiveData() + val country = MutableLiveData() + val city = MutableLiveData() + val address = MutableLiveData() + val selectedTags = mutableListOf() + + fun validateAndSaveFirst() { + _state.value = isValidFirst(dateOfBirth.value?:"", + listOf(listOf(firstName.value?:"", "Имя"), listOf(lastName.value?:"", "Фамилия")) + ) + } + + fun getWizardCache(): WizardCache { + return wizardCache + } + + fun validateAndSaveAddress() { + val checkFiles = isValidFields( + listOf( + listOf(country.value?:"", "Строна"), + listOf(city.value?:"", "Город"), + listOf(address.value?:"", "Адрес") + )) + + _state.value = if (checkFiles.isNotEmpty()) + ValidateState.LoseFiled(checkFiles) + else { + wizardCache.country = country.value + wizardCache.city = city.value + wizardCache.address = address.value + ValidateState.Ok + } + + } + + + private fun isValidFirst(dateOfBirth: String, fields: List>): ValidateState { + 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())){ + wizardCache.firstName = firstName.value + wizardCache.lastName = lastName.value + 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 "" + } + + fun saveTags() { + wizardCache.selectedTags = selectedTags + } +} \ 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/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 @@ + + + + + + + + + +