From 6d13501fa4ef48c30c7f43a44ebcb3af63f7aeb9 Mon Sep 17 00:00:00 2001 From: Sergei Korotaev Date: Sun, 15 Jun 2025 11:41:25 +0300 Subject: [PATCH 1/4] Initial commit: Task 1. --- app/build.gradle | 18 ++- app/src/main/AndroidManifest.xml | 1 + .../java/ru/otus/basicarchitecture/App.kt | 7 ++ .../ru/otus/basicarchitecture/MainActivity.kt | 10 ++ .../ru/otus/basicarchitecture/data/Person.kt | 15 +++ .../basicarchitecture/data/WizardCache.kt | 35 ++++++ .../ru/otus/basicarchitecture/ui/Chips.kt | 43 ++++++++ .../ui/FragmentBindingDelegate.kt | 49 +++++++++ .../ui/address/AddressFragment.kt | 56 ++++++++++ .../ui/address/AddressViewModel.kt | 44 ++++++++ .../ui/interests/InterestsFragment.kt | 51 +++++++++ .../ui/interests/InterestsViewModel.kt | 20 ++++ .../basicarchitecture/ui/name/NameFragment.kt | 104 ++++++++++++++++++ .../ui/name/NameViewModel.kt | 56 ++++++++++ .../ui/summary/SummaryFragment.kt | 51 +++++++++ .../ui/summary/SummaryViewModel.kt | 13 +++ app/src/main/res/layout/activity_main.xml | 7 ++ app/src/main/res/layout/fragment_address.xml | 77 +++++++++++++ .../main/res/layout/fragment_interests.xml | 28 +++++ app/src/main/res/layout/fragment_name.xml | 87 +++++++++++++++ app/src/main/res/layout/fragment_summary.xml | 87 +++++++++++++++ app/src/main/res/navigation/nav_graph.xml | 61 ++++++++++ app/src/main/res/values/dimens.xml | 6 + app/src/main/res/values/strings.xml | 28 ++++- app/src/main/res/values/styles.xml | 55 +++++++++ build.gradle | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 27 files changed, 1010 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/ru/otus/basicarchitecture/App.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/data/Person.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/data/WizardCache.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/Chips.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/FragmentBindingDelegate.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/name/NameFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/name/NameViewModel.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryFragment.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryViewModel.kt create mode 100644 app/src/main/res/layout/fragment_address.xml create mode 100644 app/src/main/res/layout/fragment_interests.xml create mode 100644 app/src/main/res/layout/fragment_name.xml create mode 100644 app/src/main/res/layout/fragment_summary.xml create mode 100644 app/src/main/res/navigation/nav_graph.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/styles.xml diff --git a/app/build.gradle b/app/build.gradle index e515992..c48a6f8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,8 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'com.google.devtools.ksp' + id 'com.google.dagger.hilt.android' } android { @@ -9,7 +11,7 @@ android { defaultConfig { applicationId "ru.otus.basicarchitecture" - minSdk 24 + minSdk 26 targetSdk 35 versionCode 1 versionName "1.0" @@ -30,14 +32,22 @@ android { kotlinOptions { jvmTarget = '17' } + buildFeatures { + viewBinding = true + } } dependencies { - implementation 'androidx.core:core-ktx:1.15.0' - implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'androidx.core:core-ktx:1.16.0' + implementation 'androidx.appcompat:appcompat:1.7.1' implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.2.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'androidx.navigation:navigation-fragment-ktx:2.9.0' + implementation 'androidx.navigation:navigation-ui-ktx:2.9.0' + implementation 'androidx.databinding:viewbinding:8.10.1' + implementation('com.google.dagger:hilt-android:2.56.2') + ksp('com.google.dagger:hilt-android-compiler:2.56.2') testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea17fa5..733976b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> = interestsMap +} + + +@Module +@InstallIn(ActivityRetainedComponent::class) +object WizardCacheModule { + @Provides + @ActivityRetainedScoped + fun wizardCache(): WizardCache = WizardCache() +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/Chips.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/Chips.kt new file mode 100644 index 0000000..c312200 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/Chips.kt @@ -0,0 +1,43 @@ +package ru.otus.basicarchitecture.ui + +import android.content.Context +import com.google.android.material.chip.Chip +import com.google.android.material.chip.ChipGroup +import com.google.android.material.shape.CornerFamily +import com.google.android.material.shape.ShapeAppearanceModel +import ru.otus.basicarchitecture.R + +object InterestChips { + + private fun getCornerRadius(context: Context): Float = + context.resources.getDimensionPixelSize(R.dimen.chip_corner_radius).toFloat() + + fun load(chipGroup: ChipGroup, tags: Map, style: (Chip.() -> Unit) = { + isClickable = true + }) { + val sam = ShapeAppearanceModel.builder() + .setAllCorners(CornerFamily.ROUNDED, getCornerRadius(chipGroup.context)) + .build() + + tags.forEach { tag -> + val chip = Chip(chipGroup.context).apply { + style() + isCheckable = isClickable + + if (isCheckable) { + isChecked = tag.value + /*setTextColor( + resources.getColor( + com.google.android.material.R.color.design_default_color_primary_variant + ) + )*/ + } + shapeAppearanceModel = sam + text = tag.key + } + + if (chip.isCheckable) chipGroup.addView(chip) + else if (tag.value) chipGroup.addView(chip) + } + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/FragmentBindingDelegate.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/FragmentBindingDelegate.kt new file mode 100644 index 0000000..9553878 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/FragmentBindingDelegate.kt @@ -0,0 +1,49 @@ +package ru.otus.basicarchitecture.ui + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleOwner +import androidx.viewbinding.ViewBinding + +/** + * Binds fragment view-binding + */ +class FragmentBindingDelegate(private val fragment: Fragment) { + + private var binding: VB? = null + + /** + * Binds fragment view-binding + * Put inside `onCreateView` + * See: https://developer.android.com/topic/libraries/view-binding#fragments + * @param container View container + * @param inflate Binding inflater + */ + fun bind( + container: ViewGroup?, + inflate: (LayoutInflater, ViewGroup?, Boolean) -> VB + ): View { + fragment.viewLifecycleOwner.lifecycle.addObserver(BindingDestroyer()) + binding = inflate(fragment.layoutInflater, container, false) + return binding!!.root + } + + /** + * Runs [block] with binding + */ + fun withBinding(block: VB.() -> R): R { + return checkNotNull(binding) { "Binding is not initialized" }.block() + } + + /** + * Destroys binding on view destroy + */ + private inner class BindingDestroyer : DefaultLifecycleObserver { + override fun onDestroy(owner: LifecycleOwner) { + binding = null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressFragment.kt new file mode 100644 index 0000000..cb52309 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressFragment.kt @@ -0,0 +1,56 @@ +package ru.otus.basicarchitecture.ui.address + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.doAfterTextChanged +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.FragmentAddressBinding +import ru.otus.basicarchitecture.ui.FragmentBindingDelegate + +@AndroidEntryPoint +class AddressFragment : Fragment() { + + private val binding = FragmentBindingDelegate(this) + private val viewModel: AddressViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View = binding.bind( + container, + FragmentAddressBinding::inflate + ) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.withBinding { + viewModel.getFromCache() + + viewModel.showNext.observe(viewLifecycleOwner) { + txtFillFields.visibility = if (it) View.INVISIBLE else View.VISIBLE + buttonNext.isEnabled = it + } + + cityInput.setText(viewModel.data.city) + cityInput.doAfterTextChanged { viewModel.setCity(it.toString()) } + + countryInput.setText(viewModel.data.country) + countryInput.doAfterTextChanged { viewModel.setCountry(it.toString()) } + + addressInput.setText(viewModel.data.address) + addressInput.doAfterTextChanged { viewModel.setAddress(it.toString()) } + + buttonNext.setOnClickListener { + viewModel.putToCache() + findNavController().navigate(R.id.action_to_interests) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressViewModel.kt new file mode 100644 index 0000000..0cce4d9 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/address/AddressViewModel.kt @@ -0,0 +1,44 @@ +package ru.otus.basicarchitecture.ui.address + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import ru.otus.basicarchitecture.data.PersonAddress +import ru.otus.basicarchitecture.data.WizardCache +import javax.inject.Inject + +@HiltViewModel +class AddressViewModel @Inject constructor(private val wizardCache: WizardCache): ViewModel() { + + var data = PersonAddress("", "", "") + var showNext = MutableLiveData(false) + + private fun checkData() { + showNext.value = + data.country.isNotEmpty() && data.city.isNotEmpty() && data.address.isNotEmpty() + } + + fun setCountry(country: String) { + data = PersonAddress(country, data.city, data.address) + checkData() + } + + fun setCity(city: String) { + data = PersonAddress(data.country, city, data.address) + checkData() + } + + fun setAddress(address: String) { + data = PersonAddress(data.country, data.city, address) + checkData() + } + + fun getFromCache() { + data = wizardCache.address + checkData() + } + + fun putToCache() { + wizardCache.address = data + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsFragment.kt new file mode 100644 index 0000000..1672a68 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsFragment.kt @@ -0,0 +1,51 @@ +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.ui.InterestChips +import ru.otus.basicarchitecture.databinding.FragmentInterestsBinding +import ru.otus.basicarchitecture.ui.FragmentBindingDelegate + +@AndroidEntryPoint +class InterestsFragment : Fragment() { + + private val binding = FragmentBindingDelegate(this) + private val viewModel: InterestsViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View = binding.bind( + container, + FragmentInterestsBinding::inflate + ) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.withBinding { + viewModel.getFromCache() + + InterestChips.load(interestsChips, viewModel.data) + + buttonNext.setOnClickListener { + val map = mutableMapOf() + interestsChips.children.toList().filterIsInstance().map { + map.put(it.text.toString(), it.isChecked) + } + viewModel.data = map + viewModel.putToCache() + findNavController().navigate(R.id.action_to_summary) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsViewModel.kt new file mode 100644 index 0000000..c4e40e2 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/interests/InterestsViewModel.kt @@ -0,0 +1,20 @@ +package ru.otus.basicarchitecture.ui.interests + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import ru.otus.basicarchitecture.data.WizardCache +import javax.inject.Inject + +@HiltViewModel +class InterestsViewModel @Inject constructor(private val wizardCache: WizardCache): ViewModel() { + + var data: Map = wizardCache.interests + + fun getFromCache() { + data = wizardCache.interests + } + + fun putToCache() { + wizardCache.interests = data + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/name/NameFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/name/NameFragment.kt new file mode 100644 index 0000000..4340c9b --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/name/NameFragment.kt @@ -0,0 +1,104 @@ +package ru.otus.basicarchitecture.ui.name + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.doAfterTextChanged +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import com.google.android.material.datepicker.MaterialDatePicker +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.R +import ru.otus.basicarchitecture.databinding.FragmentNameBinding +import ru.otus.basicarchitecture.ui.FragmentBindingDelegate +import java.time.Instant +import java.time.LocalDate +import java.time.ZoneId +import java.time.format.DateTimeFormatter +import java.util.Calendar + +@AndroidEntryPoint +class NameFragment : Fragment() { + + private val binding = FragmentBindingDelegate(this) + private val viewModel: NameViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View = binding.bind( + container, + FragmentNameBinding::inflate + ) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.withBinding { + viewModel.getFromCache() + + viewModel.showNext.observe(viewLifecycleOwner) { + buttonNext.isEnabled = it + } + + viewModel.showAgeRestricted.observe(viewLifecycleOwner) { + txtRestrictedAccess.visibility = + if (it) View.VISIBLE else View.INVISIBLE + } + + viewModel.showFieldsEmpty.observe(viewLifecycleOwner) { + txtFillFields.visibility = + if (it) View.VISIBLE else View.INVISIBLE + } + + nameInput.setText(viewModel.data.name) + surnameInput.setText(viewModel.data.surName) + dateInput.setText(formatDate(viewModel.data.birthDate)) + + dateInput.setOnClickListener { + pickDate(viewModel.data.birthDate) { newDate -> + dateInput.setText(formatDate(newDate)) + viewModel.setBirthDate(newDate) + } + } + + nameInput.doAfterTextChanged { viewModel.setName(it.toString()) } + surnameInput.doAfterTextChanged { viewModel.setSurName(it.toString()) } + + buttonNext.setOnClickListener { + viewModel.putToCache() + findNavController().navigate(R.id.action_to_address) + } + } + } + + private fun formatDate(date: LocalDate): String = date.format( + DateTimeFormatter.ofPattern(resources.getText(R.string.date_pattern).toString()) + ) + + private fun pickDate(selDate: LocalDate, newDateHandler: (LocalDate) -> Unit ) { + + binding.withBinding { + + val calendar = Calendar.getInstance() + calendar.set(selDate.year,selDate.monthValue - 1,selDate.dayOfMonth) + + val datePicker = MaterialDatePicker.Builder.datePicker() + .setTitleText(resources.getText(R.string.select_birthday)) + .setInputMode(MaterialDatePicker.INPUT_MODE_TEXT) + .setSelection(calendar.timeInMillis) + .build() + + datePicker.addOnPositiveButtonClickListener { selectedDate -> + val newDate = Instant.ofEpochMilli(selectedDate) + .atZone(ZoneId.systemDefault()) + .toLocalDate() + + newDateHandler(newDate) + } + datePicker.show(parentFragmentManager, "DATE_PICKER") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/name/NameViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/name/NameViewModel.kt new file mode 100644 index 0000000..9c7fb82 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/name/NameViewModel.kt @@ -0,0 +1,56 @@ +package ru.otus.basicarchitecture.ui.name + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import ru.otus.basicarchitecture.data.PersonName +import ru.otus.basicarchitecture.data.WizardCache +import java.time.LocalDate +import java.time.Period +import javax.inject.Inject + +@HiltViewModel +class NameViewModel @Inject constructor(private val wizardCache: WizardCache): ViewModel() { + + var data = PersonName("", "", LocalDate.now()) + + var showNext = MutableLiveData(false) + var showFieldsEmpty = MutableLiveData(false) + var showAgeRestricted = MutableLiveData(false) + + private fun checkAge(): Boolean = Period.between(data.birthDate, LocalDate.now()).years >= 18 + private fun checkName(): Boolean = data.name.isNotEmpty() && data.surName.isNotEmpty() + + private fun checkData() { + val ageOk = checkAge() + val nameOk = checkName() + + showNext.value = ageOk && nameOk + showAgeRestricted.value = !ageOk && nameOk + showFieldsEmpty.value = !nameOk + } + + fun setName(name: String) { + data = PersonName(name, data.surName, data.birthDate) + checkData() + } + + fun setSurName(surName: String) { + data = PersonName(data.name, surName, data.birthDate) + checkData() + } + + fun setBirthDate(date: LocalDate) { + data = PersonName(data.name, data.surName, date) + checkData() + } + + fun getFromCache() { + data = wizardCache.name + checkData() + } + + fun putToCache() { + wizardCache.name = data + } +} diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryFragment.kt new file mode 100644 index 0000000..e382b16 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryFragment.kt @@ -0,0 +1,51 @@ +package ru.otus.basicarchitecture.ui.summary + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import dagger.hilt.android.AndroidEntryPoint +import ru.otus.basicarchitecture.R +import ru.otus.basicarchitecture.databinding.FragmentSummaryBinding +import ru.otus.basicarchitecture.ui.FragmentBindingDelegate +import ru.otus.basicarchitecture.ui.InterestChips +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +@AndroidEntryPoint +class SummaryFragment : Fragment() { + + private val binding = FragmentBindingDelegate(this) + private val viewModel: SummaryViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View = binding.bind( + container, + FragmentSummaryBinding::inflate + ) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.withBinding { + name.text = viewModel.name.name + surname.text = viewModel.name.surName + birthDate.text = formatDate(viewModel.name.birthDate) + + address.text = String.format(resources.getString(R.string.address_mask), + viewModel.address.country, + viewModel.address.city, + viewModel.address.address) + + InterestChips.load(interestsChips, viewModel.interests) { isClickable = false } + } + } + + private fun formatDate(date: LocalDate): String = date.format( + DateTimeFormatter.ofPattern(resources.getText(R.string.date_pattern).toString()) + ) +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryViewModel.kt b/app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryViewModel.kt new file mode 100644 index 0000000..0d5bc34 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/ui/summary/SummaryViewModel.kt @@ -0,0 +1,13 @@ +package ru.otus.basicarchitecture.ui.summary + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import ru.otus.basicarchitecture.data.WizardCache +import javax.inject.Inject + +@HiltViewModel +class SummaryViewModel @Inject constructor(private val wizardCache: WizardCache): ViewModel() { + var name = wizardCache.name + var address = wizardCache.address + var interests = wizardCache.interests +} \ 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..58950a1 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,4 +6,11 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_address.xml b/app/src/main/res/layout/fragment_address.xml new file mode 100644 index 0000000..2d074e6 --- /dev/null +++ b/app/src/main/res/layout/fragment_address.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + +