Skip to content

Instantly share code, notes, and snippets.

@sajjadyousefnia
Created March 10, 2025 13:19
Show Gist options
  • Save sajjadyousefnia/ad9ddf09ff14b1588ba14c748089fa8a to your computer and use it in GitHub Desktop.
Save sajjadyousefnia/ad9ddf09ff14b1588ba14c748089fa8a to your computer and use it in GitHub Desktop.
package com.divadventure.divadventure.domain
import com.divadventure.divadventure.SignupResultWrapper
import com.divadventure.divadventure.data.AuthRepository
import com.divadventure.divadventure.data.Model.SignUpResponse
import com.divadventure.divadventure.data.Model.SignupRequest
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
class AuthUseCases @Inject constructor(
// val signInUseCase: SignInUseCase,
val signUpUseCase: SignUpUseCase,
// val forgotPasswordUseCase: ForgotPasswordUseCase,
// val isLoggedInUseCase: IsLoggedInUseCase,
// val verifyEmailUseCase: VerifyEmailUseCase,
// val getCurrentUserUseCase: GetCurrentUserUseCase,
// val logoutUseCase: LogoutUseCase
)
/*
class SignInUseCase @Inject constructor(private val authRepository: AuthRepository) {
suspend operator fun invoke(email: String, password: String): ResultWrapper<User> {
// Validate email and password if needed
if (!isValidEmail(email)) {
return Result.Error(Exception("Invalid email format"))
}
if (password.length < 6) {
return Result.Error(Exception("Password must be at least 6 characters long"))
}
return try {
authRepository.signIn(email, password)
} catch (e: Exception) {
Result.Error(e)
}
}
private fun isValidEmail(email: String): Boolean {
// You can add a better email validation logic if needed
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}
*/
class SignUpUseCase @Inject constructor(private val authRepository: AuthRepository) {
suspend operator fun invoke(signupRequest: SignupRequest): Flow<SignupResultWrapper<SignUpResponse>> =
flow {
// Validate email and password if needed
emit(SignupResultWrapper.Loading)
if (!isValidEmail(signupRequest.email)) {
Timber.d("Invalid email format")
SignupResultWrapper.Error("Invalid email format")
return@flow
}
if (signupRequest.password.length < 8) {
Timber.d("Password must be at least 6 characters long")
emit(SignupResultWrapper.Error("Password must be at least 6 characters long"))
return@flow
}
if (signupRequest.password != signupRequest.password_confirmation) {
Timber.d("Passwords do not match")
emit(SignupResultWrapper.Error(("Passwords do not match")))
return@flow
}
try {
CoroutineScope(
Dispatchers.IO
).launch {
CoroutineScope(Dispatchers.Main).launch {
val result: SignupResultWrapper<SignUpResponse> =
authRepository.signup(signupRequest)
when (result) {
is SignupResultWrapper.Error -> {
emit(
SignupResultWrapper.Error(
result.message
)
)
}
SignupResultWrapper.Loading -> {
emit(
SignupResultWrapper.Loading
)
}
is SignupResultWrapper.Success<*> -> {
emit(
SignupResultWrapper.Success(
result.data as SignUpResponse
)
)
}
}
}
}
// Emit Success state
// emit(ResultWrapper.Success(signupRequest))
} catch (e: Exception) {
SignupResultWrapper.Error("failed to sign up")
}
}
private fun isValidEmail(email: String): Boolean {
// You can add a better email validation logic if needed
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}
/*
class ForgotPasswordUseCase @Inject constructor(private val authRepository: AuthRepository) {
suspend operator fun invoke(email: String): Result<Unit> {
// Validate email if needed
if (!isValidEmail(email)) {
return Result.Error(Exception("Invalid email format"))
}
return try {
authRepository.forgotPassword(email)
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(e)
}
}
private fun isValidEmail(email: String): Boolean {
// You can add a better email validation logic if needed
return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches()
}
}
*/
/*
class IsLoggedInUseCase @Inject constructor(private val authRepository: AuthRepository) {
suspend operator fun invoke(): Boolean {
// Check if the user is logged in
return authRepository.isLoggedIn()
}
}*/
/*class VerifyEmailUseCase @Inject constructor(private val authRepository: AuthRepository) {
suspend operator fun invoke(): Result<Unit> {
// Verify the user email
return try {
authRepository.verifyEmail()
Result.Success(Unit)
} catch (e: Exception) {
Result.Error(e)
}
}
}*/
/*
class GetCurrentUserUseCase @Inject constructor(private val authRepository: AuthRepository) {
suspend operator fun invoke(): Result<User?> {
// Get the user
return try {
val user = authRepository.getCurrentUser()
Result.Success(user)
} catch (e:*/
// another file
package com.divadventure.divadventure.ViewModel
import androidx.lifecycle.viewModelScope
import com.divadventure.divadventure.BaseViewModel
import com.divadventure.divadventure.Intent.AuthIntent
import com.divadventure.divadventure.SignupResultWrapper
import com.divadventure.divadventure.State.AuthState
import com.divadventure.divadventure.data.AuthRepository
import com.divadventure.divadventure.data.Model.SignupRequest
import com.divadventure.divadventure.domain.AuthUseCases
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
class AuthViewModel @Inject constructor(
private val authUseCases: AuthUseCases, private val authRepository: AuthRepository
) : BaseViewModel<AuthIntent, AuthState>(AuthState()) {
override suspend fun handleIntent(intent: AuthIntent) {
when (intent) {
is AuthIntent.SignUp -> {
updateState(state.value.copy(isLoadingSignUp = true))
authUseCases.signUpUseCase(
SignupRequest(
email = intent.email,
password = intent.password,
password_confirmation = intent.password
)
).collectLatest { result ->
when (result) {
is SignupResultWrapper.Success -> {
viewModelScope.launch {
updateState(
state.value.copy(
isLoadingSignUp = false,
signupError = "",
isLogged = true
)
)
Timber.d("Signup successful: ${result.data}")
}
}
is SignupResultWrapper.Error -> {
viewModelScope.launch {
Timber.d("Error: ${result.message}")
updateState(
state.value.copy(
isLoadingSignUp = false,
isLogged = false,
signupError = result.message,
)
)
}
}
SignupResultWrapper.Loading -> {
viewModelScope.launch {
Timber.d("Loading...")
}
}
}
}
}
AuthIntent.CheckIfLoggedIn -> {}
AuthIntent.ClearError -> {}
AuthIntent.ClearNavigation -> {}
is AuthIntent.ForgotPassword -> {}
AuthIntent.GoToForgotPassword -> {}
AuthIntent.GoToLanding -> {}
AuthIntent.GoToSignIn -> {}
AuthIntent.GoToSignUp -> {}
AuthIntent.NavigateToEmailVerification -> {}
AuthIntent.NavigateToForgotPassword -> {}
AuthIntent.NavigateToLandingPage -> {}
AuthIntent.NavigateToMain -> {}
AuthIntent.NavigateToSignIn -> {}
AuthIntent.NavigateToSignUp -> {}
is AuthIntent.SignIn -> {}
AuthIntent.SignUpWithGoogle -> {
updateState(
state.value.copy(
isLogged = true
)
)
Timber.d("Signing up with Google...")
}
is AuthIntent.OnEmailChanged -> {
updateState(
state.value.copy(
email = intent.email
)
)
checkSignupCardColor()
}
is AuthIntent.OnPasswordChanged -> {
updateState(
state.value.copy(
password = intent.password
)
)
checkSignupCardColor()
}
is AuthIntent.OnPasswordConfirmationChanged -> {
updateState(
state.value.copy(
passwordConfirmation = intent.passwordConfirmation
)
)
checkSignupCardColor()
}
}
}
private fun checkSignupCardColor() {
val email = state.value.email
val password = state.value.password
val passwordConfirmation = state.value.passwordConfirmation
val isEmailNotEmpty = email.isNotEmpty()
val isPasswordNotEmpty = password.isNotEmpty()
val isEmailValid = isEmailValid(email)
val isPasswordAtLeast8Chars = isAtleast8Characters(password)
val doPasswordsMatch =
passwordsMatch(password = password, passwordConfirmation = passwordConfirmation)
val isValid =
isEmailNotEmpty && isPasswordNotEmpty && isEmailValid && isPasswordAtLeast8Chars && doPasswordsMatch
val issue = when {
!isEmailNotEmpty -> "Email can't be empty"
!isEmailValid -> "Email is not valid"
!isPasswordNotEmpty -> "Password can't be empty"
!isPasswordAtLeast8Chars -> "Password must have at least 8 characters"
!doPasswordsMatch -> "Passwords does not match"
else -> null
}
updateState(
state.value.copy(
rawDataAccepted = isValid,
formError = issue ?: ""
)
)
}
private fun passwordsMatch(password: String, passwordConfirmation: String): Boolean {
return password == passwordConfirmation
}
private fun isAtleast8Characters(password: String): Boolean {
return password.length >= 8
}
private fun isEmailValid(email: String): Boolean {
val emailRegex = """^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$""".toRegex()
return emailRegex.matches(email)
}
}
// another file
package com.divadventure.divadventure.data
import com.divadventure.divadventure.SignupResultWrapper
import com.divadventure.divadventure.data.Model.SignUpResponse
import com.divadventure.divadventure.data.Model.SignupRequest
import com.google.gson.Gson
import com.google.gson.JsonParseException
import okio.IOException
import retrofit2.HttpException
import timber.log.Timber
import javax.inject.Inject
class AuthRepository @Inject constructor(
private val authService: AuthService
) {
suspend fun signup(request: SignupRequest): SignupResultWrapper<SignUpResponse> {
Timber.d("Starting signup request: $request")
return try {
val response = authService.signup(request).execute()
Timber.d("Signup response: $response")
if (response.isSuccessful) {
val authResponse = response.body() as SignUpResponse?
// Extract the authResponse from response.body()
Timber.d("Signup successful: $authResponse")
if (authResponse != null) {
SignupResultWrapper.Success(authResponse)
} else {
Timber.w("Response body is null")
SignupResultWrapper.Error("Response body is null")
}
} else {
val errorBody = response.errorBody()?.string()
val message = if (errorBody != null) {
try {
val errorJson = Gson().fromJson(errorBody, Map::class.java)
Timber.w("Signup failed with error body: $errorJson")
errorJson["message"].toString() // Extract the message field
} catch (e: JsonParseException) {
Timber.e(e, "Invalid error response")
"Invalid error response: ${e.message}"
}
} else {
Timber.w("Signup failed with code: ${response.code()}")
"Signup failed with code: ${response.code()}"
}
SignupResultWrapper.Error(message)
}
} catch (httpException: HttpException) {
Timber.e(httpException, "HTTP exception occurred")
// Handle HTTP exceptions (4xx or 5xx status codes)
val message = httpException.message() ?: "HTTP Error"
SignupResultWrapper.Error("HTTP error: $message")
} catch (ioException: IOException) {
Timber.e(ioException, "Network exception occurred")
// Handle network issues (e.g., no internet connection)
SignupResultWrapper.Error("Network error: ${ioException.message}")
} catch (jsonParseException: JsonParseException) {
Timber.e(jsonParseException, "JSON parsing exception occurred")
// Handle JSON parsing issues
SignupResultWrapper.Error("JSON parsing error: ${jsonParseException.message}")
} catch (e: Exception) {
Timber.e(e, "An unexpected exception occurred")
// Handle other unexpected exceptions
SignupResultWrapper.Error("An unexpected error occurred: ${e.message}")
}
}
// Add other repository methods here...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment