Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions core/buildconfig/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ android {
"\"${properties.getProperty("FIRESTORE_DB_ID_DEBUG")}\""
)

buildConfigField(
"String",
"FIRESTORE_STORAGE_ID",
"\"${properties.getProperty("FIRESTORE_STORAGE_ID_DEBUG")}\""
)

buildConfigField(
"String",
"HTTPS_CALLABLE",
Expand All @@ -56,6 +62,12 @@ android {
"\"${properties.getProperty("FIRESTORE_DB_ID_RELEASE")}\""
)

buildConfigField(
"String",
"FIRESTORE_STORAGE_ID",
"\"${properties.getProperty("FIRESTORE_STORAGE_ID_RELEASE")}\""
)

buildConfigField(
"String",
"HTTPS_CALLABLE",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,7 @@ object LocalPropertyProvider {

const val firestoreDbId: String = BuildConfig.FIRESTORE_DB_ID

const val firebaseStorageId: String = BuildConfig.FIRESTORE_STORAGE_ID

const val httpsCallable: String = BuildConfig.HTTPS_CALLABLE
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ val Gray = Color(0xFFAAAAAA)
val White = Color(0xFFFFFFFF)

val PlayerBackground = Color(0xFF353535)
val OptionBackground = Color(0xFF2D2E2E)

val SignInButtonDarkBackground = Color(0xFF131314)
val SignInButtonLightStroke = Color(0xFF747775)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ private val DarkColorScheme = darkColorScheme(
surface = Black,
onSurface = White,
onSurfaceVariant = DarkGray,
onSecondary = Gray
onSecondary = Gray,
secondaryContainer = OptionBackground
)

private val LightColorScheme = lightColorScheme(
Expand All @@ -29,7 +30,8 @@ private val LightColorScheme = lightColorScheme(
surface = White,
onSurface = Black,
onSurfaceVariant = Gray,
onSecondary = DarkGray
onSecondary = DarkGray,
secondaryContainer = White
)

@Composable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ sealed interface UserInfoRoute : Route {
data class MyPicks(val uid: String) : UserInfoRoute

@Serializable
data class EditProfile(val userName: String) : UserInfoRoute
data class EditProfile(val userName: String, val userProfileImage: String?) : UserInfoRoute

@Serializable
data object EditNotification : UserInfoRoute
Expand Down
2 changes: 2 additions & 0 deletions data/firebase/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ dependencies {
androidTestImplementation(libs.bundles.test)

// Firebase
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.firestore.ktx)
implementation(libs.firebase.storage)
implementation(libs.geofire.android.common)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ sealed class FirebaseDocumentFields(val name: String) {
data object Uid: FirebaseDocumentFields("uid")
data object MyPicks: FirebaseDocumentFields("myPicks")
data object Name: FirebaseDocumentFields("name")
data object ProfileImage: FirebaseDocumentFields("profileImage")
data object Location: FirebaseDocumentFields("location")
data object GeoHash: FirebaseDocumentFields("geoHash")
data object CreatedUserName: FirebaseDocumentFields("createdBy.userName")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.squirtles.data.firebase

import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.storage.FirebaseStorage
import com.squirtles.core.buildconfig.LocalPropertyProvider
import dagger.Module
import dagger.Provides
Expand All @@ -17,4 +18,10 @@ object FirebaseModule {
fun provideFirebaseFirestore(): FirebaseFirestore {
return FirebaseFirestore.getInstance(LocalPropertyProvider.firestoreDbId)
}

@Provides
@Singleton
fun provideFirebaseStorage(): FirebaseStorage {
return FirebaseStorage.getInstance(LocalPropertyProvider.firebaseStorageId)
}
}
2 changes: 2 additions & 0 deletions data/user/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ dependencies {
implementation(libs.androidx.datastore.preferences)

// firebase
implementation(platform(libs.firebase.bom))
implementation(libs.firebase.firestore.ktx)
implementation(libs.firebase.auth.ktx)
implementation(libs.firebase.storage)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ interface FirebaseUserDataSource {
suspend fun createGoogleIdUser(uid: String, newUser: FirebaseUser): Result<FirebaseUser>
suspend fun updateUserName(uid: String, newUserName: String): Result<Boolean>
suspend fun deleteUser(uid: String): Result<Void>
suspend fun updateUserProfileImage(uid: String, imageData: ByteArray): Result<Boolean>
suspend fun deleteUserProfileImage(uid: String): Result<Boolean>
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.util.Log
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.firestore.toObject
import com.google.firebase.storage.FirebaseStorage
import com.squirtles.data.firebase.BaseFirebaseDataSource
import com.squirtles.data.firebase.FirebaseCollections
import com.squirtles.data.firebase.FirebaseDocumentFields
Expand All @@ -14,7 +15,8 @@ import javax.inject.Singleton

@Singleton
class FirebaseUserDataSourceImpl @Inject constructor(
private val db: FirebaseFirestore
private val db: FirebaseFirestore,
private val storage: FirebaseStorage
) : BaseFirebaseDataSource(db), FirebaseUserDataSource {

override suspend fun createGoogleIdUser(uid: String, newUser: FirebaseUser): Result<FirebaseUser> {
Expand Down Expand Up @@ -56,13 +58,76 @@ class FirebaseUserDataSourceImpl @Inject constructor(

override suspend fun deleteUser(uid: String): Result<Void> {
return runCatching {
val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow()
val profileImageUrl = userDocSnap.getString(FirebaseDocumentFields.ProfileImage.name)
profileImageUrl?.let { url ->
deleteImageFromStorage(url).runCatching {
}.onFailure { e ->
Log.w(TAG_LOG, "Failed to delete profile image from Storage for uid: $uid", e)
}
}

FirebaseAuth.getInstance().currentUser?.delete()?.await()
return deleteDocument(FirebaseCollections.Users, uid)
}.onFailure {
Log.e(TAG_LOG, "Failed to delete a user", it)
}
}

private suspend fun deleteImageFromStorage(imageUrl: String) {
try {
val storageRef = storage.getReferenceFromUrl(imageUrl)
storageRef.delete().await()
} catch (e: Exception) {
Log.w(TAG_LOG, "No image found in storage for url: $imageUrl", e)
}
}

override suspend fun updateUserProfileImage(uid: String, imageData: ByteArray): Result<Boolean> {
return runCatching {
val fileName = "profile_images/${uid}_${System.currentTimeMillis()}.jpg"
val storageRef = storage.reference.child(fileName)

storageRef.putBytes(imageData).await()
val downloadUrl = storageRef.downloadUrl.await().toString()
val userDocRef = db.collection(FirebaseCollections.Users.name).document(uid)

try {
var oldImageUrl: String? = null
db.runTransaction { transaction ->
val snapshot = transaction.get(userDocRef)
oldImageUrl = snapshot.getString(FirebaseDocumentFields.ProfileImage.name)
transaction.update(userDocRef, FirebaseDocumentFields.ProfileImage.name, downloadUrl)
}.await()

oldImageUrl?.let { url -> deleteImageFromStorage(url) }
} catch (dbError: Exception) {
storageRef.delete().await()
throw dbError
}

true
}.onFailure { e ->
Log.e(TAG_LOG, "Failed to update user profile image", e)
}
}

override suspend fun deleteUserProfileImage(uid: String): Result<Boolean> {
return runCatching {
val userDocSnap = fetchDocumentSnapshot(FirebaseCollections.Users, uid).getOrThrow()
val currentProfileImageUrl = userDocSnap.getString(FirebaseDocumentFields.ProfileImage.name)

currentProfileImageUrl?.let { deleteImageFromStorage(it) }
db.runTransaction { transaction ->
transaction.update(userDocSnap.reference, FirebaseDocumentFields.ProfileImage.name, null)
}

true
}.onFailure { e ->
Log.e(TAG_LOG, "Failed to delete user profile image", e)
}
}

companion object {
const val TAG_LOG = "FirebaseUserDataSourceImpl"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,12 @@ class FirebaseUserRepositoryImpl @Inject constructor(
override suspend fun deleteUser(uid: String): Result<Void> {
return userDataSource.deleteUser(uid)
}

override suspend fun updateUserProfileImage(uid: String, imageData: ByteArray): Result<Boolean> {
return userDataSource.updateUserProfileImage(uid, imageData)
}

override suspend fun deleteUserProfileImage(uid: String): Result<Boolean> {
return userDataSource.deleteUserProfileImage(uid)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.squirtles.data.user.di

import android.content.Context
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.storage.FirebaseStorage
import com.squirtles.data.user.FirebaseUserDataSource
import com.squirtles.data.user.FirebaseUserDataSourceImpl
import com.squirtles.domain.user.FirebaseUserRepository
Expand Down Expand Up @@ -37,6 +38,6 @@ object UserDiModule {

@Provides
@Singleton
fun provideFirebaseUserDataSource(db: FirebaseFirestore): FirebaseUserDataSource =
FirebaseUserDataSourceImpl(db)
fun provideFirebaseUserDataSource(db: FirebaseFirestore, storage: FirebaseStorage): FirebaseUserDataSource =
FirebaseUserDataSourceImpl(db, storage)
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ import com.squirtles.core.model.User

interface FirebaseUserRepository {
val currentUser: String?

// user
fun signOut()
suspend fun createGoogleIdUser(uid: String, email: String, userName: String?, userProfileImage: String?): Result<User>
suspend fun fetchUser(userId: String): Result<User>
suspend fun updateUserName(userId: String, newUserName: String): Result<Boolean>
suspend fun deleteUser(uid: String): Result<Void>
suspend fun updateUserProfileImage(uid: String, imageData: ByteArray): Result<Boolean>
suspend fun deleteUserProfileImage(uid: String): Result<Boolean>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.squirtles.domain.user.usecase

import com.squirtles.domain.user.FirebaseUserRepository
import javax.inject.Inject

class DeleteUserProfileImageUseCase @Inject constructor(
private val firebaseUserRepository: FirebaseUserRepository
) {
suspend operator fun invoke(userId: String) {
firebaseUserRepository.deleteUserProfileImage(userId)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.squirtles.domain.user.usecase

import com.squirtles.domain.user.FirebaseUserRepository
import javax.inject.Inject

class UpdateUserProfileImageUseCase @Inject constructor(
private val firebaseUserRepository: FirebaseUserRepository
) {
suspend operator fun invoke(userId: String, newImageData: ByteArray) {
firebaseUserRepository.updateUserProfileImage(userId, newImageData)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,10 @@ internal class MainNavigator(
)
}

fun navigateEditProfile(userName: String) {
fun navigateEditProfile(userName: String, userProfileImage: String?) {
navController.navigateEditProfile(
userName = userName,
userProfileImage = userProfileImage,
navOptions = navOptions { launchSingleTop = true }
)
}
Expand Down
Loading