Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.kapt'
id 'com.google.dagger.hilt.android'
}

android {
Expand All @@ -9,7 +11,7 @@ android {

defaultConfig {
applicationId "ru.otus.basicarchitecture"
minSdk 24
minSdk 26
targetSdk 33
versionCode 1
versionName "1.0"
Expand All @@ -24,11 +26,14 @@ 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'
}
buildFeatures {
viewBinding true
}
}

Expand All @@ -38,7 +43,15 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

implementation deps.fragment
implementation deps.navigation
implementation deps.viewmodel
implementation deps.livedata

// implementation deps.dagger
// kapt deps.dagger_compiler

implementation deps.hilt
kapt deps.hilt_compiler
}
11 changes: 9 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,17 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BasicArchitecture"
tools:targetApi="31">
tools:targetApi="31"
android:name=".di.HiltWizardApp">
<activity
android:name=".MainActivity"
android:exported="false" />
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
5 changes: 4 additions & 1 deletion app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package ru.otus.basicarchitecture

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/di/HiltCacheModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ru.otus.basicarchitecture.di

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.scopes.ActivityRetainedScoped
import ru.otus.basicarchitecture.wizardcache.WizardCache
import ru.otus.basicarchitecture.wizardcache.WizardCacheImpl


@Module
@InstallIn(ActivityRetainedComponent::class)
interface HiltCacheModule {

@Binds
@ActivityRetainedScoped
fun bindsCache(impl: WizardCacheImpl): WizardCache
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.otus.basicarchitecture.di

import android.app.Application
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class HiltWizardApp : Application()
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ru.otus.basicarchitecture.ui.address

import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R
import ru.otus.basicarchitecture.databinding.UserDataLayoutBinding
import ru.otus.basicarchitecture.util.WizardTextWatcher

@AndroidEntryPoint
class AddressDataFragment : Fragment() {

private var _binding: UserDataLayoutBinding? = null
private val binding get() = _binding!!

private val viewModel: AddressFragViewModel by viewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = UserDataLayoutBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

binding.apply {
nameOrCountryEditText.hint = getString(R.string.country)
surnameOrCityEditText.hint = getString(R.string.city)
ageOrAddressEditText.hint = getString(R.string.address)
ageOrAddressEditText.inputType = InputType.TYPE_CLASS_TEXT

WizardTextWatcher(nameOrCountryEditText).startListen { setButtonState() }
WizardTextWatcher(surnameOrCityEditText).startListen { setButtonState() }
WizardTextWatcher(ageOrAddressEditText).startListen { setButtonState() }

nextButton.setOnClickListener {
viewModel.onNextButtonClick(nextButton.isSelected)
}
}

viewModel.getCurrentData()

viewModel.state.observe(viewLifecycleOwner) { state ->
handleState(state)
}
viewModel.event.observe(viewLifecycleOwner) { event ->
handleEvent(event)
}
}

private fun handleState(state: AddressFragState) {
binding.apply {
nameOrCountryEditText.setText(state.country)
surnameOrCityEditText.setText(state.city)
ageOrAddressEditText.setText(state.address)
nextButton.isSelected = state.isButtonEnabled
}
}

private fun handleEvent(event: AddressFragEvent) {
when (event) {
is AddressFragEvent.Error -> {
showToast(event.message)
}
is AddressFragEvent.Success -> {
viewModel.updateAddressData(
binding.nameOrCountryEditText.text.toString(),
binding.surnameOrCityEditText.text.toString(),
binding.ageOrAddressEditText.text.toString(),
)
findNavController().navigate(R.id.action_addressDataFragment_to_interestsDataFragment)
}
is AddressFragEvent.Empty -> {}
}
}

private fun setButtonState() {
binding.apply {
nextButton.isSelected = ageOrAddressEditText.text.isNotEmpty()
&& nameOrCountryEditText.text.isNotEmpty()
&& surnameOrCityEditText.text.isNotEmpty()
}
}

private fun showToast(msg: String) {
Toast.makeText(this.context, msg, Toast.LENGTH_SHORT).show()
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.otus.basicarchitecture.ui.address

sealed interface AddressFragEvent {
object Empty : AddressFragEvent
data class Error(val message: String) : AddressFragEvent
object Success : AddressFragEvent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.otus.basicarchitecture.ui.address

data class AddressFragState(
val country: String = "",
val city: String = "",
val address: String = "",
val isButtonEnabled: Boolean = false
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ru.otus.basicarchitecture.ui.address

import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import ru.otus.basicarchitecture.wizardcache.WizardCache
import javax.inject.Inject

@HiltViewModel
class AddressFragViewModel @Inject constructor(
private val cache: WizardCache
) : ViewModel() {

private val _state = MutableLiveData(AddressFragState())
val state: LiveData<AddressFragState> get() = _state

private val _event = MutableLiveData<AddressFragEvent>()
val event: LiveData<AddressFragEvent> get() = _event

init {
getCurrentData()
}

fun getCurrentData() {
cache.getUserData().also { data ->
_state.value = _state.value?.copy(
country = data.country,
city = data.city,
address = data.address,
isButtonEnabled = data.country.isNotEmpty()
&& data.city.isNotEmpty()
&& data.address.isNotEmpty()
)
}
}

fun updateAddressData(country: String, city: String, address: String) {
cache.updateAddress(country, city, address)
}

fun onNextButtonClick(isButtonEnabled: Boolean) {
if (isButtonEnabled) {
_event.value = AddressFragEvent.Success
onEventHandled()
} else {
_event.value = AddressFragEvent.Error("Enter country, city, address")
onEventHandled()
}
}

private fun onEventHandled() {
_event.value = AddressFragEvent.Empty
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ru.otus.basicarchitecture.ui.interests

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R
import ru.otus.basicarchitecture.databinding.UserInterestsLayoutBinding

@AndroidEntryPoint
class InterestsDataFragment : Fragment() {

private var _binding: UserInterestsLayoutBinding? = null
private val binding get() = _binding!!

private val viewModel: InterestsFragViewModel by viewModels()

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = UserInterestsLayoutBinding.inflate(inflater, container, false)
return binding.root
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)

initChipGroup(viewModel.getListOfInterests())

viewModel.state.observe(viewLifecycleOwner) { state ->
handleState(state)
}

binding.goToSummaryButton.setOnClickListener {
viewModel.onSummaryButtonClick()
findNavController().navigate(R.id.action_interestsDataFragment_to_summaryFragment)
}
}

private fun handleState(state: InterestsFragState) {
setCheckedInterests(state.checkedInterests)
}

private fun setCheckedInterests(checkedInterests: List<String>) {
binding.chipGroup.children.forEach {
it as Chip
if (checkedInterests.contains(it.text)) {
it.isChecked = true
}
}
}
private fun initChipGroup(listOfInterests: List<String>) {
listOfInterests.forEach { interest ->
val chip = Chip(this.context)
chip.text = interest

chip.setOnClickListener {
if (chip.isChecked) {
viewModel.addInterest(chip.text.toString())
} else {
viewModel.removeInterest(chip.text.toString())
}
}
binding.chipGroup.addView(chip)
}
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.basicarchitecture.ui.interests

data class InterestsFragState(
val checkedInterests: List<String> = emptyList()
)
Loading