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
30 changes: 26 additions & 4 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 'com.google.dagger.hilt.android'
id 'kotlin-kapt'
}

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

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

buildConfigField("String", "DADATA_API_KEY", "\"${project.DADATA_API_KEY}\"")

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}

Expand All @@ -24,15 +28,33 @@ 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{
buildConfig true
viewBinding true
}
}

dependencies {
implementation 'com.google.dagger:hilt-android:2.52'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.fragment:fragment-ktx:1.5.6'
kapt 'com.google.dagger:hilt-compiler:2.52'

implementation 'com.google.android.flexbox:flexbox:3.0.0'

implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'

implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
Expand Down
13 changes: 11 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<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"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -11,10 +13,17 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BasicArchitecture"
tools:targetApi="31">
tools:targetApi="31"
android:exported="true">
<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>
8 changes: 8 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/App.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.otus.basicarchitecture

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

@HiltAndroidApp
class App :Application(){
}
12 changes: 12 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,22 @@ package ru.otus.basicarchitecture

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.HiltAndroidApp
import ru.otus.basicarchitecture.fragments.PersonFragment
import javax.inject.Inject

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject lateinit var personFragment:PersonFragment
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RubaN0id, не уверен, что инжект фрагмента - это хорошая идея. Нужно помнить, что андроид сохраняет состояние вьюх при изменении конфигурации. В результате, у вас будет два фрагмента - один перевернутый, а второй - созданный даггером. Это можно проверить в онКриейт - посмотреть, нулл или не нулл пришел в функцию. Если не нулл, то запрос fragmentManager.findFragmentByTag вернет ненулевой результат. И это уже и есть ваш фрагмент

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)


supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container_view_tag, personFragment)
.commit()

}
}
18 changes: 18 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/cache/WizardCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package ru.otus.basicarchitecture.cache

import java.time.LocalDate
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class WizardCache @Inject constructor(){

var person:Person?=null
var address:Address?=null
var interests:Interests?=null

}

data class Person (val name: String = "test", val surname: String = "test", val dateOfBirth: String = "01.01.1970")
data class Address(val country: String = "", val city: String = "", val address: String = "")
data class Interests(val selectedInterest: List<String> = emptyList<String>())
16 changes: 16 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/dadata/DadataApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package ru.otus.basicarchitecture.dadata

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

interface DadataApi {
@Headers("Content-Type: application/json", "Accept: application/json")
@POST("suggestions/api/4_1/rs/suggest/address")
suspend fun getAddressSuggestions(@Body request: AddressSuggestionRequest): DadataResponse
}

data class AddressSuggestionRequest(val query: String)

data class DadataResponse(val suggestions: List<AddressSuggestion>)
data class AddressSuggestion(val value: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package ru.otus.basicarchitecture.dadata

import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.otus.basicarchitecture.BuildConfig
import java.util.concurrent.TimeUnit

object DadataApiService {
private const val BASE_URL = "https://suggestions.dadata.ru/"
private const val API_KEY = BuildConfig.DADATA_API_KEY

private val client: OkHttpClient by lazy {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
val headerInterceptor = Interceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Token $API_KEY")
.build()
chain.proceed(request)
}

OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor(headerInterceptor)
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
}

val api: DadataApi by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(DadataApi::class.java)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package ru.otus.basicarchitecture.fragments

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 android.widget.ArrayAdapter
import android.widget.AutoCompleteTextView
import android.widget.Button
import android.widget.Toast
import androidx.core.widget.addTextChangedListener
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R
import ru.otus.basicarchitecture.viewmodel.AddressViewModel

@AndroidEntryPoint
class AddressFragment : Fragment() {

companion object {
fun newInstance() = AddressFragment()
}

private val viewModel: AddressViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_address, container, false)
}

private lateinit var addressEditText: AutoCompleteTextView
private lateinit var nextBtn: Button

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

addressEditText = view.findViewById(R.id.address)
nextBtn = view.findViewById(R.id.button_to_interest)


viewModel.addressSuggestions.observe(viewLifecycleOwner) { suggestions ->
val adapter = ArrayAdapter(
requireContext(),
android.R.layout.simple_dropdown_item_1line,
suggestions
)
addressEditText.setAdapter(adapter)
if (suggestions.isNotEmpty()) {
addressEditText.showDropDown()
}
}

addressEditText.addTextChangedListener {
if(it != null && it.length > 5) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RubaN0id, мне кажется, стоит сделать проверку в модели. Допустим, вы захотите протестировать, что запросы не посылаются до 5 символов - вам придется делать андроид-тесты, что геморно. Потом поменяли вьюху, и прицепили ее к новой модели. И забыли... В общем, мне кажется, такому место в модели

viewModel.updateAddress(it.toString())
viewModel.fetchAddressSuggestions(it.toString())
}
}

nextBtn.setOnClickListener {
if (nextBtn.isEnabled) {
viewModel.saveData()
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container_view_tag, InterestFragment.newInstance())
.addToBackStack(null)
.commit()
} else {
Toast.makeText(context, "Валидация не пройдена", Toast.LENGTH_SHORT).show()
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ru.otus.basicarchitecture.fragments

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 android.widget.Button
import android.widget.TextView
import com.google.android.flexbox.FlexboxLayout
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R
import ru.otus.basicarchitecture.viewmodel.InterestViewModel
import javax.inject.Inject

@AndroidEntryPoint
class InterestFragment @Inject constructor(): Fragment() {

companion object {
fun newInstance() = InterestFragment()
}

private val viewModel: InterestViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

}

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_interest, container, false)
}

private lateinit var tagFlexboxLayout: FlexboxLayout
private lateinit var nextBtn: Button

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

tagFlexboxLayout = view.findViewById(R.id.tagFlexboxLayout)
nextBtn = view.findViewById(R.id.button_to_summary)

viewModel.selectedInterests.observe(viewLifecycleOwner) { selectedInterests ->
updateTags(selectedInterests)
}

nextBtn.setOnClickListener {
viewModel.saveSelectedInterests()
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container_view_tag, SummaryFragment.newInstance())
.addToBackStack(null)
.commit()
}

viewModel.interests.forEach { interest ->
val tagView = createTagView(interest)
tagView.setOnClickListener {
viewModel.toggleInterest(interest)
}
tagFlexboxLayout.addView(tagView)
}
}

private fun createTagView(interest: String): TextView {
val tagView = LayoutInflater.from(context).inflate(R.layout.tag, tagFlexboxLayout, false) as TextView
tagView.text = interest
return tagView
}

private fun updateTags(selectedInterests: List<String>) {
for (i in 0 until tagFlexboxLayout.childCount) {
val tagView = tagFlexboxLayout.getChildAt(i) as TextView
val interest = tagView.text.toString()
tagView.isSelected = selectedInterests.contains(interest)
}
}
}
Loading