Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

firs attempt #7

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
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
1 change: 1 addition & 0 deletions api_keys.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
API_KEY=36aa779fbefaea8dcad67a90e8f68321ab4a683a
68 changes: 60 additions & 8 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'androidx.navigation.safeargs'
id 'org.jetbrains.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 +27,70 @@ 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'
}
kapt {
correctErrorTypes true
}
}

dependencies {
hilt {
enableAggregatingTask = true
}

implementation 'androidx.core:core-ktx:1.8.0'
/*buildscript {
repositories {
google()
}
dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0")
}
}*/

dependencies {
implementation('androidx.compose.material3:material3:1.1.2')
implementation 'androidx.core:core-ktx:1.12.0'
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation project(':net')
implementation project(':domain')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'

implementation 'com.google.dagger:hilt-android:2.50'
kapt 'com.google.dagger:hilt-compiler:2.50'

// Java language implementation
implementation('androidx.navigation:navigation-fragment:2.7.6')
implementation('androidx.navigation:navigation-ui:2.7.6')

// Kotlin
implementation('androidx.navigation:navigation-fragment-ktx:2.7.6')
implementation('androidx.navigation:navigation-ui-ktx:2.7.6')

// Feature module Support
implementation('androidx.navigation:navigation-dynamic-features-fragment:2.7.6')

// Testing Navigation
androidTestImplementation('androidx.navigation:navigation-testing:2.7.6')

// Jetpack Compose Integration
implementation('androidx.navigation:navigation-compose:2.7.6')

// build.gradle
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// build.gradle.kts
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation 'com.google.code.gson:gson:2.10.1'



}
1 change: 1 addition & 0 deletions app/keystore.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
API_KEY=36aa779fbefaea8dcad67a90e8f68321ab4a683a
9 changes: 7 additions & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
Expand All @@ -11,10 +10,16 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BasicArchitecture"
android:name=".DateApp"
tools:targetApi="31">
<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/DateApp.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 DateApp : Application()

9 changes: 9 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,19 @@ package ru.otus.basicarchitecture

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupActionBarWithNavController
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navHostFragment = supportFragmentManager
.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
val navController = navHostFragment.navController

setupActionBarWithNavController(navController)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ru.otus.basicarchitecture.address

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.domain.data.Address
import ru.otus.basicarchitecture.R

class AddressAdapter(private val onClick: (String) -> Unit) : ListAdapter<Address, AddressAdapter.Holder>(AddressDiffCallback) {

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder = Holder(
LayoutInflater.from(parent.context).inflate(R.layout.vh_address, parent, false)
)

override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bind(getItem(position))
}

inner class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private var address: String = ""

init {
itemView.setOnClickListener {
onClick(address)
}
}

fun bind(address: Address) {
this.address = address.address
(itemView as TextView).text = address.address
}
}
}

object AddressDiffCallback : DiffUtil.ItemCallback<Address>() {
override fun areItemsTheSame(oldItem: Address, newItem: Address): Boolean {
return oldItem.address == newItem.address
}

override fun areContentsTheSame(oldItem: Address, newItem: Address): Boolean {
return oldItem == newItem
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ru.otus.basicarchitecture.address

import android.os.Bundle
import android.text.TextWatcher
import android.view.View
import android.widget.ArrayAdapter
import android.widget.Button
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.example.domain.data.Address
import com.google.android.material.textfield.MaterialAutoCompleteTextView
import com.google.android.material.textfield.TextInputEditText
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import ru.otus.basicarchitecture.R

@AndroidEntryPoint
class AddressFragment : Fragment(R.layout.fragment_adress) {
private val addressViewModelInstance: AddressFragmentModel by viewModels()
private lateinit var nextButton: Button
private lateinit var addressField: TextInputEditText
private lateinit var addressHintField: MaterialAutoCompleteTextView
private lateinit var recycler: RecyclerView

private lateinit var textWatcher: TextWatcher

private val recyclerAdapter = AddressAdapter {
addressViewModelInstance.setAddress(it)
}


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

addressField = view.findViewById(R.id.addressTextEdit)
recycler = view.findViewById(R.id.recycler)
addressHintField = view.findViewById(R.id.addressAutoCompleteTextEdit)
nextButton = view.findViewById(R.id.addressNextButton)
nextButton.isEnabled = false

textWatcher = addressField.addTextChangedListener {
addressViewModelInstance.searchAddress(it.toString())
}

nextButton.setOnClickListener {
findNavController().navigate(R.id.action_addressFragment_to_interestsFragment)
}



viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
addressViewModelInstance.viewState.collect { it ->
addressField.removeTextChangedListener(textWatcher)
addressField.setTextKeepState(it.address)
addressField.addTextChangedListener(textWatcher)

recyclerAdapter.submitList(it.addressList)

val adapter = ArrayAdapter<Address>(
requireContext(), android.R.layout.simple_dropdown_item_1line, emptyList()
)
addressHintField.setAdapter(adapter)
addressHintField.addTextChangedListener {
addressViewModelInstance.searchAddress(it.toString())
}

nextButton.isEnabled = it.accessNextButton
}
}
}

recycler.adapter = recyclerAdapter
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package ru.otus.basicarchitecture.address

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.example.domain.DaDataRepository
import com.example.domain.data.Address
import com.example.net.DaDataRepositoryImpl
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import ru.otus.basicarchitecture.wizardCache.RegistrationData
import ru.otus.basicarchitecture.wizardCache.WizardCache
import javax.inject.Inject
import kotlin.time.Duration.Companion.seconds

@HiltViewModel
class AddressFragmentModel @Inject constructor(
private val cache: WizardCache,
private val service: DaDataRepository,
) : ViewModel() {

private val entry = MutableSharedFlow<SuggestCommand>()

@OptIn(FlowPreview::class)
val viewState: StateFlow<AddressFragmentViewState>
get() =
combine(
cache.state,
entry
.debounce {
when (it) {
is SuggestCommand.Search -> 1L.seconds
is SuggestCommand.Clear -> 0L.seconds
}
}
.map {
when (it) {
is SuggestCommand.Search -> service.getAddressSuggestions(it.address)
is SuggestCommand.Clear -> emptyList()
}
}
) { data, addresses ->
render(data, addresses)
}.stateIn(
viewModelScope,
SharingStarted.Eagerly,
render(cache.state.value, emptyList())
)

/**
* Sets address
*/
fun setAddress(address: String) {
cache.setAddress(address)
viewModelScope.launch {
entry.emit(SuggestCommand.Clear)
}
}

/**
* Searches for address
*/
fun searchAddress(address: String) {
setAddress(address)
viewModelScope.launch {
entry.emit(SuggestCommand.Search(address))
}
}

private fun checkedAssessButton(data: RegistrationData): Boolean {
return data.address.length > 2
}

private fun render(data: RegistrationData, addresses: List<Address>) =
AddressFragmentViewState(data.address, addresses, checkedAssessButton(data))
}

/**
* Commands for [AddressViewModel] address suggestions
*/
sealed class SuggestCommand {
data class Search(val address: String) : SuggestCommand()
data object Clear : SuggestCommand()
}

@Module
@InstallIn(ViewModelComponent::class)
interface AddressViewModelModule {
@Binds
@ViewModelScoped
fun service(impl: DaDataRepositoryImpl): DaDataRepository
}
Loading