From f5b4290504572bf42e35bbc3b1d6a9fc9611fae6 Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Mon, 8 Dec 2025 18:25:44 +0300 Subject: [PATCH 01/15] Libs updated --- app/build.gradle | 24 ++++++++++++++++-------- build.gradle | 9 +++++---- gradle/wrapper/gradle-wrapper.properties | 2 +- 3 files changed, 22 insertions(+), 13 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e515992..e23676b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,11 +1,13 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'com.google.devtools.ksp' + id 'com.google.dagger.hilt.android' } android { namespace 'ru.otus.basicarchitecture' - compileSdk 35 + compileSdk 36 defaultConfig { applicationId "ru.otus.basicarchitecture" @@ -30,15 +32,21 @@ android { kotlinOptions { jvmTarget = '17' } + buildFeatures { + viewBinding = true + } } dependencies { - - implementation 'androidx.core:core-ktx:1.15.0' - implementation 'androidx.appcompat:appcompat:1.7.0' - implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.2.0' + implementation 'androidx.core:core-ktx:1.17.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'com.google.android.material:material:1.13.0' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation "com.google.dagger:hilt-android:2.57.2" + implementation "androidx.navigation:navigation-fragment-ktx:2.9.6" + implementation "androidx.navigation:navigation-ui-ktx:2.9.6" + ksp "com.google.dagger:hilt-compiler:2.57.2" testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.2.1' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + androidTestImplementation 'androidx.test.ext:junit:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 7b166ff..6e7a60a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.7.3' apply false - id 'com.android.library' version '8.7.3' apply false - id 'org.jetbrains.kotlin.android' version '2.0.21' apply false + id 'com.android.application' version '8.13.1' apply false + id 'com.android.library' version '8.13.1' apply false + id "com.google.devtools.ksp" version "2.2.21-2.0.4" apply false + id 'org.jetbrains.kotlin.android' version '2.2.21' apply false + id 'com.google.dagger.hilt.android' version '2.57.2' apply false } \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..37f853b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 549f72975aff2e3a88f39dce791967fdcfa84384 Mon Sep 17 00:00:00 2001 From: YURY TILMAN Date: Mon, 8 Dec 2025 18:26:01 +0300 Subject: [PATCH 02/15] Added name fragment --- app/src/main/AndroidManifest.xml | 4 +- .../FragmentBindingDelegate.kt | 49 ++++++++++ .../java/ru/otus/basicarchitecture/MvvmApp.kt | 6 ++ .../ru/otus/basicarchitecture/NameFragment.kt | 82 ++++++++++++++++ app/src/main/res/layout/activity_main.xml | 9 ++ app/src/main/res/layout/fragment_name.xml | 97 +++++++++++++++++++ app/src/main/res/navigation/navigation.xml | 12 +++ app/src/main/res/values/strings.xml | 8 ++ 8 files changed, 266 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/ru/otus/basicarchitecture/FragmentBindingDelegate.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/MvvmApp.kt create mode 100644 app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt create mode 100644 app/src/main/res/layout/fragment_name.xml create mode 100644 app/src/main/res/navigation/navigation.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ea17fa5..9c44062 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -11,7 +11,9 @@ android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.BasicArchitecture" - tools:targetApi="31"> + tools:targetApi="31" + android:name=".MvvmApp" + > diff --git a/app/src/main/java/ru/otus/basicarchitecture/FragmentBindingDelegate.kt b/app/src/main/java/ru/otus/basicarchitecture/FragmentBindingDelegate.kt new file mode 100644 index 0000000..698a556 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/FragmentBindingDelegate.kt @@ -0,0 +1,49 @@ +package ru.otus.basicarchitecture + +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/MvvmApp.kt b/app/src/main/java/ru/otus/basicarchitecture/MvvmApp.kt new file mode 100644 index 0000000..efecee9 --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/MvvmApp.kt @@ -0,0 +1,6 @@ +package ru.otus.basicarchitecture + +import android.app.Application + +class MvvmApp : Application() { +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt b/app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt new file mode 100644 index 0000000..3a2df9d --- /dev/null +++ b/app/src/main/java/ru/otus/basicarchitecture/NameFragment.kt @@ -0,0 +1,82 @@ +package ru.otus.basicarchitecture + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.datepicker.MaterialDatePicker +import ru.otus.basicarchitecture.databinding.FragmentNameBinding +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.TimeZone + +class NameFragment : Fragment() { + + private var _binding: FragmentNameBinding? = null + private val binding get() = _binding!! + + private val dateFormat = SimpleDateFormat("dd/MM/yyyy", Locale.getDefault()).apply { + timeZone = TimeZone.getTimeZone("UTC") + } + + private val dataPickerTitle by lazy { getString(R.string.data_picker_title) } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentNameBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + with(binding) { + val datePickerBuilder = MaterialDatePicker + .Builder + .datePicker() + .setTitleText(dataPickerTitle) + + if (birthday.text.toString().isNotEmpty()) { + try { + dateFormat + .parse(birthday.text.toString()) + ?.let { + datePickerBuilder.setSelection(it.time) + } + } catch (e: Exception) { + e.printStackTrace() + } + } + val datePicker = datePickerBuilder.build() + birthday.setOnFocusChangeListener { v, hasFocus -> + if (hasFocus) { + datePicker.addOnPositiveButtonClickListener { selection: Long -> + birthday.setText(dateFormat.format(Date(selection))) + } + datePicker.addOnNegativeButtonClickListener { + birthday.clearFocus() + } + datePicker.addOnDismissListener { + birthday.clearFocus() + } + datePicker.show(childFragmentManager, "birthday_date") + + } else { + // + } + } + + + } + + + } + + override fun onDestroyView() { + super.onDestroyView() + _binding = null + } +} \ 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..055e357 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -6,4 +6,13 @@ android:layout_height="match_parent" tools:context=".MainActivity"> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_name.xml b/app/src/main/res/layout/fragment_name.xml new file mode 100644 index 0000000..cb10a08 --- /dev/null +++ b/app/src/main/res/layout/fragment_name.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + +