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
6 changes: 6 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 16 additions & 16 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ android {

applicationVariants.all { variant ->
variant.outputs.all { output ->
outputFileName = "angeni-${variant.versionName}-${variant.buildType.name}.apk"
outputFileName = "yurko-${variant.versionName}-${variant.buildType.name}.apk"
}
}
}
Expand All @@ -83,65 +83,65 @@ hilt {

dependencies {

implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
// HTTP client.
def retrofitVersion = "2.9.0"
def okhttpVersion = '4.9.0'
def okhttpVersion = '5.0.0-alpha.3'
implementation "com.squareup.retrofit2:retrofit:$retrofitVersion"
implementation "com.squareup.retrofit2:converter-moshi:$retrofitVersion"
implementation "com.squareup.okhttp3:okhttp:$okhttpVersion"
implementation "com.squareup.okhttp3:okhttp-tls:$okhttpVersion"
implementation "com.squareup.okhttp3:logging-interceptor:$okhttpVersion"
implementation "com.squareup.okhttp3:logging-interceptor:5.0.0-alpha.3"
// JSON parser.
def moshiVersion = '1.12.0'
implementation "com.squareup.moshi:moshi:$moshiVersion"
implementation "com.squareup.moshi:moshi-adapters:$moshiVersion"
kapt "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
// AndroidX.
def lifecycleVersion = "2.4.0-alpha03"
def lifecycleVersion = "2.4.0"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion"
implementation "androidx.activity:activity-ktx:1.4.0-alpha01"
implementation 'androidx.activity:activity-ktx:1.4.0'
// Image loading.
def glideVersion = "4.11.0"
def glideVersion = '4.12.0'
implementation "com.github.bumptech.glide:glide:$glideVersion"
kapt "com.github.bumptech.glide:compiler:$glideVersion"
// View binding helper.
implementation "com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.4.4"
implementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.3'
// Video player
def exoPlayerVersion = "2.15.0"
def exoPlayerVersion = "2.16.1"
implementation "com.google.android.exoplayer:exoplayer-core:$exoPlayerVersion"
implementation "com.google.android.exoplayer:exoplayer-ui:$exoPlayerVersion"
// RecyclerView adapter delegates.
// https://github.com/sockeqwe/AdapterDelegates
def adapterDelegatesVersion = '4.3.0'
def adapterDelegatesVersion = '4.3.1'
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl:$adapterDelegatesVersion"
implementation "com.hannesdorfmann:adapterdelegates4-kotlin-dsl-viewbinding:$adapterDelegatesVersion"
// Timber logger
implementation 'com.jakewharton.timber:timber:5.0.1'
// Navigation.
def navigationVersion = "2.4.0-alpha09"
def navigationVersion = '2.4.0-beta02'
implementation "androidx.navigation:navigation-fragment-ktx:$navigationVersion"
implementation "androidx.navigation:navigation-ui-ktx:$navigationVersion"
// System insets handling.
// https://chrisbanes.github.io/insetter
// https://github.com/chrisbanes/insetter
implementation "dev.chrisbanes.insetter:insetter:0.6.0"
implementation 'dev.chrisbanes.insetter:insetter:0.6.1'
// https://github.com/haroldadmin/NetworkResponseAdapter
implementation "com.github.haroldadmin:NetworkResponseAdapter:4.2.2"
// Dependency injection
def hiltVersion = "2.38.1"
def hiltVersion = '2.40.5'
implementation "com.google.dagger:hilt-android:$hiltVersion"
kapt "com.google.dagger:hilt-compiler:$hiltVersion"
implementation "androidx.hilt:hilt-navigation-fragment:1.0.0"

implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"

implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.google.code.gson:gson:2.8.9'
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.andreyyurko.firstapp.data.di.module

import android.os.Build
import com.andreyyurko.firstapp.data.network.Api
import com.andreyyurko.firstapp.BuildConfig
import com.andreyyurko.firstapp.data.network.Api
import com.andreyyurko.firstapp.data.network.MockApi
import com.andreyyurko.firstapp.data.network.interceptor.AuthorizationInterceptor
import com.andreyyurko.firstapp.data.network.interceptor.FirstAppAuthenticator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.andreyyurko.firstapp.data.di.module
import android.content.Context
import android.content.SharedPreferences
import com.andreyyurko.firstapp.data.persistent.LocalKeyValueStorage
import com.andreyyurko.firstapp.data.persistent.ProfileInformationStorage
import com.squareup.moshi.Moshi
import dagger.Module
import dagger.Provides
Expand All @@ -18,10 +19,18 @@ object PersistencyModule {
@Provides
@Singleton
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences =
context.getSharedPreferences("our_awesome_app_local_key_value_storage", Context.MODE_PRIVATE)
context.getSharedPreferences(
"our_awesome_app_local_key_value_storage",
Context.MODE_PRIVATE
)

@Provides
@Singleton
fun provideLocalKeyValueStorage(pref: SharedPreferences, moshi: Moshi): LocalKeyValueStorage =
LocalKeyValueStorage(pref, moshi)

@Provides
@Singleton
fun provideProfileInformationStorage(pref: SharedPreferences, moshi: Moshi): ProfileInformationStorage =
ProfileInformationStorage(pref, moshi)
}
29 changes: 20 additions & 9 deletions app/src/main/java/com/andreyyurko/firstapp/data/network/MockApi.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import com.andreyyurko.firstapp.data.network.response.VerificationTokenResponse
import com.andreyyurko.firstapp.entity.User

class MockApi : Api {

private var isAuthorized : Boolean = false
override suspend fun getUsers(): NetworkResponse<List<User>, GetUsersErrorResponce> {
return NetworkResponse.Success(
body = listOf(
Expand Down Expand Up @@ -62,14 +64,23 @@ class MockApi : Api {
}

override suspend fun createProfile(request: CreateProfileRequest): NetworkResponse<AuthTokens, CreateProfileErrorResponse> {
return NetworkResponse.Success(
AuthTokens(
accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2MzgsImV4cCI6MTY0MDg3MTc3MX0.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI",
refreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2MzgsImV4cCI6MTY0MDg3MTc3MX0.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI",
accessTokenExpiration = 1640871771000,
refreshTokenExpiration = 1640871771000,
),
code = 200
)
if (isAuthorized) {
return NetworkResponse.Success(
AuthTokens(
accessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2MzgsImV4cCI6MTY0MDg3MTc3MX0.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI",
refreshToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dnZWRJbkFzIjoiYWRtaW4iLCJpYXQiOjE0MjI3Nzk2MzgsImV4cCI6MTY0MDg3MTc3MX0.gzSraSYS8EXBxLN_oWnFSRgCzcmJmMjLiuyu5CSpyHI",
accessTokenExpiration = 1640871771000,
refreshTokenExpiration = 1640871771000,
),
code = 200
)
}
else {
isAuthorized = true
return NetworkResponse.ServerError<CreateProfileErrorResponse>(
body = null,
code = 400
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.andreyyurko.firstapp.data.persistent

import android.content.SharedPreferences
import androidx.core.content.edit
import com.andreyyurko.firstapp.entity.ProfileInformation
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi
import timber.log.Timber
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty

class ProfileInformationStorage(
private val pref: SharedPreferences,
moshi: Moshi
) {
var profileInformation: ProfileInformation? by JsonDelegate(
PROFILE_INFORMATION,
pref,
moshi.adapter(ProfileInformation::class.java)
)


private class JsonDelegate<T>(
private val key: String,
private val pref: SharedPreferences,
private val adapter: JsonAdapter<T>
) : ReadWriteProperty<ProfileInformationStorage, T?> {

override fun setValue(thisRef: ProfileInformationStorage, property: KProperty<*>, value: T?) {
pref.edit(commit = true) {
if (value == null) remove(key)
else putString(key, adapter.toJson(value))
}
}

override fun getValue(thisRef: ProfileInformationStorage, property: KProperty<*>): T? {
return try {
pref.getString(key, null)?.let { adapter.fromJson(it) }
} catch (e: JsonDataException) {
Timber.e(e)
setValue(thisRef, property, null)
null
}
}
}

companion object {
private const val PROFILE_INFORMATION = "profile_information"
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package com.andreyyurko.firstapp.entity

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class ProfileInformation(
val firstName : String,
val lastName : String,
val nickName : String,
val email : String,
val password : String
@Json(name = "firstname") val firstName : String,
@Json(name = "lastname") val lastName : String,
@Json(name = "nickName") val nickName : String,
@Json(name = "email") val email : String,
@Json(name = "password") val password : String
)
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,15 @@ class AuthInteractor @Inject constructor(
verificationToken: String,
firstName: String,
lastName: String,
userName: String,
nickName: String,
password: String
): NetworkResponse<AuthTokens, CreateProfileErrorResponse> {
val response = authRepository.generateAuthTokensByEmailAndPersonalInfo(
email,
verificationToken,
firstName,
lastName,
userName,
nickName,
password
)
when (response) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.andreyyurko.firstapp.interactor

import com.andreyyurko.firstapp.entity.ProfileInformation
import com.andreyyurko.firstapp.repository.ProfileInformationRepository
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.first
import javax.inject.Inject

class ProfileInformationInteractor @Inject constructor(
private val profileInformationRepository: ProfileInformationRepository
) {
suspend fun saveProfileInformation(profileInformation : ProfileInformation) {
profileInformationRepository.saveProfileInformation(profileInformation)
}

suspend fun getProfileInformation(): StateFlow<ProfileInformation?> {
return profileInformationRepository.getProfileInformationFlow()
}

suspend fun logout() {
profileInformationRepository.saveProfileInformation(null)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.andreyyurko.firstapp.repository

import com.andreyyurko.firstapp.data.persistent.ProfileInformationStorage
import com.andreyyurko.firstapp.entity.ProfileInformation
import com.blelocking.di.AppCoroutineScope
import com.blelocking.di.IoCoroutineDispatcher
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton


@Singleton
class ProfileInformationRepository @Inject constructor(
private val profileInformationStorage: ProfileInformationStorage,
@AppCoroutineScope externalCoroutineScope: CoroutineScope,
@IoCoroutineDispatcher private val ioDispatcher: CoroutineDispatcher
) {
suspend fun saveProfileInformation(profileInformation: ProfileInformation?) {
withContext(ioDispatcher) {
Timber.d("Save profile information $profileInformation")
profileInformationStorage.profileInformation = profileInformation
}
}

suspend fun getProfileInformationFlow() : StateFlow<ProfileInformation?> {
return profileInformationFlow.await().asStateFlow()
}

private val profileInformationFlow: Deferred<MutableStateFlow<ProfileInformation?>> =
externalCoroutineScope.async(context = ioDispatcher, start = CoroutineStart.LAZY) {
Timber.d("Initializing auth tokens flow.")
MutableStateFlow(
profileInformationStorage.profileInformation
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,12 @@ class MainActivity : AppCompatActivity(R.layout.activity_main) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
subscribeToAuthorizationStatus()

}

private fun subscribeToAuthorizationStatus() {
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// TODO: удалить следующую строчку
viewModel.logout()
viewModel.isAuthorized()
viewModel.isAuthorizedFlow.collect {
showSuitableNavigationFlow(it)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,23 @@
package com.andreyyurko.firstapp.ui.bookmarks

import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import by.kirich1409.viewbindingdelegate.viewBinding
import com.andreyyurko.firstapp.R
import com.andreyyurko.firstapp.databinding.FragmentBookmarksBinding
import com.andreyyurko.firstapp.ui.base.BaseFragment
import dev.chrisbanes.insetter.applyInsetter

class BookmarksFragment : BaseFragment(R.layout.fragment_bookmarks) {
private val viewBinding by viewBinding(FragmentBookmarksBinding::bind)

private val viewModel: BookmarksViewModel by viewModels()

override fun onViewCreated(view : View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding.titleTextView.applyInsetter {
type(statusBars = true) { margin() }
}
}
}
Loading