guarda info basica de perfiles para uso offline. Por ahora, se utiliza la info del usuario actual para mostrarla en ajustes

This commit is contained in:
erik-everardo 2024-01-27 00:33:58 -06:00
parent 381274a1b6
commit fb92a69fae
19 changed files with 212 additions and 28 deletions

View File

@ -1,12 +1,13 @@
package com.isolaatti.auth
import com.isolaatti.auth.data.AuthRepositoryImpl
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.auth.data.UserInfoRepositoryImpl
import com.isolaatti.auth.data.local.TokenStorage
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.auth.data.remote.AuthApi
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.auth.domain.UserInfoRepository
import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.database.AppDatabase
import com.isolaatti.settings.domain.UserIdSetting
import dagger.Module
import dagger.Provides
@ -25,4 +26,9 @@ class Module {
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, userIdSetting: UserIdSetting): AuthRepository {
return AuthRepositoryImpl(tokenStorage, authApi, userIdSetting)
}
@Provides
fun provideUserInfoRepository(userIdSetting: UserIdSetting, userInfoDao: UserInfoDao): UserInfoRepository {
return UserInfoRepositoryImpl(userIdSetting, userInfoDao)
}
}

View File

@ -1,8 +1,6 @@
package com.isolaatti.auth.data
import com.isolaatti.BuildConfig
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.settings.data.KeyValueEntity
import com.isolaatti.auth.data.remote.AuthTokenDto
import com.isolaatti.auth.data.local.TokenStorage
import com.isolaatti.auth.data.remote.AuthApi
@ -10,8 +8,12 @@ import com.isolaatti.auth.data.remote.Credential
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.settings.domain.UserIdSetting
import com.isolaatti.utils.Resource
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.launch
import retrofit2.awaitResponse
import javax.inject.Inject
@ -51,8 +53,15 @@ class AuthRepositoryImpl @Inject constructor(
return tokenStorage.token
}
override fun getUserId(): Flow<Int?> = flow {
emit(userIdSetting.getUserId())
override fun getUserId(): Flow<Int?> {
CoroutineScope(Dispatchers.IO).launch {
val currentUserId = userIdSetting.getUserIdAsync()
if(currentUserId == null) {
validateSession()
}
}
return userIdSetting.getUserId()
}
override suspend fun setToken(sessionDto: AuthTokenDto) {
@ -60,4 +69,17 @@ class AuthRepositoryImpl @Inject constructor(
userIdSetting.setUserId(sessionDto.userId)
}
private suspend fun validateSession() {
val token = tokenStorage.token?.token
if(token != null) {
try {
val response = authApi.validateTokenUrl(token).awaitResponse()
if(response.isSuccessful) {
response.body()?.userId?.let { userIdSetting.setUserId(it) }
}
} catch(_: Exception) { }
}
}
}

View File

@ -0,0 +1,22 @@
package com.isolaatti.auth.data
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.auth.domain.UserInfo
import com.isolaatti.auth.domain.UserInfoRepository
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.settings.domain.UserIdSetting
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.map
import javax.inject.Inject
class UserInfoRepositoryImpl @Inject constructor(private val userIdSetting: UserIdSetting, private val userInfoDao: UserInfoDao) : UserInfoRepository {
override fun getCurrentUserInfo(): Flow<UserInfo> = flow {
val currentUserId = userIdSetting.getUserIdAsync()
if (currentUserId != null) {
emitAll(userInfoDao.getUserInfo(currentUserId).map { UserInfo.fromEntity(it) })
}
}
}

View File

@ -0,0 +1,16 @@
package com.isolaatti.auth.data.local
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface UserInfoDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun setUserInfo(userInfoEntity: UserInfoEntity)
@Query("SELECT * FROM user_info WHERE id = :id LIMIT 1")
fun getUserInfo(id: Int): Flow<UserInfoEntity>
}

View File

@ -0,0 +1,11 @@
package com.isolaatti.auth.data.local
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "user_info")
data class UserInfoEntity(
@PrimaryKey val id: Int,
val username: String,
val displayName: String,
)

