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 package com.isolaatti.auth
import com.isolaatti.auth.data.AuthRepositoryImpl 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.TokenStorage
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.auth.data.remote.AuthApi import com.isolaatti.auth.data.remote.AuthApi
import com.isolaatti.auth.domain.AuthRepository import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.auth.domain.UserInfoRepository
import com.isolaatti.connectivity.RetrofitClient import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.database.AppDatabase
import com.isolaatti.settings.domain.UserIdSetting import com.isolaatti.settings.domain.UserIdSetting
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -25,4 +26,9 @@ class Module {
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, userIdSetting: UserIdSetting): AuthRepository { fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, userIdSetting: UserIdSetting): AuthRepository {
return AuthRepositoryImpl(tokenStorage, authApi, userIdSetting) 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 package com.isolaatti.auth.data
import com.isolaatti.BuildConfig 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.remote.AuthTokenDto
import com.isolaatti.auth.data.local.TokenStorage import com.isolaatti.auth.data.local.TokenStorage
import com.isolaatti.auth.data.remote.AuthApi 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.auth.domain.AuthRepository
import com.isolaatti.settings.domain.UserIdSetting import com.isolaatti.settings.domain.UserIdSetting
import com.isolaatti.utils.Resource 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.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.last
import kotlinx.coroutines.launch
import retrofit2.awaitResponse import retrofit2.awaitResponse
import javax.inject.Inject import javax.inject.Inject
@ -51,8 +53,15 @@ class AuthRepositoryImpl @Inject constructor(
return tokenStorage.token return tokenStorage.token
} }
override fun getUserId(): Flow<Int?> = flow { override fun getUserId(): Flow<Int?> {
emit(userIdSetting.getUserId()) CoroutineScope(Dispatchers.IO).launch {
val currentUserId = userIdSetting.getUserIdAsync()
if(currentUserId == null) {
validateSession()
}
}
return userIdSetting.getUserId()
} }
override suspend fun setToken(sessionDto: AuthTokenDto) { override suspend fun setToken(sessionDto: AuthTokenDto) {
@ -60,4 +69,17 @@ class AuthRepositoryImpl @Inject constructor(
userIdSetting.setUserId(sessionDto.userId) 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 package com.isolaatti.auth.domain
import com.isolaatti.auth.data.remote.AuthTokenDto import com.isolaatti.auth.data.remote.AuthTokenDto
import com.isolaatti.auth.data.remote.AuthTokenVerificationDto
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow 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 dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers 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 kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -38,35 +42,35 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
when(options) { when(options) {
POST_OPTIONS -> { POST_OPTIONS -> {
userIdSetting.getUserId()?.let { userId -> userIdSetting.getUserId().onEach { userId ->
_options.postValue( _options.postValue(
Options.getPostsOptions( Options.getPostsOptions(
userOwned = userId == payload?.userId, userOwned = userId == payload?.userId,
savable = false, savable = false,
snapshotAble = false) snapshotAble = false)
) )
_callerId = callerId _callerId = callerId
_payload = payload _payload = payload
} }.flowOn(Dispatchers.IO).launchIn(this)
} }
COMMENT_OPTIONS -> { COMMENT_OPTIONS -> {
userIdSetting.getUserId()?.let { userId -> userIdSetting.getUserId().onEach { userId ->
_options.postValue( _options.postValue(
Options.getCommentOptions( Options.getCommentOptions(
userOwned = userId == payload?.userId, userOwned = userId == payload?.userId,
savable = false, savable = false,
snapshotAble = false) snapshotAble = false)
) )
_callerId = callerId _callerId = callerId
_payload = payload _payload = payload
} }.flowOn(Dispatchers.IO).launchIn(this)
} }
PROFILE_PHOTO_OPTIONS -> { PROFILE_PHOTO_OPTIONS -> {
userIdSetting.getUserId()?.let { userId -> userIdSetting.getUserId().onEach { userId ->
_options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,)) _options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,))
_callerId = callerId _callerId = callerId
_payload = payload _payload = payload
} }.flowOn(Dispatchers.IO).launchIn(this)
} }
} }
} }

View File

@ -2,11 +2,14 @@ package com.isolaatti.database
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase 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.KeyValueDao
import com.isolaatti.settings.data.KeyValueEntity 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 class AppDatabase : RoomDatabase() {
abstract fun keyValueDao(): KeyValueDao abstract fun keyValueDao(): KeyValueDao
abstract fun userInfoDao(): UserInfoDao
} }

