Skip to content

Commit

Permalink
Merge pull request #32 from canopas/megh/allow-editing-user-profile-i…
Browse files Browse the repository at this point in the history
…mage

Allow editing user profile image
  • Loading branch information
cp-megh-l authored Mar 11, 2024
2 parents 40a8f0f + 7951d7f commit 3d341f3
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 45 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ dependencies {
implementation(platform("com.google.firebase:firebase-bom:32.7.0"))
implementation("com.google.firebase:firebase-auth")
implementation("com.google.android.gms:play-services-auth:20.7.0")
implementation("com.google.firebase:firebase-storage")

// Crashlytics
implementation("com.google.firebase:firebase-crashlytics")
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@
android:name=".data.receiver.location.LocationUpdateReceiver"
android:exported="false"/>

<activity
android:name="com.canhub.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat"
/>
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ private fun EditProfileScreenContent(modifier: Modifier) {
dismissProfileChooser = {
viewModel.showProfileChooser(false)
},
state.showProfileChooser
state.showProfileChooser,
state.isImageUploadInProgress
)

Spacer(modifier = Modifier.height(35.dp))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.canopas.yourspace.ui.flow.settings.profile

import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.canopas.yourspace.data.models.user.ApiUser
Expand All @@ -10,12 +11,12 @@ import com.canopas.yourspace.data.service.auth.AuthService
import com.canopas.yourspace.data.utils.AppDispatcher
import com.canopas.yourspace.ui.navigation.AppDestinations
import com.canopas.yourspace.ui.navigation.AppNavigator
import com.google.firebase.storage.FirebaseStorage
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import timber.log.Timber
import java.io.File
import javax.inject.Inject

@HiltViewModel
Expand Down Expand Up @@ -45,7 +46,7 @@ class EditProfileViewModel @Inject constructor(
lastName = user?.last_name,
email = user?.email,
phone = user?.phone,
profileUrl = null,
profileUrl = user?.profile_image,
enablePhone = user?.auth_type != LOGIN_TYPE_PHONE,
enableEmail = user?.auth_type != LOGIN_TYPE_GOOGLE
)
Expand Down Expand Up @@ -102,9 +103,41 @@ class EditProfileViewModel @Inject constructor(
_state.value = _state.value.copy(showProfileChooser = show)
}

fun onProfileImageChanged(profileUrl: File?) {
_state.value = _state.value.copy(profileUrl = profileUrl?.path)
onChange()
fun onProfileImageChanged(profileUri: Uri?) {
profileUri?.let { uri ->
uploadProfileImage(uri)
} ?: run {
_state.value = _state.value.copy(profileUrl = null)
onChange()
}
}

private fun uploadProfileImage(uri: Uri) = viewModelScope.launch(appDispatcher.IO) {
try {
val storage = FirebaseStorage.getInstance()
val storageRef = storage.reference
val fileName = "IMG_${System.currentTimeMillis()}.jpg"
val imageRef = storageRef.child("profile_images/${user?.id}/$fileName")
val uploadTask = imageRef.putFile(uri)
uploadTask.addOnProgressListener {
_state.value = _state.value.copy(isImageUploadInProgress = true)
}.addOnSuccessListener {
imageRef.downloadUrl.addOnSuccessListener { uri ->
_state.value = _state.value.copy(
profileUrl = uri.toString(),
isImageUploadInProgress = false
)
onChange()
}
}.addOnFailureListener {
Timber.e(it, "Failed to upload profile image")
_state.value = _state.value.copy(profileUrl = null, isImageUploadInProgress = false)
onChange()
}
} catch (e: Exception) {
Timber.e(e, "Failed to upload profile image")
_state.emit(_state.value.copy(isImageUploadInProgress = false, error = e.message))
}
}

fun onFirstNameChanged(firstName: String) {
Expand Down Expand Up @@ -167,5 +200,6 @@ data class EditProfileState(
val lastName: String? = null,
val email: String? = null,
val phone: String? = null,
val profileUrl: String? = null
val profileUrl: String? = null,
val isImageUploadInProgress: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.canopas.yourspace.ui.flow.settings.profile.component

import android.graphics.Bitmap
import android.net.Uri
import android.provider.MediaStore
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.result.launch
Expand All @@ -18,17 +19,18 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material.icons.filled.KeyboardArrowRight
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -46,38 +48,32 @@ import com.canhub.cropper.CropImageContractOptions
import com.canhub.cropper.CropImageOptions
import com.canopas.yourspace.R
import com.canopas.yourspace.data.models.user.ApiUser
import com.canopas.yourspace.ui.component.AppProgressIndicator
import com.canopas.yourspace.ui.component.motionClickEvent
import com.canopas.yourspace.ui.flow.settings.ProfileImageView
import com.canopas.yourspace.ui.theme.AppTheme
import java.io.File
import java.io.FileOutputStream
import java.io.ByteArrayOutputStream

@Composable
fun UserProfileView(
modifier: Modifier,
profileUrl: String?,
onProfileChanged: (File?) -> Unit,
onProfileChanged: (Uri?) -> Unit,
onProfileImageClicked: () -> Unit,
dismissProfileChooser: () -> Unit,
showProfileChooser: Boolean = false
showProfileChooser: Boolean = false,
isImageUploading: Boolean = false
) {
val context = LocalContext.current
val scope = rememberCoroutineScope()

var imageUri by remember { mutableStateOf<Uri?>(null) }

val imageCropLauncher = rememberLauncherForActivityResult(contract = CropImageContract()) { result ->
result.uriContent?.let {
imageUri = it
val fileName = "IMG_${System.currentTimeMillis()}.jpg"
val file = File(context.cacheDir, "images/$fileName")
val inputStream = context.contentResolver.openInputStream(imageUri!!)

val outputStream = FileOutputStream(file)
inputStream?.copyTo(outputStream)
inputStream?.close()
outputStream.close()
onProfileChanged(file)
if (result.isSuccessful) {
result.uriContent?.let {
imageUri = it
onProfileChanged(imageUri)
}
}
}

Expand All @@ -92,19 +88,16 @@ fun UserProfileView(

val cameraLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.TakePicturePreview()) { bitmap ->

bitmap?.let {
val fileName = "IMG_${System.currentTimeMillis()}.jpg"
val file = File(context.cacheDir, "images/$fileName")
try {
val out = FileOutputStream(file)
it.compress(Bitmap.CompressFormat.JPEG, 100, out)
out.flush()
out.close()
} catch (e: Exception) {
e.printStackTrace()
}
onProfileChanged(file)
val bytes = ByteArrayOutputStream()
it.compress(Bitmap.CompressFormat.JPEG, 100, bytes)
val path: String = MediaStore.Images.Media.insertImage(
context.contentResolver,
it,
"Title",
null
)
onProfileChanged(Uri.parse(path))
}
}

Expand Down Expand Up @@ -135,22 +128,50 @@ fun UserProfileView(
painter = rememberAsyncImagePainter(
ImageRequest.Builder(LocalContext.current).data(data = setProfile).build()
),
modifier = Modifier.fillMaxSize()
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
.border(1.dp, AppTheme.colorScheme.textPrimary, CircleShape)
.background(AppTheme.colorScheme.containerHigh, CircleShape)
.padding(if (profileUrl == null) 32.dp else 0.dp),
contentScale = ContentScale.Crop,
contentDescription = "ProfileImage"
)

// Image(
// painter = painterResource(id = R.drawable.ic_edit_user_profile),
// contentDescription = null,
// modifier = Modifier
// .align(Alignment.BottomEnd)
// .size(32.dp)
// .motionClickEvent { onProfileImageClicked() }
// )
Box(
modifier = Modifier
.wrapContentSize()
.align(Alignment.BottomEnd)
.clip(CircleShape)
.border(1.dp, AppTheme.colorScheme.textPrimary, CircleShape)
.background(AppTheme.colorScheme.onPrimary, CircleShape)
) {
Icon(
Icons.Default.Edit,
contentDescription = null,
modifier = Modifier
.align(Alignment.Center)
.padding(8.dp)
.size(20.dp)
.motionClickEvent {
if (!isImageUploading) {
onProfileImageClicked()
}
}
)
}

if (isImageUploading) {
Box(
modifier = Modifier
.fillMaxSize()
.clip(CircleShape)
.background(AppTheme.colorScheme.onPrimary.copy(0.5f), CircleShape),
contentAlignment = Alignment.Center
) {
AppProgressIndicator()
}
}
}
}

Expand Down

0 comments on commit 3d341f3

Please sign in to comment.