Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dc57e5d
add fragments
SpiritualAdviser Jan 26, 2025
2a7cac7
add navigation
SpiritualAdviser Jan 26, 2025
4fed97f
add start dest
SpiritualAdviser Jan 26, 2025
31fefd3
add buttons on fragments
SpiritualAdviser Jan 26, 2025
34e2468
add buttons on fragments
SpiritualAdviser Jan 26, 2025
68cb4b3
add hobby
SpiritualAdviser Feb 15, 2025
ad0232d
add personProfile xml
SpiritualAdviser Feb 15, 2025
8fbadf6
add hilt
SpiritualAdviser Feb 15, 2025
f8bca7e
add WizardCache
SpiritualAdviser Feb 15, 2025
507a6a1
fix WizardCache
SpiritualAdviser Feb 15, 2025
9d05dd8
add WizardCache to ViewModels
SpiritualAdviser Feb 15, 2025
625a009
save bugs
SpiritualAdviser Feb 15, 2025
3f9d746
fix hilt bugs
SpiritualAdviser Feb 15, 2025
3aadd8e
add Hobby
SpiritualAdviser Feb 15, 2025
3de121f
add ageVerification
SpiritualAdviser Feb 15, 2025
8933150
Merge branch 'master' into master
SpiritualAdviser Feb 15, 2025
97a6bed
one Address
SpiritualAdviser Mar 2, 2025
421ac90
one Address on profile
SpiritualAdviser Mar 2, 2025
9e150b0
add getDataNotice
SpiritualAdviser Mar 2, 2025
5e14274
Authorization error
SpiritualAdviser Mar 2, 2025
41f8c06
fix Authorization error
SpiritualAdviser Mar 2, 2025
8bd9b21
fix AddressData
SpiritualAdviser Mar 2, 2025
769cdff
add autoCompleteTextView
SpiritualAdviser Mar 2, 2025
0254932
add AgeVerificationTest
SpiritualAdviser Mar 7, 2025
4b105d8
add AddressViewModelTest init
SpiritualAdviser Mar 7, 2025
8b11046
add AddressViewModelTest
SpiritualAdviser Mar 7, 2025
3dbdd4b
fix AddressViewModelTest
SpiritualAdviser Mar 7, 2025
e6acadb
the task3 is done
SpiritualAdviser Mar 7, 2025
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
37 changes: 34 additions & 3 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'
id 'com.google.dagger.hilt.android'
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}

android {
Expand All @@ -24,11 +27,16 @@ android {
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
jvmTarget = '17'
jvmTarget = '1.8'
}

buildFeatures {
buildConfig = true
}
}

Expand All @@ -38,7 +46,30 @@ dependencies {
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.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.7'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7'
implementation 'androidx.fragment:fragment-ktx:1.8.5'
implementation 'androidx.navigation:navigation-fragment-ktx:2.8.5'
implementation 'androidx.navigation:navigation-ui-ktx:2.8.5'
implementation 'com.squareup.okhttp3:okhttp:4.10.0'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'

testImplementation 'org.mockito:mockito-core:4.0.0'
androidTestImplementation 'org.mockito:mockito-android:3.10.0'

implementation "com.google.dagger:hilt-android:2.55"
kapt "com.google.dagger:hilt-compiler:2.55"

androidTestImplementation "com.squareup.okhttp3:mockwebserver:4.6.0"

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
}