View File

@ -1,6 +1,7 @@
package com.isolaatti.auth.domain
import com.isolaatti.auth.data.remote.AuthTokenDto
import com.isolaatti.auth.data.remote.AuthTokenVerificationDto
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow

View File

@ -0,0 +1,15 @@
package com.isolaatti.auth.domain
import com.isolaatti.auth.data.local.UserInfoEntity
import com.isolaatti.utils.UrlGen
data class UserInfo(val id: Int, val username: String, val displayName: String) {
val imageUrl get() = UrlGen.userProfileImage(id, false)
companion object {
fun fromEntity(userInfoEntity: UserInfoEntity): UserInfo {
return UserInfo(userInfoEntity.id, userInfoEntity.username, userInfoEntity.displayName)
}
}
}

View File

@ -0,0 +1,7 @@
package com.isolaatti.auth.domain
import kotlinx.coroutines.flow.Flow
interface UserInfoRepository {
fun getCurrentUserInfo(): Flow<UserInfo>
}

View File

@ -14,6 +14,10 @@ import com.isolaatti.settings.domain.UserIdSetting
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -38,7 +42,7 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
CoroutineScope(Dispatchers.IO).launch {
when(options) {
POST_OPTIONS -> {
userIdSetting.getUserId()?.let { userId ->
userIdSetting.getUserId().onEach { userId ->
_options.postValue(
Options.getPostsOptions(
userOwned = userId == payload?.userId,
@ -47,10 +51,10 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
)
_callerId = callerId
_payload = payload
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
COMMENT_OPTIONS -> {
userIdSetting.getUserId()?.let { userId ->
userIdSetting.getUserId().onEach { userId ->
_options.postValue(
Options.getCommentOptions(
userOwned = userId == payload?.userId,
@ -59,14 +63,14 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
)
_callerId = callerId
_payload = payload
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
PROFILE_PHOTO_OPTIONS -> {
userIdSetting.getUserId()?.let { userId ->
userIdSetting.getUserId().onEach { userId ->
_options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,))
_callerId = callerId
_payload = payload
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -2,11 +2,14 @@ package com.isolaatti.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.auth.data.local.UserInfoEntity
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.settings.data.KeyValueEntity
@Database(entities = [KeyValueEntity::class], version = 1)
@Database(entities = [KeyValueEntity::class, UserInfoEntity::class], version = 2)
abstract class AppDatabase : RoomDatabase() {
abstract fun keyValueDao(): KeyValueDao
abstract fun userInfoDao(): UserInfoDao
}

View File

@ -15,6 +15,8 @@ class Module {
@Provides
@Singleton
fun provideAppDatabase(@ApplicationContext applicationContext: Context): AppDatabase {
return Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database.db").build()
return Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database.db")
.fallbackToDestructiveMigration()
.build()
}
}

View File

@ -1,6 +1,8 @@
package com.isolaatti.profile
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.database.AppDatabase
import com.isolaatti.profile.data.remote.ProfileApi
import com.isolaatti.profile.data.repository.ProfileRepositoryImpl
import com.isolaatti.profile.domain.ProfileRepository
@ -18,7 +20,14 @@ class Module {
}
@Provides
fun provideProfileRepository(profileApi: ProfileApi): ProfileRepository {
return ProfileRepositoryImpl(profileApi)
fun provideUserInfoDao(database: AppDatabase): UserInfoDao {
return database.userInfoDao()
}
@Provides
fun provideProfileRepository(profileApi: ProfileApi, userInfoDao: UserInfoDao): ProfileRepository {
return ProfileRepositoryImpl(profileApi, userInfoDao)
}
}

View File

@ -1,6 +1,8 @@
package com.isolaatti.profile.data.repository
import android.util.Log
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.auth.data.local.UserInfoEntity
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.profile.data.remote.ProfileApi
import com.isolaatti.profile.data.remote.UpdateProfileDto
@ -12,13 +14,17 @@ import kotlinx.coroutines.flow.flow
import retrofit2.awaitResponse
import javax.inject.Inject
class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository {
class ProfileRepositoryImpl @Inject constructor(
private val profileApi: ProfileApi,
private val userInfoDao: UserInfoDao) : ProfileRepository {
override fun getProfile(userId: Int): Flow<Resource<UserProfile>> = flow {
try {
val result = profileApi.userProfile(userId).awaitResponse()
if(result.isSuccessful) {
val dto = result.body()
if(dto != null) {
userInfoDao.setUserInfo(UserInfoEntity(userId, dto.uniqueUsername, dto.name))
emit(Resource.Success(UserProfile.fromDto(dto)))
}
} else {

View File

@ -4,12 +4,16 @@ import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao
interface KeyValueDao {
@Query("SELECT value FROM key_values WHERE id = :key")
suspend fun getValue(key: String): String
fun getValue(key: String): Flow<String>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setValue(entity: KeyValueEntity)
@Query("SELECT value FROM key_values WHERE id = :key")
suspend fun getValueAsync(key: String): String?
}

View File

@ -1,13 +1,18 @@
package com.isolaatti.settings.data
import com.isolaatti.settings.domain.SettingsRepository
import kotlinx.coroutines.flow.Flow
class SettingsRepositoryImpl(private val keyValueDao: KeyValueDao) : SettingsRepository {
override suspend fun setKeyValue(key: String, value: String) {
keyValueDao.setValue(KeyValueEntity(key, value))
}
override suspend fun getKeyValue(key: String): String? {
override fun getKeyValue(key: String): Flow<String?> {
return keyValueDao.getValue(key)
}
override suspend fun getKeyValueAsync(key: String): String? {
return keyValueDao.getValueAsync(key)
}
}

View File

@ -1,6 +1,10 @@
package com.isolaatti.settings.domain
import kotlinx.coroutines.flow.Flow
interface SettingsRepository {
suspend fun setKeyValue(key: String, value: String)
suspend fun getKeyValue(key: String): String?
fun getKeyValue(key: String): Flow<String?>
suspend fun getKeyValueAsync(key: String): String?
}

View File

@ -2,11 +2,16 @@ package com.isolaatti.settings.domain
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject
class UserIdSetting @Inject constructor(private val settingsRepository: SettingsRepository) {
suspend fun getUserId(): Int? {
return settingsRepository.getKeyValue(KEY_USER_ID)?.toIntOrNull()
fun getUserId(): Flow<Int?> {
return settingsRepository.getKeyValue(KEY_USER_ID).map { it?.toIntOrNull() }
}
suspend fun getUserIdAsync(): Int? {
return settingsRepository.getKeyValueAsync(KEY_USER_ID)?.toIntOrNull()
}
suspend fun setUserId(userId: Int) {

View File

@ -0,0 +1,26 @@
package com.isolaatti.settings.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.auth.domain.UserInfo
import com.isolaatti.auth.domain.UserInfoRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SettingsViewModel @Inject constructor(private val userInfoRepository: UserInfoRepository) : ViewModel() {
val userInfo: MutableLiveData<UserInfo> = MutableLiveData()
fun getUserInfo() {
viewModelScope.launch {
userInfoRepository.getCurrentUserInfo().onEach {
userInfo.postValue(it)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -5,11 +5,17 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import coil.load
import com.isolaatti.databinding.FragmentSettingsBinding
import com.isolaatti.settings.presentation.SettingsViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SettingsFragment : Fragment() {
private lateinit var binding: FragmentSettingsBinding
private val viewModel: SettingsViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater,
@ -25,6 +31,16 @@ class SettingsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState)
setupListeners()
setupObservers()
viewModel.getUserInfo()
}
private fun setupObservers() {
viewModel.userInfo.observe(viewLifecycleOwner) {
binding.textViewUsername.text = "@${it.username}"
binding.textViewDisplayName.text = it.displayName
binding.profileImageView.load(it.imageUrl)
}
}
private fun setupListeners() {