feature: editar perfil

This commit is contained in:
erike 2024-01-13 20:35:14 -06:00
parent 458de335d4
commit 6b3a3b904c
12 changed files with 224 additions and 13 deletions

View File

@ -1,6 +1,7 @@
package com.isolaatti.profile.data.remote
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET
import retrofit2.http.POST
import retrofit2.http.Path
@ -14,4 +15,7 @@ interface ProfileApi {
@POST("EditProfile/SetProfilePhoto")
fun setProfileImage(@Query("imageId") imageId: String): Call<Void>
@POST("EditProfile/UpdateProfile")
fun updateProfile(@Body updateProfileDto: UpdateProfileDto): Call<UserProfileDto>
}

View File

@ -0,0 +1,6 @@
package com.isolaatti.profile.data.remote
data class UpdateProfileDto(
val newDescription: String,
val newUsername: String
)

View File

@ -3,6 +3,7 @@ package com.isolaatti.profile.data.repository
import android.util.Log
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.profile.data.remote.ProfileApi
import com.isolaatti.profile.data.remote.UpdateProfileDto
import com.isolaatti.profile.domain.ProfileRepository
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.utils.Resource
@ -45,4 +46,19 @@ class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileA
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
override fun updateProfile(newDisplayName: String, newDescription: String): Flow<Resource<Boolean>> = flow {
try {
val result = profileApi.updateProfile(UpdateProfileDto(newDescription, newDisplayName)).awaitResponse()
if(result.isSuccessful) {
emit(Resource.Success(true))
} else {
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
}
} catch (e: Exception) {
Log.e("ProfileRepositoryImpl", e.message.toString())
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
}

View File

@ -1,6 +1,7 @@
package com.isolaatti.profile.domain
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.profile.data.remote.UpdateProfileDto
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
@ -9,4 +10,6 @@ interface ProfileRepository {
fun getProfile(userId: Int): Flow<Resource<UserProfile>>
fun setProfileImage(image: Image): Flow<Resource<Boolean>>
fun updateProfile(newDisplayName: String, newDescription: String): Flow<Resource<Boolean>>
}

View File

@ -8,7 +8,7 @@ import java.io.Serializable
data class UserProfile(
override val userId: Int,
val name: String,
var name: String,
val email: String?,
val numberOfFollowers: Int,
val numberOfFollowing: Int,
@ -18,7 +18,7 @@ data class UserProfile(
var followingThisUser: Boolean,
val thisUserIsFollowingMe: Boolean,
val profileImageId: String?,
val descriptionText: String?,
var descriptionText: String?,
val descriptionAudioId: String?,
val descriptionAudio: Audio?
) : Ownable, Serializable {

View File

@ -8,9 +8,11 @@ import androidx.activity.result.contract.ActivityResultContract
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.ui.EditProfileActivity
class EditProfileContract : ActivityResultContract<Void?, UserProfile?>() {
override fun createIntent(context: Context, input: Void?): Intent {
return Intent(context, EditProfileActivity::class.java)
class EditProfileContract : ActivityResultContract<UserProfile, UserProfile?>() {
override fun createIntent(context: Context, input: UserProfile): Intent {
return Intent(context, EditProfileActivity::class.java).apply {
putExtra(EditProfileActivity.EXTRA_IN_USER_PROFILE, input)
}
}
override fun parseResult(resultCode: Int, intent: Intent?): UserProfile? {

View File

@ -1,9 +1,49 @@
package com.isolaatti.profile.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.profile.domain.ProfileRepository
import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class EditProfileViewModel @Inject constructor(): ViewModel() {
class EditProfileViewModel @Inject constructor(private val profileRepository: ProfileRepository): ViewModel() {
var displayName = ""
set(value) {
field = value
validate()
}
var description = ""
set(value) {
field = value
validate()
}
val isValid: MutableLiveData<Boolean> = MutableLiveData()
private fun validate() {
isValid.value = displayName.length in 1..20 && description.length <= 300
}
val updateResult: MutableLiveData<Resource<Boolean>> = MutableLiveData()
fun updateProfile() {
if(displayName.isEmpty() || description.isEmpty()) {
return
}
viewModelScope.launch {
profileRepository.updateProfile(displayName, description).onEach {
updateResult.postValue(it)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -139,4 +139,8 @@ class ProfileViewModel @Inject constructor(
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun setProfile(profile: UserProfile) {
_profile.value = profile
}
}

View File

@ -1,23 +1,93 @@
package com.isolaatti.profile.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.widget.doOnTextChanged
import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityEditProfileBinding
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.presentation.EditProfileViewModel
import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class EditProfileActivity : IsolaattiBaseActivity() {
private lateinit var binding: ActivityEditProfileBinding
private val viewModel: EditProfileViewModel by viewModels()
private var inputProfile: UserProfile? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEditProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
setupListeners()
setupObservers()
inputProfile = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.extras?.getSerializable(EXTRA_IN_USER_PROFILE, UserProfile::class.java)
} else {
intent.extras?.getSerializable(EXTRA_IN_USER_PROFILE) as UserProfile
}
if(inputProfile != null) {
binding.displayName.editText?.setText(inputProfile!!.name)
binding.description.editText?.setText(inputProfile!!.descriptionText)
}
}
private fun setupListeners() {
binding.toolbar.setNavigationOnClickListener {
finish()
}
binding.displayName.editText?.doOnTextChanged { text, _, _, _ ->
viewModel.displayName = text.toString()
}
binding.description.editText?.doOnTextChanged { text, _, _, _ ->
viewModel.description = text.toString()
}
binding.acceptButton.setOnClickListener {
viewModel.updateProfile()
}
}
private fun setupObservers() {
viewModel.isValid.observe(this) {
binding.acceptButton.isEnabled = it
}
viewModel.updateResult.observe(this) {
when(it) {
is Resource.Error -> {}
is Resource.Loading -> {}
is Resource.Success -> {
inputProfile?.apply {
name = viewModel.displayName
descriptionText = viewModel.description
}
if(inputProfile != null) {
val resultIntent = Intent().apply {
putExtra(EXTRA_OUT_USER_PROFILE, inputProfile)
}
setResult(RESULT_OK, resultIntent)
}
finish()
}
}
}
}
companion object {
const val EXTRA_OUT_USER_PROFILE = "user_profile"
const val EXTRA_OUT_USER_PROFILE = "out_user_profile"
const val EXTRA_IN_USER_PROFILE = "in_user_profile"
}
}

View File

@ -99,8 +99,10 @@ class ProfileMainFragment : Fragment() {
}
}
private val editProfile = registerForActivityResult(EditProfileContract()) {
private val editProfile = registerForActivityResult(EditProfileContract()) { updatedProfile ->
if(updatedProfile != null) {
viewModel.setProfile(updatedProfile)
}
}
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
@ -278,7 +280,8 @@ class ProfileMainFragment : Fragment() {
viewBinding.topAppBar.setOnMenuItemClickListener {
when(it.itemId) {
R.id.edit_profile -> {
editProfile.launch(null)
viewModel.profile.value?.let { profile -> editProfile.launch(profile) }
true
}
R.id.user_link_menu_item -> {

View File

@ -1,6 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="@string/edit_profile"
app:navigationIcon="@drawable/baseline_close_24"/>
</com.google.android.material.appbar.AppBarLayout>
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/displayName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/appBarLayout"
android:layout_marginTop="32dp"
android:layout_marginHorizontal="32dp"
android:hint="@string/display_name">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/displayName"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="16dp"
android:hint="@string/description">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="6"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/acceptButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="32dp"
android:layout_marginTop="16dp"
android:text="@string/accept"/>
</LinearLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -136,4 +136,5 @@
<string name="create_a_new_discussion">Create a new discussion</string>
<string name="edit_profile">Edit profile</string>
<string name="remove_image_message_confirmation">Remove image? This will not delete your image, but only unset it as profile image</string>
<string name="description">Description</string>
</resources>