From 4755d5269ce431ced54a3c7e70e18a9f43886fa6 Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 11:21:50 -0300 Subject: [PATCH 01/10] =?UTF-8?q?Refatorado=20tratamento=20de=20erros=20e?= =?UTF-8?q?=20resposta=20da=20API=20com=20padr=C3=A3o=20Result?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Criadas classes utilitárias `Result` e `HttpMap` para padronizar respostas e mapear códigos de erro HTTP. - Atualizado `RepoRepository` e sua implementação para retornar `Result` e tratar falhas de conexão (`IOException`). - Ajustado `RepoDetailViewModel` para gerenciar estados de sucesso e erro provenientes do repositório. - Atualizado modelo `RepoDetail` com campos anuláveis para evitar inconsistências na deserialização. - Adicionada chamada para `clearState` ao navegar para fora da `RepoDetailScreen`. --- .../data/repository/RepoRepositoryImpl.kt | 44 +++++++++------ .../devhub/domain/model/RepoDetail.kt | 28 +++++----- .../domain/repository/RepoRepository.kt | 5 +- .../devhub/ui/repo/RepoDetailScreen.kt | 13 +++-- .../devhub/ui/repo/RepoDetailViewModel.kt | 56 +++++++++++-------- .../com/delecrode/devhub/utils/HttpMap.kt | 11 ++++ .../java/com/delecrode/devhub/utils/Result.kt | 6 ++ 7 files changed, 104 insertions(+), 59 deletions(-) create mode 100644 app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt create mode 100644 app/src/main/java/com/delecrode/devhub/utils/Result.kt diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt index 95c5b37..c55756b 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt @@ -11,10 +11,17 @@ import com.delecrode.devhub.domain.model.Languages import com.delecrode.devhub.domain.model.RepoDetail import com.delecrode.devhub.domain.model.RepoFav import com.delecrode.devhub.domain.repository.RepoRepository +import com.delecrode.devhub.utils.Result +import com.delecrode.devhub.utils.mapHttpError import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map +import java.io.IOException -class RepoRepositoryImpl(val repoApi: RepoApiService, private val localDataSource: RepoLocalDataSource, private val authLocalDataSource: AuthLocalDataSource) : RepoRepository { +class RepoRepositoryImpl( + val repoApi: RepoApiService, + private val localDataSource: RepoLocalDataSource, + private val authLocalDataSource: AuthLocalDataSource +) : RepoRepository { override suspend fun save(repo: RepoFav) { localDataSource.save(repo.toEntity()) @@ -27,35 +34,40 @@ class RepoRepositoryImpl(val repoApi: RepoApiService, private val localDataSour override fun getUserName(): Flow = authLocalDataSource.getUserName() - override suspend fun getRepoDetail(owner: String, repo: String): RepoDetail { - try { + override suspend fun getRepoDetail(owner: String, repo: String): Result { + return try { val response = repoApi.getRepoDetail(owner, repo) if (response.isSuccessful) { val body = response.body() - if (body != null) { - return body.toRepoDetailDomain() - } else { - throw Exception("Resposta vazia do servidor") - } + ?: return Result.Error("Dados do repositório indisponíveis") + Result.Success(body.toRepoDetailDomain()) } else { - throw Exception("Erro na requisição ${response.code()}") + Result.Error(mapHttpError(response.code())) } - } catch (e: Exception) { - throw e + } catch (e: IOException) { + Result.Error("Sem conexão com a internet") } } - override suspend fun getLanguagesRepo(owner: String, repo: String) : Languages{ + override suspend fun getLanguagesRepo( + owner: String, + repo: String + ): Result { return try { val response = repoApi.getRepoLanguages(owner, repo) + if (response.isSuccessful) { val body = response.body() - body?.toLanguagesDomain() ?: Languages(emptyList()) + ?: return Result.Error("Linguagens indisponíveis") + + Result.Success(body.toLanguagesDomain()) } else { - Languages(emptyList()) + Result.Error(mapHttpError(response.code())) } - } catch (e: Exception) { - Languages(emptyList()) + + } catch (e: IOException) { + Result.Error("Sem conexão com a internet") } } + } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/domain/model/RepoDetail.kt b/app/src/main/java/com/delecrode/devhub/domain/model/RepoDetail.kt index fe6441f..9cddbe8 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/model/RepoDetail.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/model/RepoDetail.kt @@ -1,21 +1,21 @@ package com.delecrode.devhub.domain.model data class RepoDetail( - val id: Int, - val name: String, - val html_url: String, + val id: Int?, + val name: String?, + val html_url: String?, val description: String?, - val branches_url: String, - val tags_url: String, - val created_at: String, - val updated_at: String, - val pushed_at: String, - val clone_url: String, - val size: Int, - val language: String, - val forks_count: Int, - val default_branch: String, - val subscribers_count: Int + val branches_url: String?, + val tags_url: String?, + val created_at: String?, + val updated_at: String?, + val pushed_at: String?, + val clone_url: String?, + val size: Int?, + val language: String?, + val forks_count: Int?, + val default_branch: String?, + val subscribers_count: Int? ) data class Languages( diff --git a/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt b/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt index c2d510c..8657a94 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt @@ -3,13 +3,14 @@ package com.delecrode.devhub.domain.repository import com.delecrode.devhub.domain.model.Languages import com.delecrode.devhub.domain.model.RepoDetail import com.delecrode.devhub.domain.model.RepoFav +import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.Flow interface RepoRepository { - suspend fun getRepoDetail(owner: String, repo: String): RepoDetail + suspend fun getRepoDetail(owner: String, repo: String): Result - suspend fun getLanguagesRepo(owner: String, repo: String): Languages + suspend fun getLanguagesRepo(owner: String, repo: String): Result suspend fun save(repo: RepoFav) fun getAll(): Flow> diff --git a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt index fa9da5c..911e0c7 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt @@ -125,6 +125,7 @@ fun RepoDetailScreen( ), navigationIcon = { IconButton(onClick = { + viewModel.clearState() navController.popBackStack() }) { Icon( @@ -178,11 +179,13 @@ fun RepoDetailScreen( color = MaterialTheme.colorScheme.onBackground ) } - items(state.languages ?: emptyList()) { lang -> - Text( - text = " $lang ", - color = MaterialTheme.colorScheme.onBackground - ) + state.languages.let { + items(state.languages ?: emptyList()) { lang -> + Text( + text = " $lang ", + color = MaterialTheme.colorScheme.onBackground + ) + } } } diff --git a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt index a054b28..bc516f2 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.delecrode.devhub.domain.model.RepoFav import com.delecrode.devhub.domain.repository.RepoRepository +import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -19,39 +20,50 @@ class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { isLoading = true, error = null ) - try { - val repoDetail = repository.getRepoDetail(owner, repo) - _uiState.value = _uiState.value.copy( - repo = repoDetail, - isLoading = false - ) + when (val result = repository.getRepoDetail(owner, repo)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + repo = result.data, + isLoading = false, + error = null + ) + } - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false, - ) + is Result.Error -> { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = result.message + ) + } } } } - fun getLanguagesForRepo(owner: String, repo: String){ + fun getLanguagesForRepo(owner: String, repo: String) { viewModelScope.launch { - try{ - val result = repository.getLanguagesRepo(owner, repo) - _uiState.value = _uiState.value.copy( - languages = result.languages - ) - }catch (e: Exception){ - _uiState.value = _uiState.value.copy( - languages = emptyList() - ) + when (val result = repository.getLanguagesRepo(owner, repo)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + languages = result.data.languages, + isLoading = false, + error = null + ) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = result.message + ) + } } } } - fun clearState(){ + fun clearState() { _uiState.value = _uiState.value.copy( + repo = null, + languages = emptyList(), isLoading = false, error = null ) diff --git a/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt b/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt new file mode 100644 index 0000000..c821dcf --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt @@ -0,0 +1,11 @@ +package com.delecrode.devhub.utils + + +fun mapHttpError(code: Int): String = + when (code) { + 404 -> "Repositório não encontrado" + 403 -> "Limite da API atingido" + in 500..599 -> "Servidor indisponível" + else -> "Erro ao buscar dados" + } + diff --git a/app/src/main/java/com/delecrode/devhub/utils/Result.kt b/app/src/main/java/com/delecrode/devhub/utils/Result.kt new file mode 100644 index 0000000..17445cd --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/utils/Result.kt @@ -0,0 +1,6 @@ +package com.delecrode.devhub.utils + +sealed class Result { + data class Success(val data: T) : Result() + data class Error(val message: String) : Result() +} From c87780f2c91d3be8ec1e593487a2f6ac86523b26 Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 11:43:05 -0300 Subject: [PATCH 02/10] =?UTF-8?q?Implementado=20tratamento=20de=20erros=20?= =?UTF-8?q?com=20Result=20=20no=20reposit=C3=B3rio=20de=20usu=C3=A1rios?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refatorado `UserRepository` e `UserRepositoryImpl` para retornar `Result` em vez de lançar exceções. - Atualizado `HomeViewModel` e `ProfileViewModel` para tratar os estados de `Success` e `Error` retornados pelo repositório. - Renomeadas funções de mapeamento para `toUserGitDomain` e `toUserFirebaseDomain` para maior clareza. - Adicionado tratamento de erros HTTP e de conexão (IOException) no `UserRepositoryImpl`. --- .../devhub/data/mapper/UserMapper.kt | 4 +- .../data/repository/UserRepositoryImpl.kt | 83 +++++----- .../domain/repository/UserRepository.kt | 7 +- .../delecrode/devhub/ui/home/HomeViewModel.kt | 142 ++++++++++-------- .../devhub/ui/profile/ProfileViewModel.kt | 94 ++++++------ .../devhub/ui/repo/RepoDetailViewModel.kt | 2 +- 6 files changed, 176 insertions(+), 156 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt b/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt index 101e968..e145392 100644 --- a/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt @@ -5,7 +5,7 @@ import com.delecrode.devhub.data.model.dto.UserForGitDto import com.delecrode.devhub.domain.model.UserForFirebase import com.delecrode.devhub.domain.model.UserForGit -fun UserForGitDto.toUserDomain(): UserForGit { +fun UserForGitDto.toUserGitDomain(): UserForGit { return UserForGit( login = login, avatar_url = avatar_url, @@ -17,7 +17,7 @@ fun UserForGitDto.toUserDomain(): UserForGit { } -fun UserForFirebaseDto.toUserDomain(): UserForFirebase{ +fun UserForFirebaseDto.toUserFirebaseDomain(): UserForFirebase{ return UserForFirebase( fullName = fullName, username = username, diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt index aeb7139..bb26918 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt @@ -3,7 +3,8 @@ package com.delecrode.devhub.data.repository import android.util.Log import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource import com.delecrode.devhub.data.mapper.toReposDomain -import com.delecrode.devhub.data.mapper.toUserDomain +import com.delecrode.devhub.data.mapper.toUserFirebaseDomain +import com.delecrode.devhub.data.mapper.toUserGitDomain import com.delecrode.devhub.data.model.dto.UserForFirebaseDto import com.delecrode.devhub.data.remote.firebase.UserExtraData import com.delecrode.devhub.data.remote.webApi.service.UserApiService @@ -11,7 +12,10 @@ import com.delecrode.devhub.domain.model.Repos import com.delecrode.devhub.domain.model.UserForFirebase import com.delecrode.devhub.domain.model.UserForGit import com.delecrode.devhub.domain.repository.UserRepository +import com.delecrode.devhub.utils.Result +import com.delecrode.devhub.utils.mapHttpError import kotlinx.coroutines.flow.first +import java.io.IOException class UserRepositoryImpl( private val userApi: UserApiService, @@ -19,68 +23,59 @@ class UserRepositoryImpl( private val authLocalDataSource: AuthLocalDataSource ) : UserRepository { - override suspend fun getUserForGitHub(userName: String): UserForGit { - try { + override suspend fun getUserForGitHub(userName: String): Result { + return try { val response = userApi.getUser(userName) if (response.isSuccessful) { val body = response.body() - if (body != null) { - Log.i("GitRepositoryImpl", "getUser: $body") - return body.toUserDomain() - } else { - throw Exception("Resposta vazia do servidor") - } + ?: return Result.Error("Dados do usuário indisponíveis") + Result.Success(body.toUserGitDomain()) } else { - throw Exception("Erro na requisição ${response.code()}") + Result.Error(mapHttpError(response.code())) } - } catch (e: Exception) { - throw e + } catch (e: IOException) { + Result.Error("Sem conexão com a internet") } } - override suspend fun getUserForFirebase(): UserForFirebase { - try { + override suspend fun getUserForFirebase(): Result { + return try { val uid = authLocalDataSource.getUID().first() - Log.i("UserRepositoryImpl", "getUserForFirebase (UID Real): $uid") - if (uid != null) { - val response = userExtraData.getUser(uid) - if (response.exists()) { - val body = response.toObject(UserForFirebaseDto::class.java)?.toUserDomain() - val userName = body?.username - if (userName != null) { - authLocalDataSource.saveUserName(userName) - } - if (body != null) { - return body - } else { - throw Exception("Resposta vazia do servidor") - } - } else { - throw Exception("Usuário não encontrado") - } - } else { - return UserForFirebase() + ?: return Result.Error("Usuário não autenticado") + + val snapshot = userExtraData.getUser(uid) + + if (!snapshot.exists()) { + return Result.Error("Usuário não encontrado") } + + val user = snapshot + .toObject(UserForFirebaseDto::class.java) + ?.toUserFirebaseDomain() + ?: return Result.Error("Dados do usuário inválidos") + + authLocalDataSource.saveUserName(user.username) + + Result.Success(user) + } catch (e: Exception) { - throw e + Result.Error("Erro ao buscar dados do usuário") } } - override suspend fun getRepos(userName: String): List { - try { + + override suspend fun getRepos(userName: String): Result> { + return try { val response = userApi.getReposForUser(userName) if (response.isSuccessful) { val body = response.body() - if (body != null) { - return body.map { it.toReposDomain() } - } else { - throw Exception("Resposta vazia do servidor") - } + ?: return Result.Error("Dados dos repositórios indisponíveis") + Result.Success(body.map { it.toReposDomain() }) } else { - throw Exception("Erro na requisição ${response.code()}") + Result.Error(mapHttpError(response.code())) } - } catch (e: Exception) { - throw e + } catch (e: IOException) { + Result.Error("Sem conexão com a internet") } } } diff --git a/app/src/main/java/com/delecrode/devhub/domain/repository/UserRepository.kt b/app/src/main/java/com/delecrode/devhub/domain/repository/UserRepository.kt index a773d55..8bba190 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/repository/UserRepository.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/repository/UserRepository.kt @@ -3,12 +3,13 @@ package com.delecrode.devhub.domain.repository import com.delecrode.devhub.domain.model.Repos import com.delecrode.devhub.domain.model.UserForFirebase import com.delecrode.devhub.domain.model.UserForGit +import com.delecrode.devhub.utils.Result interface UserRepository { - suspend fun getUserForGitHub(userName: String): UserForGit - suspend fun getRepos(userName: String): List + suspend fun getUserForGitHub(userName: String): Result + suspend fun getRepos(userName: String): Result> - suspend fun getUserForFirebase(): UserForFirebase + suspend fun getUserForFirebase(): Result } diff --git a/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt index 382010a..81a6eba 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt @@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope import com.delecrode.devhub.domain.model.Repos import com.delecrode.devhub.domain.repository.AuthRepository import com.delecrode.devhub.domain.repository.UserRepository +import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -25,18 +26,21 @@ class HomeViewModel( isLoading = true, error = null ) - try { - val user = userRepository.getUserForGitHub(userName) - _uiState.value = _uiState.value.copy( - userForSearchGit = user, - isLoading = false - ) - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao buscar usuário", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + when (val result = userRepository.getUserForGitHub(userName)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + userForGit = result.data, + isLoading = false, + error = null + ) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = result.message + ) + } } } } @@ -47,18 +51,21 @@ class HomeViewModel( isLoading = true, error = null ) - try { - val user = userRepository.getUserForGitHub(userName) - _uiState.value = _uiState.value.copy( - userForGit = user, - isLoading = false - ) - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao buscar usuário", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + when (val result = userRepository.getUserForGitHub(userName)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + userForGit = result.data, + isLoading = false, + error = null + ) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = result.message + ) + } } } } @@ -69,16 +76,23 @@ class HomeViewModel( isLoading = true, error = null ) - try { - val user = userRepository.getUserForFirebase() - _uiState.value = _uiState.value.copy( - userForFirebase = user, - isLoading = false - ) - Log.i("HomeViewModel", "getUserForFirebase: $user") - } catch (e: Exception) { - throw e + when (val result = userRepository.getUserForFirebase()) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + userForFirebase = result.data, + isLoading = false, + error = null + ) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = result.message + ) + } } + } } @@ -88,40 +102,44 @@ class HomeViewModel( isLoading = true, error = null ) - try { - val repos: List = userRepository.getRepos(userName) - _uiState.value = _uiState.value.copy( - repos = repos, - isLoading = false - ) - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao buscar repositórios", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + when (val result = userRepository.getRepos(userName)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + repos = result.data, + isLoading = false, + error = null + ) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = result.message + ) + } } } } - fun signOut() { - viewModelScope.launch { - try { - authRepository.signOut() - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao fazer logout", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + fun signOut() { + viewModelScope.launch { + try { + authRepository.signOut() + } catch (e: Exception) { + Log.e("HomeViewModel", "Erro ao fazer logout", e) + _uiState.value = _uiState.value.copy( + error = e.message, + isLoading = false + ) + } } } - } - fun clearStates() { - _uiState.value = _uiState.value.copy( - isLoading = false, - error = null - ) + fun clearStates() { + _uiState.value = _uiState.value.copy( + isLoading = false, + error = null + ) + } } -} + diff --git a/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt index 1562025..b6279ca 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt @@ -3,20 +3,23 @@ package com.delecrode.devhub.ui.profile import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.delecrode.devhub.domain.model.Repos import com.delecrode.devhub.domain.repository.AuthRepository import com.delecrode.devhub.domain.repository.UserRepository +import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch -class ProfileViewModel(private val userRepository: UserRepository, private val authRepository: AuthRepository) : ViewModel() { +class ProfileViewModel( + private val userRepository: UserRepository, + private val authRepository: AuthRepository +) : ViewModel() { private val _uiState = MutableStateFlow(ProfileState()) val uiState: StateFlow = _uiState - init{ + init { getUserForFirebase() } @@ -25,59 +28,62 @@ class ProfileViewModel(private val userRepository: UserRepository, private val a isLoading = true ) viewModelScope.launch { - try { - val userForFirebase = userRepository.getUserForFirebase() - _uiState.value = _uiState.value.copy( - userForFirebase = userForFirebase - ) - - if (userForFirebase.username.isNotBlank()) { - getUserForGit(userForFirebase.username) - getRepos(userForFirebase.username) - } else { - _uiState.value = _uiState.value.copy(isLoading = false) + when (val result = userRepository.getUserForFirebase()) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + userForFirebase = result.data, + isLoading = false + ) + getUserForGit(result.data.username) } - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + is Result.Error -> { + _uiState.value = _uiState.value.copy( + error = result.message, + isLoading = false + ) + } } } } fun getUserForGit(userName: String) { viewModelScope.launch { - try { - val userForGit = userRepository.getUserForGitHub(userName) - _uiState.value = _uiState.value.copy( - userForGit = userForGit, - isLoading = false - ) - } catch (e: Exception) { - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + when (val result = userRepository.getUserForGitHub(userName)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + userForGit = result.data, + isLoading = false + ) + getRepos(userName) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + error = result.message, + isLoading = false + ) + } } } } fun getRepos(userName: String) { viewModelScope.launch { - try { - val repos: List = userRepository.getRepos(userName) - _uiState.value = _uiState.value.copy( - repos = repos, - isLoading = false - ) - } catch (e: Exception) { - Log.e("HomeViewModel", "Erro ao buscar repositórios", e) - _uiState.value = _uiState.value.copy( - error = e.message, - isLoading = false - ) + when (val result = userRepository.getRepos(userName)) { + is Result.Success -> { + _uiState.value = _uiState.value.copy( + repos = result.data, + isLoading = false + ) + } + + is Result.Error -> { + _uiState.value = _uiState.value.copy( + error = result.message, + isLoading = false + ) + } } } } @@ -96,11 +102,11 @@ class ProfileViewModel(private val userRepository: UserRepository, private val a } } - fun clearState(){ + fun clearState() { _uiState.value = _uiState.value.copy( isLoading = false, error = null ) } - } + diff --git a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt index bc516f2..0d9515e 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt @@ -76,7 +76,7 @@ class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { RepoFav( id = id, name = name, - userName = owner ?: "", + userName = owner, description = description, url = url ) From c2b3152bb78a62d6a3f7a38e342f2b1f04edec3c Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 12:00:58 -0300 Subject: [PATCH 03/10] =?UTF-8?q?Refatorado=20fluxo=20de=20autentica=C3=A7?= =?UTF-8?q?=C3=A3o=20com=20padr=C3=A3o=20Result=20e=20modelo=20de=20dom?= =?UTF-8?q?=C3=ADnio?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Atualizado `AuthRepository` para retornar `Result` e `Result`, removendo dependência direta de `FirebaseUser`. - Adicionado modelo de domínio `UserAuth` e mapper `toUserAuthDomain`. - Centralizado tratamento de erros de login e cadastro em `HttpMap.kt`. - Ajustados `AuthViewModel` e `RegisterViewModel` para consumir o novo padrão de resultado. - Removidos logs e imports não utilizados. --- .../devhub/data/mapper/UserMapper.kt | 11 ++++ .../devhub/data/model/dto/UserDto.kt | 2 +- .../data/repository/AuthRepositoryImpl.kt | 52 +++++++++---------- .../data/repository/UserRepositoryImpl.kt | 1 - .../com/delecrode/devhub/domain/model/User.kt | 6 +++ .../domain/repository/AuthRepository.kt | 7 +-- .../delecrode/devhub/ui/home/HomeViewModel.kt | 1 - .../devhub/ui/login/AuthViewModel.kt | 22 +++----- .../devhub/ui/register/RegisterViewModel.kt | 22 ++++---- .../com/delecrode/devhub/utils/HttpMap.kt | 41 +++++++++++++++ 10 files changed, 106 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt b/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt index e145392..e492c3d 100644 --- a/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt @@ -1,9 +1,12 @@ package com.delecrode.devhub.data.mapper +import com.delecrode.devhub.data.model.dto.UserAuthDto import com.delecrode.devhub.data.model.dto.UserForFirebaseDto import com.delecrode.devhub.data.model.dto.UserForGitDto +import com.delecrode.devhub.domain.model.UserAuth import com.delecrode.devhub.domain.model.UserForFirebase import com.delecrode.devhub.domain.model.UserForGit +import com.google.firebase.auth.FirebaseUser fun UserForGitDto.toUserGitDomain(): UserForGit { return UserForGit( @@ -24,3 +27,11 @@ fun UserForFirebaseDto.toUserFirebaseDomain(): UserForFirebase{ email = email ) } + +fun FirebaseUser.toUserAuthDomain(): UserAuth = + UserAuth( + uid = uid, + email = email + ) + + diff --git a/app/src/main/java/com/delecrode/devhub/data/model/dto/UserDto.kt b/app/src/main/java/com/delecrode/devhub/data/model/dto/UserDto.kt index 16327c6..5f07bdd 100644 --- a/app/src/main/java/com/delecrode/devhub/data/model/dto/UserDto.kt +++ b/app/src/main/java/com/delecrode/devhub/data/model/dto/UserDto.kt @@ -40,4 +40,4 @@ data class UserForFirebaseDto( val fullName: String = "", val username: String = "", val email: String = "" -) +) \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt index 4fe8709..0b8e68a 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt @@ -1,15 +1,18 @@ package com.delecrode.devhub.data.repository import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource +import com.delecrode.devhub.data.mapper.toUserAuthDomain import com.delecrode.devhub.data.remote.firebase.FirebaseAuth import com.delecrode.devhub.data.remote.firebase.UserExtraData import com.delecrode.devhub.domain.model.RegisterUser +import com.delecrode.devhub.domain.model.UserAuth import com.delecrode.devhub.domain.repository.AuthRepository import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException -import com.google.firebase.auth.FirebaseAuthInvalidUserException import com.google.firebase.auth.FirebaseAuthUserCollisionException import com.google.firebase.auth.FirebaseAuthWeakPasswordException -import com.google.firebase.auth.FirebaseUser +import com.delecrode.devhub.utils.Result +import com.delecrode.devhub.utils.mapAuthError +import com.delecrode.devhub.utils.mapSignUpError class AuthRepositoryImpl( private val authDataSource: FirebaseAuth, @@ -17,31 +20,32 @@ class AuthRepositoryImpl( private val authLocalDataSource: AuthLocalDataSource ) : AuthRepository { - override suspend fun signIn(email: String, password: String): FirebaseUser { - try { - val response = authDataSource.signIn(email, password) - authLocalDataSource.saveUID(response?.uid ?: "") - return response ?: throw Exception("Erro ao recuperar usuário após login") + override suspend fun signIn( + email: String, + password: String + ): Result { + return try { + val firebaseUser = authDataSource.signIn(email, password) + ?: return Result.Error("Erro ao autenticar usuário") + authLocalDataSource.saveUID(firebaseUser.uid) + + Result.Success(firebaseUser.toUserAuthDomain()) + } catch (e: Exception) { - val errorMessage = when (e) { - is FirebaseAuthInvalidUserException -> "Usuário não encontrado." - is FirebaseAuthInvalidCredentialsException -> "Senha incorreta ou e-mail inválido." - is FirebaseAuthUserCollisionException -> "Este e-mail já está em uso." - is FirebaseAuthWeakPasswordException -> "A senha deve ter pelo menos 6 caracteres." - else -> e.message ?: "Erro desconhecido ao fazer login" - } - throw Exception(errorMessage) + Result.Error(mapAuthError(e)) } } + override suspend fun signUp( name: String, username: String, email: String, password: String - ): Boolean { - try { - val uid = authDataSource.signUp(email, password) ?: return false + ): Result { + return try { + val uid = authDataSource.signUp(email, password) + ?: return Result.Error("Erro ao criar conta") val userData = RegisterUser( fullName = name, @@ -51,18 +55,14 @@ class AuthRepositoryImpl( userExtraDataSource.saveUserData(uid, userData) - return true + Result.Success(Unit) + } catch (e: Exception) { - val errorMessage = when (e) { - is FirebaseAuthUserCollisionException -> "Este e-mail já está em uso." - is FirebaseAuthWeakPasswordException -> "A senha deve ter pelo menos 6 caracteres." - is FirebaseAuthInvalidCredentialsException -> "E-mail inválido." - else -> e.message ?: "Erro desconhecido ao cadastrar" - } - throw Exception(errorMessage) + Result.Error(mapSignUpError(e)) } } + override suspend fun signOut() { authDataSource.signOut() authLocalDataSource.clearUID() diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt index bb26918..ff3c858 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt @@ -1,6 +1,5 @@ package com.delecrode.devhub.data.repository -import android.util.Log import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource import com.delecrode.devhub.data.mapper.toReposDomain import com.delecrode.devhub.data.mapper.toUserFirebaseDomain diff --git a/app/src/main/java/com/delecrode/devhub/domain/model/User.kt b/app/src/main/java/com/delecrode/devhub/domain/model/User.kt index 9aac089..cb51733 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/model/User.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/model/User.kt @@ -25,3 +25,9 @@ data class UserForFirebase( val email: String = "" ) +data class UserAuth( + val uid: String, + val email: String? +) + + diff --git a/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt b/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt index 5113038..c8aef41 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt @@ -1,17 +1,18 @@ package com.delecrode.devhub.domain.repository -import com.google.firebase.auth.FirebaseUser +import com.delecrode.devhub.domain.model.UserAuth +import com.delecrode.devhub.utils.Result interface AuthRepository { - suspend fun signIn(email: String, password: String): FirebaseUser + suspend fun signIn(email: String, password: String): Result suspend fun signUp( name: String, username: String, email: String, password: String - ): Boolean + ): Result suspend fun signOut() diff --git a/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt index 81a6eba..fd26ef3 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt @@ -3,7 +3,6 @@ package com.delecrode.devhub.ui.home import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import com.delecrode.devhub.domain.model.Repos import com.delecrode.devhub.domain.repository.AuthRepository import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.utils.Result diff --git a/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt index 694d0c2..ceb883b 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt @@ -1,9 +1,9 @@ package com.delecrode.devhub.ui.login -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -20,19 +20,13 @@ class AuthViewModel( viewModelScope.launch { _state.value = AuthState(isLoading = true) - try { - val user = repository.signIn(email, password) - _state.value = AuthState( - isSuccess = true, - userUid = user.uid, - isLoading = false - ) - Log.i("AuthViewModel", "signIn: Usuario Logado ${user.uid}") - } catch (e: Exception) { - _state.value = AuthState( - error = e.message, - isLoading = false - ) + when (val result = repository.signIn(email, password)) { + is Result.Success -> { + _state.value = AuthState(isSuccess = true) + } + is Result.Error-> { + _state.value = AuthState(error = result.message) + } } } } diff --git a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt index 2d696c2..365fd4e 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt @@ -3,6 +3,7 @@ package com.delecrode.devhub.ui.register import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch @@ -15,24 +16,19 @@ class RegisterViewModel( val state = _state.asStateFlow() fun signUp(name: String, username: String, email: String, password: String) { - _state.value = RegisterState(isLoading = true) viewModelScope.launch { - try{ - val response = repository.signUp(name, username, email, password) - _state.value = RegisterState( - isSuccess = response, - isLoading = false - ) - }catch (e: Exception){ - _state.value = RegisterState( - error = e.message, - isLoading = false - ) + when (val result = repository.signUp(name, username, email, password)) { + is Result.Success -> { + _state.value = RegisterState(isSuccess = true) + } + is Result.Error -> { + _state.value = RegisterState(error = result.message) + } } } - } + fun clearState() { _state.value = RegisterState() diff --git a/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt b/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt index c821dcf..16ce048 100644 --- a/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt +++ b/app/src/main/java/com/delecrode/devhub/utils/HttpMap.kt @@ -1,5 +1,10 @@ package com.delecrode.devhub.utils +import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException +import com.google.firebase.auth.FirebaseAuthInvalidUserException +import com.google.firebase.auth.FirebaseAuthUserCollisionException +import com.google.firebase.auth.FirebaseAuthWeakPasswordException + fun mapHttpError(code: Int): String = when (code) { @@ -9,3 +14,39 @@ fun mapHttpError(code: Int): String = else -> "Erro ao buscar dados" } + +fun mapAuthError(e: Exception): String = + when (e) { + is FirebaseAuthInvalidUserException -> + "Usuário não encontrado." + + is FirebaseAuthInvalidCredentialsException -> + "E-mail ou senha inválidos." + + is FirebaseAuthUserCollisionException -> + "Este e-mail já está em uso." + + is FirebaseAuthWeakPasswordException -> + "A senha deve ter no mínimo 6 caracteres." + + else -> + "Erro ao realizar login. Tente novamente." + } + +fun mapSignUpError(e: Exception): String = + when (e) { + is FirebaseAuthUserCollisionException -> + "Este e-mail já está em uso." + + is FirebaseAuthWeakPasswordException -> + "A senha deve ter no mínimo 6 caracteres." + + is FirebaseAuthInvalidCredentialsException -> + "E-mail inválido." + + else -> + "Erro ao criar conta. Tente novamente." + } + + + From 6178c41178425817fe13e5ca63d3bf7e89239590 Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 12:06:41 -0300 Subject: [PATCH 04/10] =?UTF-8?q?Melhorias=20na=20valida=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20cadastro=20e=20limpeza=20de=20imports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adicionada verificação de tamanho mínimo da senha (6 caracteres) para habilitar o botão de registro em `RegisterScreen`. - Removidos imports não utilizados em `AuthRepositoryImpl` relacionados a exceções do Firebase. - Removido import não utilizado `UserAuthDto` em `UserMapper`. --- .../main/java/com/delecrode/devhub/data/mapper/UserMapper.kt | 1 - .../com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt | 3 --- .../java/com/delecrode/devhub/ui/register/RegisterScreen.kt | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt b/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt index e492c3d..f0ded28 100644 --- a/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt +++ b/app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt @@ -1,6 +1,5 @@ package com.delecrode.devhub.data.mapper -import com.delecrode.devhub.data.model.dto.UserAuthDto import com.delecrode.devhub.data.model.dto.UserForFirebaseDto import com.delecrode.devhub.data.model.dto.UserForGitDto import com.delecrode.devhub.domain.model.UserAuth diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt index 0b8e68a..9d42d50 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt @@ -7,9 +7,6 @@ import com.delecrode.devhub.data.remote.firebase.UserExtraData import com.delecrode.devhub.domain.model.RegisterUser import com.delecrode.devhub.domain.model.UserAuth import com.delecrode.devhub.domain.repository.AuthRepository -import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException -import com.google.firebase.auth.FirebaseAuthUserCollisionException -import com.google.firebase.auth.FirebaseAuthWeakPasswordException import com.delecrode.devhub.utils.Result import com.delecrode.devhub.utils.mapAuthError import com.delecrode.devhub.utils.mapSignUpError diff --git a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt index a3f4521..f3f60c8 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt @@ -239,7 +239,7 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { password = password ) }, - enabled = email.isNotBlank() && password.isNotBlank() && password == confirmPassword + enabled = email.isNotBlank() && password.isNotBlank() && password == confirmPassword && password.length >= 6 ) } } From 0ff6fa971e72d03310549c9cd2b08574f32a341b Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 12:27:28 -0300 Subject: [PATCH 05/10] =?UTF-8?q?Refatorada=20l=C3=B3gica=20de=20busca=20e?= =?UTF-8?q?=20gerenciamento=20de=20estado=20na=20HomeScreen?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Centralizado estado do texto de busca no `HomeViewModel` e `HomeState`. - Removidos `LaunchedEffect` e variáveis de estado local da UI para input e trigger de busca. - Corrigida atualização incorreta da propriedade `userForSearchGit` no ViewModel. - Adicionada chamada para limpar estados ao navegar para o perfil ou realizar logout. --- .../delecrode/devhub/ui/home/HomeScreen.kt | 20 +++++++------------ .../com/delecrode/devhub/ui/home/HomeState.kt | 3 ++- .../delecrode/devhub/ui/home/HomeViewModel.kt | 18 ++++++++++++++++- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/ui/home/HomeScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/home/HomeScreen.kt index 14792c4..38da238 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/home/HomeScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/home/HomeScreen.kt @@ -72,20 +72,10 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { val repos = uiState.value.repos - var searchText by remember { mutableStateOf("") } - var search by remember { mutableStateOf(false) } var expanded by remember { mutableStateOf(false) } val context = LocalContext.current - LaunchedEffect(search) { - if (search && searchText.isNotBlank()) { - homeViewModel.getRepos(searchText) - homeViewModel.getUserForSearchGit(searchText) - search = false - } - } - LaunchedEffect(uiState.value.error) { if (uiState.value.error != null) { Toast.makeText(context, uiState.value.error, Toast.LENGTH_SHORT).show() @@ -155,6 +145,7 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { onClick = { expanded = false navController.navigate(AppDestinations.Profile.route) + homeViewModel.clearStates() } ) DropdownMenuItem( @@ -165,6 +156,7 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { navController.navigate(AppDestinations.Profile.route) { popUpTo(0) } + homeViewModel.clearStates() } ) } @@ -189,8 +181,10 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { .padding(8.dp) ) { OutlinedTextField( - value = searchText, - onValueChange = { searchText = it }, + value = uiState.value.searchText, + onValueChange = { value -> + homeViewModel.onSearchTextChange(value) + }, modifier = Modifier .fillMaxWidth() .padding(end = 16.dp), @@ -203,7 +197,7 @@ fun HomeScreen(navController: NavController, homeViewModel: HomeViewModel) { unfocusedBorderColor = Color.Gray ), trailingIcon = { - IconButton(onClick = { search = true }) { + IconButton(onClick = { homeViewModel.onSearchClick() }) { Icon( painter = painterResource(R.drawable.ic_search_24), contentDescription = "Search", diff --git a/app/src/main/java/com/delecrode/devhub/ui/home/HomeState.kt b/app/src/main/java/com/delecrode/devhub/ui/home/HomeState.kt index e262c63..4a8b152 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/home/HomeState.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/home/HomeState.kt @@ -5,10 +5,11 @@ import com.delecrode.devhub.domain.model.UserForFirebase import com.delecrode.devhub.domain.model.UserForGit data class HomeState( - val isLoading: Boolean = false, val userForSearchGit: UserForGit? = null, val userForGit: UserForGit? = null, val userForFirebase: UserForFirebase? = null, val repos: List = emptyList(), + val searchText: String = "", + val isLoading: Boolean = false, val error: String? = null, ) \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt index fd26ef3..8c23ce1 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/home/HomeViewModel.kt @@ -8,6 +8,7 @@ import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch class HomeViewModel( @@ -19,6 +20,21 @@ class HomeViewModel( private val _uiState = MutableStateFlow(HomeState()) val uiState: StateFlow = _uiState + fun onSearchTextChange(value: String) { + _uiState.update { + it.copy(searchText = value) + } + } + + fun onSearchClick() { + val search = uiState.value.searchText + if (search.isBlank()) return + + getRepos(search) + getUserForSearchGit(search) + } + + fun getUserForSearchGit(userName: String) { viewModelScope.launch { _uiState.value = _uiState.value.copy( @@ -28,7 +44,7 @@ class HomeViewModel( when (val result = userRepository.getUserForGitHub(userName)) { is Result.Success -> { _uiState.value = _uiState.value.copy( - userForGit = result.data, + userForSearchGit = result.data, isLoading = false, error = null ) From de1e763cd4a3827af8fab5155c20daf6d8b79ecf Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 13:02:29 -0300 Subject: [PATCH 06/10] =?UTF-8?q?Implementada=20valida=C3=A7=C3=A3o=20de?= =?UTF-8?q?=20campos=20nas=20telas=20de=20Login=20e=20Cadastro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adicionada lógica de validação (email, senha, nome) em `AuthViewModel` e `RegisterViewModel`. - Atualizados `AuthState` e `RegisterState` para armazenar mensagens de erro e estados de validação. - Refatorada UI para exibir erros nos campos de texto e limpar estados ao digitar. - Implementado controle de habilitação dos botões de ação baseado na validade do formulário (`canLogin`/`canRegister`). - Incluída verificação de confirmação de senha no fluxo de cadastro. --- .../delecrode/devhub/ui/login/AuthState.kt | 12 ++- .../devhub/ui/login/AuthViewModel.kt | 66 +++++++++++++-- .../delecrode/devhub/ui/login/LoginScreen.kt | 22 +++-- .../devhub/ui/register/RegisterScreen.kt | 39 ++++++--- .../devhub/ui/register/RegisterState.kt | 15 +++- .../devhub/ui/register/RegisterViewModel.kt | 83 ++++++++++++++++++- 6 files changed, 212 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/ui/login/AuthState.kt b/app/src/main/java/com/delecrode/devhub/ui/login/AuthState.kt index 8c67f4d..0f690ca 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/login/AuthState.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/login/AuthState.kt @@ -4,5 +4,13 @@ data class AuthState( val isLoading: Boolean = false, val isSuccess: Boolean = false, val error: String? = null, - val userUid: String? = null -) + val userUid: String? = null, + val emailError: String? = null, + val passwordError: String? = null, +) { + val hasValidationErrors: Boolean + get() = emailError != null || passwordError != null + + val canLogin: Boolean + get() = !isLoading && !hasValidationErrors && error == null +} diff --git a/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt index ceb883b..07da1bf 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/login/AuthViewModel.kt @@ -16,23 +16,79 @@ class AuthViewModel( val state = _state.asStateFlow() fun signIn(email: String, password: String) { - _state.value = AuthState(isLoading = true) + viewModelScope.launch { _state.value = AuthState(isLoading = true) + val emailValidation = validateEmail(email) + if (emailValidation != null) { + _state.value = _state.value.copy( + emailError = emailValidation, + passwordError = null, + error = null, + isLoading = false + ) + return@launch + } - when (val result = repository.signIn(email, password)) { + val passwordValidation = validatePassword(password) + if (passwordValidation != null) { + _state.value = _state.value.copy( + emailError = null, + passwordError = passwordValidation, + error = null, + isLoading = false + ) + return@launch + } + + when (val result = repository.signIn(email, password)) { is Result.Success -> { _state.value = AuthState(isSuccess = true) } - is Result.Error-> { - _state.value = AuthState(error = result.message) + + is Result.Error -> { + _state.value = AuthState(error = result.message, isLoading = false) + } } } } - fun clearState(){ + fun clearState() { _state.value = AuthState() } + + fun clearEmailError() { + _state.value = _state.value.copy(emailError = null) + } + + fun clearPasswordError() { + _state.value = _state.value.copy(passwordError = null) + } + + private fun validateEmail(email: String): String? { + return when { + email.isBlank() -> "Email é obrigatório" + !isValidEmailFormat(email) -> "Email inválido" + else -> null + } + } + + private fun validatePassword(password: String): String? { + return when { + password.isBlank() -> "Senha é obrigatória" + password.length < 6 -> "Senha deve ter pelo menos 6 caracteres" + !countPassword(password) -> "Senha deve conter pelo menos 1 letra e 1 número" + else -> null + } + } + + private fun isValidEmailFormat(email: String): Boolean { + return Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$").matches(email) + } + + private fun countPassword(password: String): Boolean { + return password.length >= 6 + } } diff --git a/app/src/main/java/com/delecrode/devhub/ui/login/LoginScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/login/LoginScreen.kt index 9f86180..b97dfc1 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/login/LoginScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/login/LoginScreen.kt @@ -71,6 +71,18 @@ fun LoginScreen(navController: NavController, viewModel: AuthViewModel) { } } + LaunchedEffect(email) { + if (state.emailError != null) { + viewModel.clearEmailError() + } + } + + LaunchedEffect(password) { + if (state.passwordError != null) { + viewModel.clearPasswordError() + } + } + Scaffold( topBar = { @@ -111,8 +123,8 @@ fun LoginScreen(navController: NavController, viewModel: AuthViewModel) { value = email, onValueChange = { email = it }, imeAction = ImeAction.Next, - //isError = uiState.emailError != null, - //errorMessage = uiState.emailError ?: "" + isError = state.emailError != null, + errorMessage = state.emailError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -132,8 +144,8 @@ fun LoginScreen(navController: NavController, viewModel: AuthViewModel) { imeAction = ImeAction.Done, isPasswordVisible = passwordVisible, onVisibilityChange = { passwordVisible = !passwordVisible }, - //isError = uiState.passwordError != null, - //errorMessage = uiState.passwordError ?: "" + isError = state.passwordError != null, + errorMessage = state.passwordError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -156,7 +168,7 @@ fun LoginScreen(navController: NavController, viewModel: AuthViewModel) { onClick = { viewModel.signIn(email, password) }, - enabled = email.isNotBlank() && password.isNotBlank() + enabled = state.canLogin && email.isNotBlank() && password.isNotBlank() ) } } diff --git a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt index f3f60c8..b1054d9 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterScreen.kt @@ -75,6 +75,18 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { } } + LaunchedEffect(email) { + if (state.emailError != null) { + viewModel.clearEmailError() + } + } + + LaunchedEffect(password) { + if (state.passwordError != null) { + viewModel.clearPasswordError() + } + } + Scaffold( @@ -140,7 +152,9 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { label = "Nome Completo", leadingIcon = Icons.Default.Person, keyboardType = KeyboardType.Text, - imeAction = ImeAction.Next + imeAction = ImeAction.Next, + isError = state.nameError != null, + errorMessage = state.nameError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -159,7 +173,9 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { label = "Nome de Usuario", leadingIcon = Icons.Default.Person, keyboardType = KeyboardType.Text, - imeAction = ImeAction.Next + imeAction = ImeAction.Next, + isError = state.usernameError != null, + errorMessage = state.usernameError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -177,8 +193,8 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { value = email, onValueChange = { email = it }, imeAction = ImeAction.Next, - //isError = uiState.emailError != null, - //errorMessage = uiState.emailError ?: "" + isError = state.emailError != null, + errorMessage = state.emailError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -198,8 +214,8 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { imeAction = ImeAction.Next, isPasswordVisible = passwordVisible, onVisibilityChange = { passwordVisible = !passwordVisible }, - //isError = uiState.passwordError != null, - //errorMessage = uiState.passwordError ?: "" + isError = state.passwordError != null, + errorMessage = state.passwordError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -222,9 +238,9 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { onVisibilityChange = { passwordConfirmVisible = !passwordConfirmVisible }, - label = "Confirmar Senha" - //isError = uiState.passwordError != null, - //errorMessage = uiState.passwordError ?: "" + label = "Confirmar Senha", + isError = state.confirmPasswordError != null, + errorMessage = state.confirmPasswordError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) @@ -236,10 +252,11 @@ fun RegisterScreen(navController: NavController, viewModel: RegisterViewModel) { name = name, username = userName, email = email, - password = password + password = password, + confirmPassword = confirmPassword ) }, - enabled = email.isNotBlank() && password.isNotBlank() && password == confirmPassword && password.length >= 6 + enabled = state.canRegister && name.isNotBlank() && userName.isNotBlank() && email.isNotBlank() && password.isNotBlank() ) } } diff --git a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterState.kt b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterState.kt index c241785..8df8121 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterState.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterState.kt @@ -4,4 +4,17 @@ data class RegisterState( val isLoading: Boolean = false, val isSuccess: Boolean = false, val error: String? = null, -) + + val nameError: String? = null, + val usernameError: String? = null, + val emailError: String? = null, + val passwordError: String? = null, + val confirmPasswordError: String? = null +) { + val hasValidationErrors: Boolean + get() = emailError != null || passwordError != null + + val canRegister: Boolean + get() = !isLoading && !hasValidationErrors && error == null +} + diff --git a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt index 365fd4e..2796dfc 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/register/RegisterViewModel.kt @@ -15,12 +15,46 @@ class RegisterViewModel( private val _state = MutableStateFlow(RegisterState()) val state = _state.asStateFlow() - fun signUp(name: String, username: String, email: String, password: String) { + fun signUp(name: String, username: String, email: String, password: String, confirmPassword: String) { viewModelScope.launch { + validateName(name)?.let { + _state.value = _state.value.copy(nameError = it) + return@launch + } + + validateUsername(username)?.let { + _state.value = _state.value.copy(usernameError = it) + return@launch + } + val emailValidation = validateEmail(email) + if (emailValidation != null) { + _state.value = _state.value.copy( + emailError = emailValidation, + passwordError = null, + error = null + ) + return@launch + } + + val passwordValidation = validatePassword(password) + if (passwordValidation != null) { + _state.value = _state.value.copy( + emailError = null, + passwordError = passwordValidation, + error = null + ) + return@launch + } + validateConfirmPassword(password, confirmPassword)?.let { + _state.value = _state.value.copy(confirmPasswordError = it) + return@launch + } + when (val result = repository.signUp(name, username, email, password)) { is Result.Success -> { _state.value = RegisterState(isSuccess = true) } + is Result.Error -> { _state.value = RegisterState(error = result.message) } @@ -33,4 +67,51 @@ class RegisterViewModel( _state.value = RegisterState() } + + private fun validateName(name: String): String? = + if (name.isBlank()) "Nome é obrigatório" else null + + private fun validateUsername(username: String): String? = + if (username.isBlank()) "Username é obrigatório" else null + + private fun validateEmail(email: String): String? = + when { + email.isBlank() -> "Email é obrigatório" + !Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$").matches(email) -> + "Email inválido" + + else -> null + } + + private fun validatePassword(password: String): String? = + when { + password.isBlank() -> "Senha é obrigatória" + password.length < 6 -> "Senha deve ter pelo menos 6 caracteres" + !password.any { it.isDigit() } -> + "Senha deve conter pelo menos 1 número" + + else -> null + } + + private fun validateConfirmPassword( + password: String, + confirmPassword: String + ): String? = + when { + confirmPassword.isBlank() -> "Confirme a senha" + password != confirmPassword -> "As senhas devem ser iguais" + else -> null + } + + + + + fun clearEmailError() { + _state.value = _state.value.copy(emailError = null) + } + + fun clearPasswordError() { + _state.value = _state.value.copy(passwordError = null) + } + } \ No newline at end of file From 950abd82dd94b2ed462271e66a142738913ffc75 Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 15:51:45 -0300 Subject: [PATCH 07/10] =?UTF-8?q?Implementada=20persist=C3=AAncia=20e=20ge?= =?UTF-8?q?st=C3=A3o=20de=20estado=20de=20favoritos=20na=20tela=20de=20det?= =?UTF-8?q?alhes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adicionado suporte a exclusão (`delete`) no `RepoDao`, DataSource e Repository. - Atualizado `RepoDetailViewModel` para verificar persistência inicial (`checkIfFavorite`) e alternar estado via `deleteRepo` ou `favoriteRepo`. - Refatorada `RepoDetailScreen` para observar `uiState.isFavorite` em vez de gerenciar estado local, e removida chamada `clearState`. - Ajustado layout da `ReposFavScreen` com adição de ícone de favorito nos itens e remoção de cabeçalho estático. --- .../devhub/data/local/database/dao/RepoDao.kt | 6 ++- .../database/data/RepoLocalDataSource.kt | 2 + .../database/data/RepoLocalDataSourceImpl.kt | 4 ++ .../data/repository/RepoRepositoryImpl.kt | 4 ++ .../domain/repository/RepoRepository.kt | 3 +- .../devhub/ui/favoritos/ReposFavScreen.kt | 17 ++++---- .../devhub/ui/repo/RepoDetailScreen.kt | 41 ++++++++----------- .../devhub/ui/repo/RepoDetailState.kt | 3 +- .../devhub/ui/repo/RepoDetailViewModel.kt | 23 ++++++++++- 9 files changed, 65 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt b/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt index 9ca6795..41def3a 100644 --- a/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt +++ b/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt @@ -1,6 +1,7 @@ package com.delecrode.devhub.data.local.database.dao import androidx.room.Dao +import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query @@ -13,7 +14,10 @@ interface RepoDao { @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insert(repo: RepoEntity) + @Query("DELETE FROM repositories WHERE id = :id") + suspend fun delete(id: Int) + @Query("SELECT * FROM repositories") fun getAll(): Flow> -} \ No newline at end of file +} diff --git a/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSource.kt b/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSource.kt index 841baab..794806d 100644 --- a/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSource.kt +++ b/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSource.kt @@ -8,4 +8,6 @@ interface RepoLocalDataSource { suspend fun save(repo: RepoEntity) fun getAll(): Flow> + + suspend fun delete(id: Int) } diff --git a/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSourceImpl.kt b/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSourceImpl.kt index 9d8e8e4..e73fc63 100644 --- a/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSourceImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/local/database/data/RepoLocalDataSourceImpl.kt @@ -14,4 +14,8 @@ class RepoLocalDataSourceImpl( override fun getAll(): Flow> = dao.getAll() + + override suspend fun delete(id: Int) { + dao.delete(id) + } } diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt index c55756b..acc2b4b 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/RepoRepositoryImpl.kt @@ -27,6 +27,10 @@ class RepoRepositoryImpl( localDataSource.save(repo.toEntity()) } + override suspend fun delete(id: Int){ + localDataSource.delete(id) + } + override fun getAll(): Flow> = localDataSource.getAll() .map { list -> list.map { it.toDomain() } } diff --git a/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt b/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt index 8657a94..af662ae 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/repository/RepoRepository.kt @@ -9,11 +9,10 @@ import kotlinx.coroutines.flow.Flow interface RepoRepository { suspend fun getRepoDetail(owner: String, repo: String): Result - suspend fun getLanguagesRepo(owner: String, repo: String): Result - suspend fun save(repo: RepoFav) fun getAll(): Flow> + suspend fun delete(id: Int) fun getUserName(): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt index f1261ec..3c5ae39 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowBack +import androidx.compose.material.icons.filled.Favorite import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.CenterAlignedTopAppBar @@ -68,15 +69,6 @@ fun ReposFavScreen(navController: NavController, viewModel: RepoFavViewModel) { ) { padding -> Column(modifier = Modifier.padding(padding)) { - - Text( - "Repositórios Favoritos", fontWeight = FontWeight.Bold, - fontSize = 28.sp, - color = MaterialTheme.colorScheme.onBackground - ) - - Spacer(modifier = Modifier.height(16.dp)) - LazyColumn(modifier = Modifier.fillMaxWidth()) { items(state.repoFav.size) { index -> val repo = repos[index] @@ -103,7 +95,12 @@ fun ReposFavScreen(navController: NavController, viewModel: RepoFavViewModel) { .padding(10.dp), verticalAlignment = Alignment.CenterVertically ) { - Text(text = repo.name) + Text(text = repo.name, modifier = Modifier.weight(1f)) + Icon( + imageVector = Icons.Default.Favorite, + contentDescription = "Favorito", + tint = Color.Red + ) } if (repo.description != null) { diff --git a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt index 911e0c7..ed82a47 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailScreen.kt @@ -33,9 +33,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -63,8 +60,6 @@ fun RepoDetailScreen( ) { Log.i("RepoDetailScreen", "RepoDetailScreen: $owner, $repo") - var isFavorite by remember { mutableStateOf(false) } - val state by viewModel.uiState.collectAsState() val context = LocalContext.current @@ -97,23 +92,26 @@ fun RepoDetailScreen( actions = { IconButton( onClick = { - isFavorite = !isFavorite - viewModel.favoriteRepo( - id = state.repo?.id ?: 0, - name = state.repo?.name ?: "", - owner = owner, - description = state.repo?.description ?: "", - url = state.repo?.html_url ?: "" - ) + if (state.isFavorite) { + viewModel.deleteRepo(state.repo?.id ?: 0) + } else { + viewModel.favoriteRepo( + id = state.repo?.id ?: 0, + name = state.repo?.name ?: "", + owner = owner, + description = state.repo?.description ?: "", + url = state.repo?.html_url ?: "" + ) + } } ) { Icon( - imageVector = if (isFavorite) + imageVector = if (state.isFavorite) Icons.Filled.Favorite else Icons.Outlined.FavoriteBorder, contentDescription = "Favorito", - tint = if (isFavorite) + tint = if (state.isFavorite) Color.Red else MaterialTheme.colorScheme.onBackground @@ -125,7 +123,6 @@ fun RepoDetailScreen( ), navigationIcon = { IconButton(onClick = { - viewModel.clearState() navController.popBackStack() }) { Icon( @@ -179,13 +176,11 @@ fun RepoDetailScreen( color = MaterialTheme.colorScheme.onBackground ) } - state.languages.let { - items(state.languages ?: emptyList()) { lang -> - Text( - text = " $lang ", - color = MaterialTheme.colorScheme.onBackground - ) - } + items(state.languages ?: emptyList()) { lang -> + Text( + text = " $lang ", + color = MaterialTheme.colorScheme.onBackground + ) } } diff --git a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailState.kt b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailState.kt index a43caa2..f919d9a 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailState.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailState.kt @@ -7,5 +7,6 @@ data class RepoState( val error: String? = null, val userName: String? = null, val repo: RepoDetail? = null, - val languages: List? = null + val languages: List? = null, + val isFavorite: Boolean = false ) \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt index 0d9515e..6ce6e89 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/repo/RepoDetailViewModel.kt @@ -7,6 +7,7 @@ import com.delecrode.devhub.domain.repository.RepoRepository import com.delecrode.devhub.utils.Result import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { @@ -20,6 +21,7 @@ class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { isLoading = true, error = null ) + checkIfFavorite(repo) when (val result = repository.getRepoDetail(owner, repo)) { is Result.Success -> { _uiState.value = _uiState.value.copy( @@ -39,6 +41,18 @@ class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { } } + private fun checkIfFavorite(repoName: String) { + viewModelScope.launch { + try { + val favorites = repository.getAll().first() + val isFav = favorites.any { it.name == repoName } + _uiState.value = _uiState.value.copy(isFavorite = isFav) + } catch (e: Exception) { + _uiState.value = _uiState.value.copy(isFavorite = false) + } + } + } + fun getLanguagesForRepo(owner: String, repo: String) { viewModelScope.launch { when (val result = repository.getLanguagesRepo(owner, repo)) { @@ -69,7 +83,6 @@ class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { ) } - fun favoriteRepo(id: Int, owner: String, name: String, description: String, url: String) { viewModelScope.launch { repository.save( @@ -81,6 +94,14 @@ class RepoDetailViewModel(val repository: RepoRepository) : ViewModel() { url = url ) ) + _uiState.value = _uiState.value.copy(isFavorite = true) + } + } + + fun deleteRepo(id: Int) { + viewModelScope.launch { + repository.delete(id) + _uiState.value = _uiState.value.copy(isFavorite = false) } } } From 3fb7b89f20fdcde0ec7e0fba1f99cae6be28761f Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 15:53:26 -0300 Subject: [PATCH 08/10] =?UTF-8?q?Removendo=20imports=20n=C3=A3o=20utilizad?= =?UTF-8?q?os?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Removidos imports desnecessários em `ReposFavScreen` (Spacer, height, FontWeight, sp). - Removido import não utilizado em `RepoDao` (Delete). --- .../com/delecrode/devhub/data/local/database/dao/RepoDao.kt | 1 - .../java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt | 4 ---- 2 files changed, 5 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt b/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt index 41def3a..f8d78f3 100644 --- a/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt +++ b/app/src/main/java/com/delecrode/devhub/data/local/database/dao/RepoDao.kt @@ -1,7 +1,6 @@ package com.delecrode.devhub.data.local.database.dao import androidx.room.Dao -import androidx.room.Delete import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query diff --git a/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt index 3c5ae39..3b8fae4 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/favoritos/ReposFavScreen.kt @@ -5,10 +5,8 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -33,9 +31,7 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.compose.ui.zIndex import androidx.navigation.NavController import com.delecrode.devhub.navigation.AppDestinations From e08d2006eb893efd4181a7e6e2b4c2c51bd3c277 Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 16:40:05 -0300 Subject: [PATCH 09/10] =?UTF-8?q?Implementada=20l=C3=B3gica=20de=20recuper?= =?UTF-8?q?a=C3=A7=C3=A3o=20de=20senha=20e=20integra=C3=A7=C3=A3o=20com=20?= =?UTF-8?q?Firebase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adicionado `ForgotPasswordViewModel` e `ForgotPasswordState` para gerenciamento de estado e validação de email. - Implementado método `forgotPassword` no `AuthRepository` e na fonte de dados `FirebaseAuth` para envio de email de redefinição. - Atualizada `ForgotPasswordScreen` para integrar com o ViewModel, tratando estados de carregamento, erros e navegação de sucesso. - Configurada injeção de dependência do novo ViewModel no `AppModule` e `AppNavHost`. --- .../data/remote/firebase/FirebaseAuth.kt | 18 +++++ .../data/repository/AuthRepositoryImpl.kt | 10 +++ .../java/com/delecrode/devhub/di/AppModule.kt | 7 +- .../domain/repository/AuthRepository.kt | 3 +- .../delecrode/devhub/navigation/AppNavHost.kt | 5 +- .../devhub/ui/forgot/ForgotPasswordScreen.kt | 36 ++++++++-- .../devhub/ui/forgot/ForgotPasswordState.kt | 8 +++ .../ui/forgot/ForgotPasswordViewModel.kt | 69 +++++++++++++++++++ 8 files changed, 146 insertions(+), 10 deletions(-) create mode 100644 app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordState.kt create mode 100644 app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordViewModel.kt diff --git a/app/src/main/java/com/delecrode/devhub/data/remote/firebase/FirebaseAuth.kt b/app/src/main/java/com/delecrode/devhub/data/remote/firebase/FirebaseAuth.kt index e634003..56c26f5 100644 --- a/app/src/main/java/com/delecrode/devhub/data/remote/firebase/FirebaseAuth.kt +++ b/app/src/main/java/com/delecrode/devhub/data/remote/firebase/FirebaseAuth.kt @@ -1,5 +1,6 @@ package com.delecrode.devhub.data.remote.firebase +import android.util.Log import com.google.firebase.auth.FirebaseAuth import com.google.firebase.auth.FirebaseUser import kotlin.coroutines.resume @@ -43,4 +44,21 @@ class FirebaseAuth( fun signOut() { auth.signOut() } + + suspend fun forgotPassword(email: String) { + Log.i("ForgotPasswordScreen", "forgotPassword: $email") + suspendCoroutine { cont -> + auth.sendPasswordResetEmail(email) + .addOnCompleteListener { task -> + if (task.isSuccessful) { + cont.resume(Unit) + } else { + cont.resumeWithException( + task.exception ?: Exception("Erro ao enviar e-mail de recuperação") + ) + } + } + } + } + } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt index 9d42d50..4c1e6c6 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/AuthRepositoryImpl.kt @@ -59,6 +59,16 @@ class AuthRepositoryImpl( } } + override suspend fun forgotPassword(email: String): Result { + return try { + authDataSource.forgotPassword(email) + Result.Success(Unit) + + } catch (e: Exception) { + Result.Error(mapAuthError(e)) + } + } + override suspend fun signOut() { authDataSource.signOut() diff --git a/app/src/main/java/com/delecrode/devhub/di/AppModule.kt b/app/src/main/java/com/delecrode/devhub/di/AppModule.kt index cfd29d1..b8052cd 100644 --- a/app/src/main/java/com/delecrode/devhub/di/AppModule.kt +++ b/app/src/main/java/com/delecrode/devhub/di/AppModule.kt @@ -16,6 +16,7 @@ import com.delecrode.devhub.domain.repository.RepoRepository import com.delecrode.devhub.domain.repository.UserRepository import com.delecrode.devhub.domain.session.SessionViewModel import com.delecrode.devhub.ui.favoritos.RepoFavViewModel +import com.delecrode.devhub.ui.forgot.ForgotPasswordViewModel import com.delecrode.devhub.ui.home.HomeViewModel import com.delecrode.devhub.ui.login.AuthViewModel import com.delecrode.devhub.ui.profile.ProfileViewModel @@ -55,7 +56,7 @@ val appModule = module { single { UserExtraData(get()) } single { UserRepositoryImpl(get(), get(), get()) } - single { RepoRepositoryImpl(get(),get(),get()) } + single { RepoRepositoryImpl(get(), get(), get()) } single { AuthRepositoryImpl(get(), get(), get()) } viewModel { HomeViewModel(get(), get()) } @@ -63,7 +64,7 @@ val appModule = module { viewModel { AuthViewModel(get()) } viewModel { SessionViewModel(get()) } viewModel { RegisterViewModel(get()) } - viewModel { ProfileViewModel(get(),get()) } + viewModel { ProfileViewModel(get(), get()) } viewModel { RepoFavViewModel(get()) } - + viewModel { ForgotPasswordViewModel(get()) } } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt b/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt index c8aef41..56a2bb0 100644 --- a/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/delecrode/devhub/domain/repository/AuthRepository.kt @@ -14,6 +14,5 @@ interface AuthRepository { password: String ): Result suspend fun signOut() - - + suspend fun forgotPassword(email: String): Result } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/navigation/AppNavHost.kt b/app/src/main/java/com/delecrode/devhub/navigation/AppNavHost.kt index f81f9ee..5e9cb30 100644 --- a/app/src/main/java/com/delecrode/devhub/navigation/AppNavHost.kt +++ b/app/src/main/java/com/delecrode/devhub/navigation/AppNavHost.kt @@ -11,6 +11,7 @@ import com.delecrode.devhub.domain.session.SessionViewModel import com.delecrode.devhub.ui.favoritos.RepoFavViewModel import com.delecrode.devhub.ui.favoritos.ReposFavScreen import com.delecrode.devhub.ui.forgot.ForgotPasswordScreen +import com.delecrode.devhub.ui.forgot.ForgotPasswordViewModel import com.delecrode.devhub.ui.home.HomeScreen import com.delecrode.devhub.ui.home.HomeViewModel import com.delecrode.devhub.ui.login.AuthViewModel @@ -34,6 +35,8 @@ fun AppNavHost(sessionViewModel: SessionViewModel) { val registerViewModel: RegisterViewModel = koinViewModel() val profileViewModel: ProfileViewModel = koinViewModel() val repoFavViewModel: RepoFavViewModel = koinViewModel() + val forgotPasswordViewModel: ForgotPasswordViewModel = koinViewModel() + val logged = sessionViewModel.isLoggedIn.collectAsState() @@ -74,7 +77,7 @@ fun AppNavHost(sessionViewModel: SessionViewModel) { //Forgot Password Flow composable(AppDestinations.ForgotPassword.route) { - ForgotPasswordScreen(navController) + ForgotPasswordScreen(navController, forgotPasswordViewModel) } composable(AppDestinations.Profile.route) { diff --git a/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordScreen.kt index 5d790a7..572e5c1 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordScreen.kt @@ -1,5 +1,6 @@ package com.delecrode.devhub.ui.forgot +import android.widget.Toast import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -16,11 +17,14 @@ import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign @@ -31,13 +35,35 @@ import androidx.navigation.compose.rememberNavController import com.delecrode.devhub.navigation.AppDestinations import com.delecrode.devhub.ui.components.EmailTextField import com.delecrode.devhub.ui.components.PrimaryButton +import org.koin.androidx.compose.koinViewModel @OptIn(ExperimentalMaterial3Api::class) @Composable -fun ForgotPasswordScreen(navController: NavController) { +fun ForgotPasswordScreen(navController: NavController, viewModel: ForgotPasswordViewModel) { + + val state by viewModel.state.collectAsState() + val context = LocalContext.current var email by remember { mutableStateOf("") } + LaunchedEffect(state.success) { + if (state.success) { + navController.navigate(AppDestinations.Login.route){ + popUpTo(AppDestinations.Login.route){ + inclusive = true + } + } + Toast.makeText(context, "E-mail enviado com sucesso!", Toast.LENGTH_SHORT).show() + viewModel.clearState() + } + } + + LaunchedEffect(email) { + if (state.emailError != null) { + viewModel.clearEmailError() + } + } + Scaffold( topBar = { @@ -83,14 +109,16 @@ fun ForgotPasswordScreen(navController: NavController) { value = email, onValueChange = { email = it }, label = "Email", - imeAction = ImeAction.Done + imeAction = ImeAction.Done, + isError = state.emailError != null, + errorMessage = state.emailError ?: "" ) Spacer(modifier = Modifier.height(16.dp)) PrimaryButton( text = "Enviar e-mail", - onClick = { navController.navigate(AppDestinations.Login.route) }, + onClick = { viewModel.forgotPassword(email) }, enabled = email.isNotBlank() ) } @@ -101,5 +129,5 @@ fun ForgotPasswordScreen(navController: NavController) { @Preview @Composable fun ForgotPasswordScreenPreview() { - ForgotPasswordScreen(rememberNavController()) + ForgotPasswordScreen(rememberNavController(), koinViewModel()) } \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordState.kt b/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordState.kt new file mode 100644 index 0000000..9e34c09 --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordState.kt @@ -0,0 +1,8 @@ +package com.delecrode.devhub.ui.forgot + +data class ForgotPasswordState( + val isLoading: Boolean = false, + val error: String? = null, + val success: Boolean = false, + val emailError: String? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordViewModel.kt new file mode 100644 index 0000000..f1a6818 --- /dev/null +++ b/app/src/main/java/com/delecrode/devhub/ui/forgot/ForgotPasswordViewModel.kt @@ -0,0 +1,69 @@ +package com.delecrode.devhub.ui.forgot + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.delecrode.devhub.domain.repository.AuthRepository +import com.delecrode.devhub.utils.Result +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class ForgotPasswordViewModel(private val authRepository: AuthRepository) : ViewModel() { + + private val _state = MutableStateFlow(ForgotPasswordState()) + val state: StateFlow = _state + + fun forgotPassword(email: String) { + viewModelScope.launch { + val emailValidation = validateEmail(email) + if (emailValidation != null) { + _state.value = _state.value.copy( + emailError = emailValidation, + error = null, + isLoading = false + ) + return@launch + } + _state.value = _state.value.copy( + isLoading = true, + error = null + ) + when (val result = authRepository.forgotPassword(email)) { + is Result.Success -> { + _state.value = _state.value.copy( + isLoading = false, + success = true + ) + } + + is Result.Error -> { + _state.value = _state.value.copy( + isLoading = false, + error = result.message + ) + } + } + } + } + + fun clearState() { + _state.value = ForgotPasswordState() + } + + fun clearEmailError() { + _state.value = _state.value.copy(emailError = null) + } + + private fun validateEmail(email: String): String? { + return when { + email.isBlank() -> "Email é obrigatório" + !isValidEmailFormat(email) -> "Email inválido" + else -> null + } + } + + private fun isValidEmailFormat(email: String): Boolean { + return Regex("^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$").matches(email) + } + +} \ No newline at end of file From 1e19bd61eef3c2e7a5b540ff06599cc2e4becf9a Mon Sep 17 00:00:00 2001 From: guilh Date: Tue, 16 Dec 2025 16:57:08 -0300 Subject: [PATCH 10/10] =?UTF-8?q?FIX=20-=20Corre=C3=A7=C3=B5es=20na=20obte?= =?UTF-8?q?n=C3=A7=C3=A3o=20do=20usu=C3=A1rio=20Firebase=20e=20inicializa?= =?UTF-8?q?=C3=A7=C3=A3o=20do=20ProfileViewModel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Adicionada verificação `isNullOrBlank()` para o UID no `UserRepositoryImpl` para evitar erros de autenticação. - Removida inicialização automática de `getUserForFirebase()` no bloco `init` do `ProfileViewModel`. - Removida variável local `userName` não utilizada na `ProfileScreen`. --- .../delecrode/devhub/data/repository/UserRepositoryImpl.kt | 5 ++++- .../java/com/delecrode/devhub/ui/profile/ProfileScreen.kt | 1 - .../java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt | 3 --- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt b/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt index ff3c858..244a0b5 100644 --- a/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt +++ b/app/src/main/java/com/delecrode/devhub/data/repository/UserRepositoryImpl.kt @@ -40,7 +40,10 @@ class UserRepositoryImpl( override suspend fun getUserForFirebase(): Result { return try { val uid = authLocalDataSource.getUID().first() - ?: return Result.Error("Usuário não autenticado") + + if (uid.isNullOrBlank()) { + return Result.Error("Usuário não autenticado") + } val snapshot = userExtraData.getUser(uid) diff --git a/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileScreen.kt b/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileScreen.kt index 256cda3..767d740 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileScreen.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileScreen.kt @@ -64,7 +64,6 @@ fun ProfileScreen(navController: NavController, viewModel: ProfileViewModel) { val state = viewModel.uiState.collectAsState() val user = state.value.userForGit - val userName = state.value.userForFirebase.username val repos = state.value.repos LaunchedEffect(Unit) { diff --git a/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt b/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt index b6279ca..24f4925 100644 --- a/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt +++ b/app/src/main/java/com/delecrode/devhub/ui/profile/ProfileViewModel.kt @@ -19,9 +19,6 @@ class ProfileViewModel( private val _uiState = MutableStateFlow(ProfileState()) val uiState: StateFlow = _uiState - init { - getUserForFirebase() - } fun getUserForFirebase() { _uiState.value = _uiState.value.copy(