kapt {
correctErrorTypes true
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package ru.otus.basicarchitecture.ui.address

import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.After
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.anyString
import org.mockito.junit.MockitoJUnitRunner
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.otus.basicarchitecture.ui.data.AddressData
import ru.otus.basicarchitecture.ui.data.AddressIP
import ru.otus.basicarchitecture.ui.data.RetrofitClient
import ru.otus.basicarchitecture.ui.data.WizardCache

@RunWith(MockitoJUnitRunner::class)
class AddressViewModelTest {
private lateinit var server: MockWebServer
private lateinit var viewModel: AddressViewModel
private lateinit var retrofitClient: RetrofitClient

@Before
fun set() {
server = MockWebServer()
val wizardCache = WizardCache()
val addressData = AddressData()
retrofitClient = RetrofitClient()

retrofitClient.apiService = Retrofit.Builder()
.baseUrl(server.url("/"))
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(AddressIP::class.java)

viewModel = AddressViewModel(wizardCache, addressData, retrofitClient)
}

@Test
fun getDataNotice() {
runBlocking {

val response = MockResponse().setResponseCode(200).setBody(
"""
{
"suggestions": [
{
"value": "г Москва, ул Хабаровская",
},
]
}

""".trimIndent()
)
server.enqueue(response)
viewModel.getDataNotice(anyString())

val result = viewModel.addressDataArray.isEmpty()
val exceptedValue = true
assertEquals(exceptedValue, result)
}
}

@Test
fun setAddress() {

viewModel.setAddress("Москва")

val result = viewModel.wizardCache.personAddress
val exceptedValue = "Москва"
assertEquals(exceptedValue, result)
}

@After
fun shutdown() {
server.shutdown()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.otus.basicarchitecture.ui.authorization

import androidx.test.ext.junit.runners.AndroidJUnit4
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import ru.otus.basicarchitecture.ui.data.WizardCache

@RunWith(AndroidJUnit4::class)
class AuthorizationViewModelTest {

private lateinit var viewModel: AuthorizationViewModel

@Before
fun set() {
val wizardCache = WizardCache()
viewModel = AuthorizationViewModel(wizardCache)
viewModel.setCurrentDate(1, 1, 2018)
}

@Test
fun ageVerification() {

runBlocking {

withContext(Dispatchers.Main) {
val day = 1
val month = 1
val year = 2000

viewModel.ageVerification(day, month, year)
val result = viewModel.anAdult.value

val exceptedValue = true
assertEquals(exceptedValue, result)
}
}
}
}
6 changes: 5 additions & 1 deletion 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=".ui.MyHilt"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand All @@ -11,12 +14,13 @@
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.BasicArchitecture"
tools:targetApi="31">
tools:targetApi="34">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
Expand Down
2 changes: 2 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,7 +2,9 @@ package ru.otus.basicarchitecture

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/ui/MyHilt.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ru.otus.basicarchitecture.ui

import android.app.Application
import dagger.hilt.android.HiltAndroidApp
import ru.otus.basicarchitecture.ui.data.WizardCache
import javax.inject.Inject

@HiltAndroidApp
class MyHilt : Application() {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package ru.otus.basicarchitecture.ui.address

import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
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 androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import com.google.android.material.textfield.TextInputLayout
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.R


@AndroidEntryPoint
class AddressFragment : Fragment() {

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

private val viewModel: AddressViewModel by viewModels()

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

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

val addressTextInput = view.findViewById<TextInputLayout>(R.id.addressTextInput)
val navController = findNavController()

view.findViewById<AutoCompleteTextView>(R.id.autoCompleteTextView).apply {
threshold = 4
addTextChangedListener(object :
TextWatcher {
override fun onTextChanged(
s: CharSequence,
start: Int,
before: Int,
count: Int
) {

if (s.length > 3) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

То есть, насколько я понял, у вас список предложений, который сейчас в модели находится, устанавливается в список предложений? Мне кажется, это предложения от прошлого изменения строки поиска (см ниже)

Copy link
Author

Choose a reason for hiding this comment

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

Тут, возможно и прошлое цепанулось гляну по возможности. Тоже не совсем понял вопроса)

Copy link
Author

Choose a reason for hiding this comment

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

Тут логика такая, при вводе больше 3х букв в поле мы кидаем запрос серверу при ответе мы результат пишем в вью модель, наверное смутило то что я использую доп переменную во вью модели для этого, можно и сразу в адаптер написать. После 4х букв мы начинаем показывать подсказки.

viewModel.getDataNotice(s.toString())
val adapter = activity?.let {
ArrayAdapter(
it.baseContext,
android.R.layout.simple_dropdown_item_1line,
viewModel.addressDataArray
)
}
setAdapter(adapter)
}
}

override fun beforeTextChanged(
s: CharSequence,
start: Int,
count: Int,
after: Int
) {
}

override fun afterTextChanged(s: Editable) {}
});
}

view.findViewById<Button>(R.id.toInterests).setOnClickListener {
viewModel.setAddress(addressTextInput.editText?.text.toString())

navController.navigate(R.id.action_to_interestsFragment)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package ru.otus.basicarchitecture.ui.address

import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import ru.otus.basicarchitecture.ui.data.AddressData
import ru.otus.basicarchitecture.ui.data.AddressIP
import ru.otus.basicarchitecture.ui.data.RetrofitClient
import ru.otus.basicarchitecture.ui.data.WizardCache
import javax.inject.Inject

@HiltViewModel
class AddressViewModel @Inject constructor(
var wizardCache: WizardCache,
private var addressData: AddressData,
private var retrofitClient:RetrofitClient

) : ViewModel() {

var addressDataArray: MutableList<String> = mutableListOf()
private var postDataJob: Job? = null

fun setAddress(address: String) {
wizardCache.personAddress = address
}

fun getDataNotice(address: String) {
addressData.query = address
postDataJob?.cancel()

postDataJob = viewModelScope.launch(Dispatchers.IO) {

try {
val result = retrofitClient.apiService.postData(
dataModal = addressData
)
if (result.isSuccessful) {
result.body()?.let {
it.suggestions.forEach { address ->
addressDataArray.add(address.value)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Вы пишите найденные значения в список. А как UI узнает, что список поменялся?

Copy link
Author

Choose a reason for hiding this comment

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

На сколько я понял, адаптер принимает готовый список строк, потому я просто использую во вью модели список адресов чисто для адаптера. Возможно адаптер может и цеплять лайвдату какую ни будь и менять на лету. Но у меня он не обновлял подсказки, я пофиксил таким образом. Если так, задам вопрос на разборе.

Copy link
Author

@SpiritualAdviser SpiritualAdviser Mar 3, 2025

Choose a reason for hiding this comment

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

В момент когда компонент начнёт показывать подсказки, список уже будет получен от сервера. И так далее по шагам при добавлении по одной букве. Если не будет ответа от сервера ничего страшного, мы либо не покажем подсказку или через 2-3 буквы при успешном запросе показываем подсказки. Для такой логики лайвдаты или флоу не нужны. Да и с адаптером у меня не получилось подружить их.

}
}
}

} catch (e: Exception) {
Log.d("my", e.toString())
}
}
}
}
Loading