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
47 changes: 37 additions & 10 deletions app/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id "com.android.application"
id "org.jetbrains.kotlin.android"
id("androidx.navigation.safeargs.kotlin") version("2.9.0") apply(false)
id("com.google.devtools.ksp")
id("kotlin-kapt")
id("com.google.dagger.hilt.android")
id("kotlinx-serialization")
id("com.google.android.libraries.mapsplatform.secrets-gradle-plugin")
}

android {
Expand All @@ -9,7 +15,7 @@ android {

defaultConfig {
applicationId "ru.otus.basicarchitecture"
minSdk 24
minSdk 26
targetSdk 35
versionCode 1
versionName "1.0"
Expand All @@ -30,15 +36,36 @@ android {
kotlinOptions {
jvmTarget = '17'
}

buildFeatures {
viewBinding = true
buildConfig = true
}
}

dependencies {

implementation 'androidx.core:core-ktx:1.15.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation "androidx.core:core-ktx:1.16.0"
implementation "androidx.appcompat:appcompat:1.7.1"
implementation "com.google.android.material:material:1.12.0"
implementation "androidx.constraintlayout:constraintlayout:2.2.1"

implementation("androidx.navigation:navigation-fragment-ktx:2.9.0")
implementation("androidx.navigation:navigation-ui-ktx:2.9.0")

implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")

implementation("com.google.dagger:hilt-android:2.56.2")
ksp("com.google.dagger:hilt-android-compiler:2.56.2")

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

implementation("com.squareup.okhttp3:okhttp:4.12.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
implementation("com.squareup.retrofit2:retrofit:2.11.0")
implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0")

}
3 changes: 3 additions & 0 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=".WizardApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
16 changes: 15 additions & 1 deletion app/src/main/java/ru/otus/basicarchitecture/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,24 @@ package ru.otus.basicarchitecture

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.activity.addCallback
import androidx.navigation.findNavController
import dagger.hilt.android.AndroidEntryPoint
import ru.otus.basicarchitecture.databinding.ActivityMainBinding

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
onBackPressedDispatcher.addCallback(this) {
if (!findNavController(R.id.main).popBackStack()) {
finish()
}
}
}
}
13 changes: 13 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/SessionManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package ru.otus.basicarchitecture

import javax.inject.Inject

interface SessionManager {
fun getToken(): String

class Impl @Inject constructor() : SessionManager {
override fun getToken(): String {
return BuildConfig.apiKey
}
}
}
21 changes: 21 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/WizardApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package ru.otus.basicarchitecture

import android.app.Application
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.android.HiltAndroidApp
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton

@HiltAndroidApp
class WizardApplication : Application() {
}

@Module
@InstallIn(SingletonComponent::class)
abstract class AppModule {
@Binds
@Singleton
abstract fun sessionManager(impl: SessionManager.Impl): SessionManager
}
66 changes: 66 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,66 @@
package ru.otus.basicarchitecture

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityRetainedComponent
import dagger.hilt.android.scopes.ActivityRetainedScoped
import java.time.LocalDate

data class WizardUser(
val name: String,
val lastname: String,
val birthday: LocalDate
)

data class WizardAddress(
val country: String,
val city: String,
val address: String,
val value: String = "",
val house: String = "",
val street: String = ""
)

fun WizardAddress.toText(): String {
return "${if (country != "") "$country, " else ""}${if (city != "") "$city, " else ""}$address"
}

@Module
@InstallIn(ActivityRetainedComponent::class)
object WizardModule {
@Provides
@ActivityRetainedScoped
fun wizardCache(): WizardCache = WizardCache()
}

class WizardCache {
private var user: WizardUser = WizardUser("", "", LocalDate.now())
private var address: WizardAddress = WizardAddress("", "", "")

private var hobbies: Set<String> = emptySet()

fun setNewUser(newUser: WizardUser) {
user = newUser
}

fun setNewAddress(newAddress: WizardAddress) {
address = newAddress
}

fun setHobbies(newHobbies: Set<String>) {
hobbies = newHobbies
}

fun getUser(): WizardUser {
return user
}

fun getAddress(): WizardAddress {
return address
}

fun getHobbies(): Set<String> {
return hobbies
}
}
38 changes: 38 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/data/MockData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package ru.otus.basicarchitecture.data

class MockData {
val hobbies = setOf(
"Reading",
"Writing",
"Painting",
"Drawing",
"Photography",
"Playing musical instruments",
"Singing",
"Dancing",
"Hiking",
"Cycling",
"Running",
"Swimming",
"Yoga",
"Meditation",
"Gardening",
"Cooking",
"Baking",
"Knitting",
"Chess",
"Video gaming",
"Board games",
"Pottery",
"Woodworking",
"Fishing",
"Bird watching",
"Astronomy",
"Traveling",
"Language learning",
"Collecting stamps",
"Geocaching"
)

val searchResult = "{\"suggestions\":[{\"value\":\"Респ Бурятия, Тункинский р-н, улус Охор-Шибирь, ул Ленина, д 17\",\"unrestricted_value\":\"Респ Бурятия, Тункинский р-н, улус Охор-Шибирь, ул Ленина, д 17\",\"data\":{\"postal_code\":null,\"country\":\"Россия\",\"country_iso_code\":\"RU\",\"federal_district\":\"Дальневосточный\",\"region_fias_id\":\"a84ebed3-153d-4ba9-8532-8bdf879e1f5a\",\"region_kladr_id\":\"0300000000000\",\"region_iso_code\":\"RU-BU\",\"region_with_type\":\"Респ Бурятия\",\"region_type\":\"Респ\",\"region_type_full\":\"республика\",\"region\":\"Бурятия\",\"area_fias_id\":\"d455c68f-329d-45c0-b2e9-f87b8b807ef8\",\"area_kladr_id\":\"0302000000000\",\"area_with_type\":\"Тункинский р-н\",\"area_type\":\"р-н\",\"area_type_full\":\"район\",\"area\":\"Тункинский\",\"city_fias_id\":null,\"city_kladr_id\":null,\"city_with_type\":null,\"city_type\":null,\"city_type_full\":null,\"city\":null,\"city_area\":null,\"city_district_fias_id\":null,\"city_district_kladr_id\":null,\"city_district_with_type\":null,\"city_district_type\":null,\"city_district_type_full\":null,\"city_district\":null,\"settlement_fias_id\":\"c96a2089-3f3b-40ff-a8b5-a9dfa498cedf\",\"settlement_kladr_id\":\"0302000001600\",\"settlement_with_type\":\"улус Охор-Шибирь\",\"settlement_type\":\"у\",\"settlement_type_full\":\"улус\",\"settlement\":\"Охор-Шибирь\",\"street_fias_id\":\"2788547b-af79-4931-accd-2dac483551eb\",\"street_kladr_id\":\"03020000016000100\",\"street_with_type\":\"ул Ленина\",\"street_type\":\"ул\",\"street_type_full\":\"улица\",\"street\":\"Ленина\",\"stead_fias_id\":null,\"stead_cadnum\":null,\"stead_type\":null,\"stead_type_full\":null,\"stead\":null,\"house_fias_id\":null,\"house_kladr_id\":null,\"house_cadnum\":null,\"house_flat_count\":null,\"house_type\":\"д\",\"house_type_full\":\"дом\",\"house\":\"17\",\"block_type\":null,\"block_type_full\":null,\"block\":null,\"entrance\":null,\"floor\":null,\"flat_fias_id\":null,\"flat_cadnum\":null,\"flat_type\":null,\"flat_type_full\":null,\"flat\":null,\"flat_area\":null,\"square_meter_price\":null,\"flat_price\":null,\"room_fias_id\":null,\"room_cadnum\":null,\"room_type\":null,\"room_type_full\":null,\"room\":null,\"postal_box\":null,\"fias_id\":\"2788547b-af79-4931-accd-2dac483551eb\",\"fias_code\":null,\"fias_level\":\"7\",\"fias_actuality_state\":\"0\",\"kladr_id\":\"03020000016000100\",\"geoname_id\":null,\"capital_marker\":\"0\",\"okato\":\"81251811002\",\"oktmo\":\"81651411111\",\"tax_office\":\"0300\",\"tax_office_legal\":\"0300\",\"timezone\":null,\"geo_lat\":\"51.675343\",\"geo_lon\":\"102.417482\",\"beltway_hit\":null,\"beltway_distance\":null,\"metro\":null,\"divisions\":null,\"qc_geo\":\"3\",\"qc_complete\":null,\"qc_house\":null,\"history_values\":null,\"unparsed_parts\":null,\"source\":null,\"qc\":null}}]}"
}
35 changes: 35 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/helpers/ChipLoader.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package ru.otus.basicarchitecture.helpers

import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import ru.otus.basicarchitecture.data.MockData

object ChipLoader {
private val defaultHobbies = MockData().hobbies

fun loadChipInto(
chipGroup: ChipGroup, tags: Set<String> = defaultHobbies,
style: (Chip.() -> Unit) = {
isClickable = true
isCheckable = true
},
onChipClicked: ((Chip) -> Unit)? = null,
checkedChips: Set<String> = emptySet()
) {
tags.forEach { tag ->
Chip(chipGroup.context).apply {
text = tag
isSelected = checkedChips.contains(text)
style()

onChipClicked?.let { listener ->
setOnClickListener {
listener(this)
}
}
}.also {
chipGroup.addView(it)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ru.otus.basicarchitecture.helpers

import java.time.LocalDate
import java.time.format.DateTimeFormatter

fun LocalDate.toText(pattern: String = "dd.MM.yyyy"): Result<String> {
return runCatching {
this.format(DateTimeFormatter.ofPattern(pattern))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ru.otus.basicarchitecture.helpers

import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.fragment.findNavController

val Fragment.navController: NavController get() = findNavController()
30 changes: 30 additions & 0 deletions app/src/main/java/ru/otus/basicarchitecture/network/Api.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ru.otus.basicarchitecture.network

import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import kotlinx.serialization.json.Json
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.http.Body
import retrofit2.http.POST
import ru.otus.basicarchitecture.ui.address.SuggestionResponse
import ru.otus.basicarchitecture.ui.address.SuggestQuery


private const val suggestionUrl =
"https://suggestions.dadata.ru/suggestions/api/4_1/rs/suggest/"

interface Api {
@POST("address")
suspend fun getSuggestions(@Body query: SuggestQuery): Response<SuggestionResponse>
}

fun buildRetrofit(okHttpClient: OkHttpClient): Retrofit {
val json = Json { ignoreUnknownKeys = true }
return Retrofit.Builder()
.baseUrl(suggestionUrl)
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package ru.otus.basicarchitecture.network

import okhttp3.Interceptor
import okhttp3.Response
import ru.otus.basicarchitecture.SessionManager
import javax.inject.Inject

class AuthInterceptor @Inject constructor(private val sessionManager: SessionManager) :
Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
val requestWithToken = request.newBuilder()
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Token ${sessionManager.getToken()}")
.build()

return chain.proceed(requestWithToken)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package ru.otus.basicarchitecture.network

interface Debouncer {
suspend fun debounce()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ru.otus.basicarchitecture.network

import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ViewModelComponent
import kotlinx.coroutines.delay

@Module
@InstallIn(ViewModelComponent::class)
object DebouncerProvider {
private const val debouncePeriod = 500L

class DebouncerImpl : Debouncer {
override suspend fun debounce() {
delay(debouncePeriod)
}
}

@Provides
fun provideDebouncer(): Debouncer {
return DebouncerImpl()
}
}
Loading