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
42 changes: 41 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'com.google.devtools.ksp'
id 'com.google.dagger.hilt.android'
id 'dagger.hilt.android.plugin'
id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}

android {
namespace 'ru.otus.basicarchitecture'
compileSdk 35
compileSdk 36

defaultConfig {
applicationId "ru.otus.basicarchitecture"
minSdk 24
//noinspection OldTargetApi
targetSdk 35
versionCode 1
versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

// Получаем ключи из local.properties через secrets-gradle-plugin
buildConfigField "String", "DADATA_API_KEY", "\"${project.findProperty("DADATA_API_KEY") ?: ""}\""
buildConfigField "String", "DADATA_SECRET_KEY", "\"${project.findProperty("DADATA_SECRET_KEY") ?: ""}\""
}

buildFeatures {
viewBinding = true
buildConfig = true
}

testOptions {
unitTests.returnDefaultValues = true
}

buildTypes {
Expand All @@ -38,7 +56,29 @@ dependencies {
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
// Lifecycle
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
// Navigation
implementation 'androidx.navigation:navigation-fragment:2.8.5'
implementation 'androidx.navigation:navigation-ui:2.8.5'
// Hilt
implementation "com.google.dagger:hilt-android:2.57.2"
ksp "com.google.dagger:hilt-compiler:2.57.2"
// Retrofit
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
// Корутины
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
// Тестирование
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.8'
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3'
testImplementation 'app.cash.turbine:turbine:1.0.0'
testImplementation 'androidx.arch.core:core-testing:2.2.0'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
}
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">

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

<application
android:name=".MyApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
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
7 changes: 7 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/MyApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.otus.basicarchitecture

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

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

import dagger.hilt.android.scopes.ActivityRetainedScoped
import javax.inject.Inject

/**
* Кеш для хранения данных мастера регистрации
* Использует ActivityRetainedScoped - данные сохраняются при повороте экрана,
* но очищаются при уничтожении Activity
*/
@ActivityRetainedScoped
class WizardCache @Inject constructor() {
var firstName: String = ""
var lastName: String = ""
var birthDate: String = ""
var address: String = ""
var interests: List<String> = emptyList()
}
62 changes: 62 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/di/NetworkModule.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package ru.otus.basicarchitecture.di

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.otus.basicarchitecture.BuildConfig
import ru.otus.basicarchitecture.network.dadata.DadataApi
import ru.otus.basicarchitecture.network.dadata.DadataAuthInterceptor
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {

// Базовый URL для API Дадата
private const val DADATA_BASE_URL = "https://suggestions.dadata.ru/"

@Provides
@Singleton
fun provideDadataAuthInterceptor(): DadataAuthInterceptor {
// Получаем ключи из BuildConfig, которые были сгенерированы из local.properties
return DadataAuthInterceptor(
apiKey = BuildConfig.DADATA_API_KEY,
secretKey = BuildConfig.DADATA_SECRET_KEY
)
}

@Provides
@Singleton
fun provideOkHttpClient(authInterceptor: DadataAuthInterceptor): OkHttpClient {
val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
return OkHttpClient.Builder()
.addInterceptor(authInterceptor) // Добавляем интерцептор аутентификации перед логированием
.addInterceptor(loggingInterceptor)
.build()
}

@Provides
@Singleton
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(DADATA_BASE_URL)
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}

// API интерфейс для работы с Дадата
@Provides
@Singleton
fun provideDadataApi(retrofit: Retrofit): DadataApi {
return retrofit.create(DadataApi::class.java)
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package ru.otus.basicarchitecture.network.dadata

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

/**
* Интерфейс для работы с API Дадата
* Используется для получения подсказок адресов по введенному тексту
*/
interface DadataApi {
@POST("suggestions/api/4_1/rs/suggest/address")
suspend fun getAddressSuggestions(@Body request: DadataRequest): DadataResponse
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ru.otus.basicarchitecture.network.dadata

import okhttp3.Interceptor
import okhttp3.Response

/**
* Интерцептор для добавления заголовков аутентификации к запросам API Дадата
* Добавляет Authorization и X-Secret заголовки
*/
class DadataAuthInterceptor(
private val apiKey: String,
private val secretKey: String
) : Interceptor {

override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()

// Добавляем заголовки аутентификации
val authenticatedRequest = originalRequest.newBuilder()
.header("Authorization", "Token $apiKey")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("X-Secret", secretKey)
.build()

return chain.proceed(authenticatedRequest)
}
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ru.otus.basicarchitecture.network.dadata

// Модель запроса к API Дадата
data class DadataRequest(
val query: String,
val count: Int = 10
)

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.otus.basicarchitecture.network.dadata

// Модель ответа от API Дадата
data class DadataResponse(
val suggestions: List<Suggestion>
)

// Модель подсказки адреса
data class Suggestion(
val value: String,
val unrestricted_value: String
)

Loading