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
44 changes: 34 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
}

android {
namespace 'ru.otus.basicarchitecture'
compileSdk 33
compileSdk 34

defaultConfig {
applicationId "ru.otus.basicarchitecture"
minSdk 24
targetSdk 33
targetSdk 34
versionCode 1
versionName "1.0"

Expand All @@ -24,21 +26,43 @@ 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"
}
viewBinding {
enabled = true
}
}

kapt {
correctErrorTypes true
}

dependencies {

implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.core:core-ktx:1.13.1'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation 'androidx.annotation:annotation:1.8.2'
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.4"

implementation "com.squareup.retrofit2:retrofit:2.11.0"
implementation "com.squareup.retrofit2:converter-gson:2.11.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1"

implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.fragment:fragment-ktx:1.8.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'

//Hilt
implementation "com.google.dagger:hilt-android:2.52"
kapt "com.google.dagger:hilt-compiler:2.52"
}
14 changes: 12 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />

<application
android:name=".app.MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -13,8 +16,15 @@
android:theme="@style/Theme.BasicArchitecture"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="false" />
android:name=".presentation.MainActivity"
android:exported="true" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />

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

</activity>
</application>

</manifest>
11 changes: 0 additions & 11 deletions app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.otus.basicarchitecture.app

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

@HiltAndroidApp
class MyApplication : Application() {}
20 changes: 20 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/network/DaDataApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.otus.basicarchitecture.network

import retrofit2.http.Body
import retrofit2.http.Headers
import retrofit2.http.POST

interface DaDataApi {
@Headers(
"Content-Type: application/json",
"Authorization: Token 1f17bb43e8c5a22a491f517c8cf95a70704bb456"
)
@POST("suggest/address")
suspend fun suggestAddress(@Body request: SuggestRequest): SuggestResponse
}

data class SuggestRequest(val query: String)

data class SuggestResponse(val suggestions: List<Suggestion>)

data class Suggestion(val value: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package ru.otus.basicarchitecture.network

import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory

object RetrofitClient {
private const val BASE_URL = "https://suggestions.dadata.ru/suggestions/api/4_1/rs/"

val instance: DaDataApi by lazy {
val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()

retrofit.create(DaDataApi::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.otus.basicarchitecture.presentation

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R
import ru.otus.basicarchitecture.presentation.personalInfoFragment.PersonalInfoFragment

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

if (savedInstanceState == null) {
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, PersonalInfoFragment())
.commit()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package ru.otus.basicarchitecture.presentation.addressFragment

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import androidx.core.widget.addTextChangedListener
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.FragmentAddressBinding
import ru.otus.basicarchitecture.presentation.interestsFragment.InterestsFragment

@AndroidEntryPoint
class AddressFragment : Fragment() {

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

private val viewModel: AddressViewModel by viewModels()

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

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

val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, emptyList<String>())
binding.autoCompleteTextViewAddress.setAdapter(adapter)

binding.autoCompleteTextViewAddress.addTextChangedListener { text ->
val query = text.toString()
if (query.length < 3) {
viewModel.fetchAddressSuggestions(query) { suggestions ->
adapter.clear()
adapter.addAll(suggestions)
adapter.notifyDataSetChanged()
}
}
}

binding.buttonNext.setOnClickListener {
viewModel.address = binding.autoCompleteTextViewAddress.text.toString()
if (viewModel.saveData()) {
navigateToNextFragment()
}
}
}

private fun navigateToNextFragment() {
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, InterestsFragment())
.addToBackStack(null)
.commit()
}

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

import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.*
import retrofit2.HttpException
import ru.otus.basicarchitecture.network.RetrofitClient
import ru.otus.basicarchitecture.network.SuggestRequest
import ru.otus.basicarchitecture.repository.WizardCache
import javax.inject.Inject

@HiltViewModel
class AddressViewModel @Inject constructor(
private val wizardCache: WizardCache,
) : ViewModel() {

// Получаем экземпляр DaDataApi через RetrofitClient
private val daDataApi = RetrofitClient.instance

private val coroutineScope = CoroutineScope(Dispatchers.Main + Job())

var address: String? = null
var suggestions: List<String> = emptyList()

fun fetchAddressSuggestions(query: String, onResult: (List<String>) -> Unit) {
coroutineScope.launch {
try {
val response = withContext(Dispatchers.IO) {
daDataApi.suggestAddress(SuggestRequest(query))
}
suggestions = response.suggestions.map { it.value }
onResult(suggestions)
} catch (e: HttpException) {
onResult(emptyList())
} catch (e: Exception) {
onResult(emptyList())
}
}
}

fun saveData(): Boolean {
wizardCache.address = address
return true
}

override fun onCleared() {
super.onCleared()
coroutineScope.cancel() // Отменяем все запущенные корутины при уничтожении ViewModel
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package ru.otus.basicarchitecture.presentation.interestsFragment

import androidx.fragment.app.viewModels
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.google.android.material.chip.Chip
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R
import ru.otus.basicarchitecture.databinding.FragmentInterestsBinding
import ru.otus.basicarchitecture.presentation.summaryFragment.SummaryFragment

@AndroidEntryPoint
class InterestsFragment : Fragment() {

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

private val viewModel: InterestsViewModel by viewModels()

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

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

private fun setupTagCloud() {
val interests = viewModel.getInterests()
interests.forEach { interest ->
val chip = Chip(requireContext()).apply {
text = interest
isCheckable = true
isChecked = viewModel.selectedInterests.contains(interest)
setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
viewModel.selectedInterests.add(interest)
} else {
viewModel.selectedInterests.remove(interest)
}
}
}
binding.chipGroupInterests.addView(chip)
}
}

private fun setupListeners() {
binding.buttonNext.setOnClickListener {
if (viewModel.saveData()) {
navigateToNextFragment()
}
}
}

private fun navigateToNextFragment() {
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, SummaryFragment())
.addToBackStack(null)
.commit()
}

override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Loading