Skip to content
Merged
10 changes: 10 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
id("com.google.gms.google-services")
}

android {
Expand Down Expand Up @@ -48,6 +49,15 @@ android {

dependencies {

//DataStore
implementation(libs.data.store)

//Firebase
implementation(platform("com.google.firebase:firebase-bom:34.6.0"))
implementation("com.google.firebase:firebase-analytics")
implementation("com.google.firebase:firebase-firestore")
implementation("com.google.firebase:firebase-auth")

//Koil
implementation("io.insert-koin:koin-android:3.5.6")
implementation("io.insert-koin:koin-androidx-compose:3.5.6")
Expand Down
29 changes: 29 additions & 0 deletions app/google-services.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"project_info": {
"project_number": "1059643006132",
"project_id": "devhub-4912b",
"storage_bucket": "devhub-4912b.firebasestorage.app"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:1059643006132:android:459aaa892b0f25fdf6cbe1",
"android_client_info": {
"package_name": "com.delecrode.devhub"
}
},
"oauth_client": [],
"api_key": [
{
"current_key": "AIzaSyCclTzmPM0pdKLYQQJESqx0ZHdXJ7-vxdM"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": []
}
}
}
],
"configuration_version": "1"
}
6 changes: 5 additions & 1 deletion app/src/main/java/com/delecrode/devhub/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,20 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import com.delecrode.devhub.domain.session.SessionViewModel
import com.delecrode.devhub.navigation.AppNavHost
import com.delecrode.devhub.ui.theme.DevHubTheme
import org.koin.androidx.compose.koinViewModel

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
DevHubTheme {
AppNavHost()
val sessionViewModel : SessionViewModel = koinViewModel()
AppNavHost(sessionViewModel)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.delecrode.devhub.data.local.dataStore

import kotlinx.coroutines.flow.Flow

interface AuthLocalDataSource {
fun getUID(): Flow<String?>
suspend fun saveUID(uid: String)

suspend fun clearUID()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.delecrode.devhub.data.local.dataStore

import android.content.Context
import android.util.Log
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
import androidx.datastore.preferences.preferencesDataStore
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map

private val Context.dataStore by preferencesDataStore("auth_prefs")

class AuthLocalDataSourceImpl(private val context: Context) : AuthLocalDataSource {

private object PreferencesKeys {
val UID_KEY = stringPreferencesKey("uid")
}

override fun getUID(): Flow<String?> =
context.dataStore.data.map { prefs ->
prefs[PreferencesKeys.UID_KEY]
}


override suspend fun saveUID(uid: String) {
Log.i("AuthLocalDataSourceImpl", "saveUser: $uid")
context.dataStore.edit { prefs ->
prefs[PreferencesKeys.UID_KEY] = uid
}
}

override suspend fun clearUID() {
context.dataStore.edit { prefs ->
prefs.remove(PreferencesKeys.UID_KEY)
}
}
}
19 changes: 15 additions & 4 deletions app/src/main/java/com/delecrode/devhub/data/mapper/UserMapper.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.delecrode.devhub.data.mapper

import com.delecrode.devhub.data.model.UserDto
import com.delecrode.devhub.domain.model.User
import com.delecrode.devhub.data.model.UserForFirebaseDto
import com.delecrode.devhub.data.model.UserForGitDto
import com.delecrode.devhub.domain.model.UserForFirebase
import com.delecrode.devhub.domain.model.UserForGit

fun UserDto.toUserDomain(): User {
return User(
fun UserForGitDto.toUserDomain(): UserForGit {
return UserForGit(
login = login,
avatar_url = avatar_url,
url = url ,
Expand All @@ -13,3 +15,12 @@ fun UserDto.toUserDomain(): User {
repos_url = repos_url
)
}


fun UserForFirebaseDto.toUserDomain(): UserForFirebase{
return UserForFirebase(
fullName = fullName,
username = username,
email = email
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.delecrode.devhub.data.model

data class UserDto(
data class UserForGitDto(
val login: String ?,
val id: Int?,
val node_id: String?,
Expand Down Expand Up @@ -35,3 +35,9 @@ data class UserDto(
val created_at: String?,
val updated_at: String?
)

data class UserForFirebaseDto(
val fullName: String = "",
val username: String = "",
val email: String = ""
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.delecrode.devhub.data.remote.firebase

import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.auth.FirebaseUser
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine

class FirebaseAuth(
private val auth: FirebaseAuth
) {

suspend fun signIn(email: String, password: String): FirebaseUser? {
return suspendCoroutine { cont ->
auth.signInWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
cont.resume(task.result?.user)
} else {
cont.resumeWithException(
task.exception ?: Exception("Erro desconhecido ao fazer login")
)
}
}
}
}

suspend fun signUp(email: String, password: String): String? {
return suspendCoroutine { cont ->
auth.createUserWithEmailAndPassword(email, password)
.addOnCompleteListener { task ->
if (task.isSuccessful) {
cont.resume(task.result?.user?.uid)
} else {
cont.resumeWithException(
task.exception ?: Exception("Erro desconhecido ao cadastrar")
)
}
}
}
}

fun signOut() {
auth.signOut()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.delecrode.devhub.data.remote.firebase

import com.delecrode.devhub.domain.model.RegisterUser
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.FirebaseFirestore
import kotlinx.coroutines.tasks.await

class UserExtraData(
private val firestore: FirebaseFirestore
) {

fun saveUserData(uid: String, user: RegisterUser) {
firestore.collection("users")
.document(uid)
.set(user)
}

suspend fun getUser(uid: String): DocumentSnapshot {
return firestore.collection("users")
.document(uid)
.get()
.await()
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.delecrode.devhub.data.remote
package com.delecrode.devhub.data.remote.webApi.instance

import com.delecrode.devhub.BuildConfig
import com.delecrode.devhub.data.remote.service.RepoApiService
import com.delecrode.devhub.data.remote.service.UserApiService
import com.delecrode.devhub.data.remote.webApi.service.RepoApiService
import com.delecrode.devhub.data.remote.webApi.service.UserApiService
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
Expand Down Expand Up @@ -36,4 +36,4 @@ object RetrofitInstance {
val repoApi: RepoApiService by lazy {
retrofit.create(RepoApiService::class.java)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.delecrode.devhub.data.remote.service
package com.delecrode.devhub.data.remote.webApi.service

import com.delecrode.devhub.data.model.LanguagesDto
import com.delecrode.devhub.data.model.RepoDetailDto
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.delecrode.devhub.data.remote.service
package com.delecrode.devhub.data.remote.webApi.service

import com.delecrode.devhub.data.model.ReposDto
import com.delecrode.devhub.data.model.UserDto
import com.delecrode.devhub.data.model.UserForGitDto
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Path

interface UserApiService {

@GET("users/{userName}")
suspend fun getUser(@Path("userName") userName: String): Response<UserDto>
suspend fun getUser(@Path("userName") userName: String): Response<UserForGitDto>

@GET("users/{userName}/repos")
suspend fun getReposForUser(@Path("userName") userName: String) : Response<List<ReposDto>>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.delecrode.devhub.data.repository

import com.delecrode.devhub.data.local.dataStore.AuthLocalDataSource
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.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

class AuthRepositoryImpl(
private val authDataSource: FirebaseAuth,
private val userExtraDataSource: UserExtraData,
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")
} 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)
}
}

override suspend fun signUp(
name: String,
username: String,
email: String,
password: String
): Boolean {
try {
val uid = authDataSource.signUp(email, password) ?: return false

val userData = RegisterUser(
fullName = name,
username = username,
email = email
)

userExtraDataSource.saveUserData(uid, userData)

return true
} 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)
}
}

override suspend fun signOut() {
authDataSource.signOut()
authLocalDataSource.clearUID()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package com.delecrode.devhub.data.repository

import com.delecrode.devhub.data.mapper.toLanguagesDomain
import com.delecrode.devhub.data.mapper.toRepoDetailDomain
import com.delecrode.devhub.data.remote.service.RepoApiService
import com.delecrode.devhub.data.remote.webApi.service.RepoApiService
import com.delecrode.devhub.domain.model.Languages
import com.delecrode.devhub.domain.model.RepoDetail
import com.delecrode.devhub.domain.repository.RepoRepository
Expand Down
Loading