View File

@ -15,6 +15,8 @@ class Module {
@Provides @Provides
@Singleton @Singleton
fun provideAppDatabase(@ApplicationContext applicationContext: Context): AppDatabase { 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 package com.isolaatti.profile
import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.connectivity.RetrofitClient import com.isolaatti.connectivity.RetrofitClient
import com.isolaatti.database.AppDatabase
import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.remote.ProfileApi
import com.isolaatti.profile.data.repository.ProfileRepositoryImpl import com.isolaatti.profile.data.repository.ProfileRepositoryImpl
import com.isolaatti.profile.domain.ProfileRepository import com.isolaatti.profile.domain.ProfileRepository
@ -18,7 +20,14 @@ class Module {
} }
@Provides @Provides
fun provideProfileRepository(profileApi: ProfileApi): ProfileRepository { fun provideUserInfoDao(database: AppDatabase): UserInfoDao {
return ProfileRepositoryImpl(profileApi) 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 package com.isolaatti.profile.data.repository
import android.util.Log 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.images.common.domain.entity.Image
import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.remote.ProfileApi
import com.isolaatti.profile.data.remote.UpdateProfileDto import com.isolaatti.profile.data.remote.UpdateProfileDto
@ -12,13 +14,17 @@ import kotlinx.coroutines.flow.flow
import retrofit2.awaitResponse import retrofit2.awaitResponse
import javax.inject.Inject 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 { override fun getProfile(userId: Int): Flow<Resource<UserProfile>> = flow {
try { try {
val result = profileApi.userProfile(userId).awaitResponse() val result = profileApi.userProfile(userId).awaitResponse()
if(result.isSuccessful) { if(result.isSuccessful) {
val dto = result.body() val dto = result.body()
if(dto != null) { if(dto != null) {
userInfoDao.setUserInfo(UserInfoEntity(userId, dto.uniqueUsername, dto.name))
emit(Resource.Success(UserProfile.fromDto(dto))) emit(Resource.Success(UserProfile.fromDto(dto)))
} }
} else { } else {

View File

@ -4,12 +4,16 @@ import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import kotlinx.coroutines.flow.Flow
@Dao @Dao
interface KeyValueDao { interface KeyValueDao {
@Query("SELECT value FROM key_values WHERE id = :key") @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) @Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun setValue(entity: KeyValueEntity) 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 package com.isolaatti.settings.data
import com.isolaatti.settings.domain.SettingsRepository import com.isolaatti.settings.domain.SettingsRepository
import kotlinx.coroutines.flow.Flow
class SettingsRepositoryImpl(private val keyValueDao: KeyValueDao) : SettingsRepository { class SettingsRepositoryImpl(private val keyValueDao: KeyValueDao) : SettingsRepository {
override suspend fun setKeyValue(key: String, value: String) { override suspend fun setKeyValue(key: String, value: String) {
keyValueDao.setValue(KeyValueEntity(key, value)) keyValueDao.setValue(KeyValueEntity(key, value))
} }
override suspend fun getKeyValue(key: String): String? { override fun getKeyValue(key: String): Flow<String?> {
return keyValueDao.getValue(key) 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 package com.isolaatti.settings.domain
import kotlinx.coroutines.flow.Flow
interface SettingsRepository { interface SettingsRepository {
suspend fun setKeyValue(key: String, value: String) 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.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import javax.inject.Inject import javax.inject.Inject
class UserIdSetting @Inject constructor(private val settingsRepository: SettingsRepository) { class UserIdSetting @Inject constructor(private val settingsRepository: SettingsRepository) {
suspend fun getUserId(): Int? { fun getUserId(): Flow<Int?> {
return settingsRepository.getKeyValue(KEY_USER_ID)?.toIntOrNull() 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) { 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.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import coil.load
import com.isolaatti.databinding.FragmentSettingsBinding import com.isolaatti.databinding.FragmentSettingsBinding
import com.isolaatti.settings.presentation.SettingsViewModel
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class SettingsFragment : Fragment() { class SettingsFragment : Fragment() {
private lateinit var binding: FragmentSettingsBinding private lateinit var binding: FragmentSettingsBinding
private val viewModel: SettingsViewModel by viewModels()
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, inflater: LayoutInflater,
@ -25,6 +31,16 @@ class SettingsFragment : Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupListeners() 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() { private fun setupListeners() {