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 package com.isolaatti.profile.data.remote
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
@ -14,4 +15,7 @@ interface ProfileApi {
@POST("EditProfile/SetProfilePhoto") @POST("EditProfile/SetProfilePhoto")
fun setProfileImage(@Query("imageId") imageId: String): Call<Void> 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 android.util.Log
import com.isolaatti.images.common.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.profile.data.remote.ProfileApi 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.ProfileRepository
import com.isolaatti.profile.domain.entity.UserProfile import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
@ -45,4 +46,19 @@ class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileA
emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) 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 package com.isolaatti.profile.domain
import com.isolaatti.images.common.domain.entity.Image 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.profile.domain.entity.UserProfile
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -9,4 +10,6 @@ interface ProfileRepository {
fun getProfile(userId: Int): Flow<Resource<UserProfile>> fun getProfile(userId: Int): Flow<Resource<UserProfile>>
fun setProfileImage(image: Image): Flow<Resource<Boolean>> 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( data class UserProfile(
override val userId: Int, override val userId: Int,
val name: String, var name: String,
val email: String?, val email: String?,
val numberOfFollowers: Int, val numberOfFollowers: Int,
val numberOfFollowing: Int, val numberOfFollowing: Int,
@ -18,7 +18,7 @@ data class UserProfile(
var followingThisUser: Boolean, var followingThisUser: Boolean,
val thisUserIsFollowingMe: Boolean, val thisUserIsFollowingMe: Boolean,
val profileImageId: String?, val profileImageId: String?,
val descriptionText: String?, var descriptionText: String?,
val descriptionAudioId: String?, val descriptionAudioId: String?,
val descriptionAudio: Audio? val descriptionAudio: Audio?
) : Ownable, Serializable { ) : 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.domain.entity.UserProfile
import com.isolaatti.profile.ui.EditProfileActivity import com.isolaatti.profile.ui.EditProfileActivity
class EditProfileContract : ActivityResultContract<Void?, UserProfile?>() { class EditProfileContract : ActivityResultContract<UserProfile, UserProfile?>() {
override fun createIntent(context: Context, input: Void?): Intent { override fun createIntent(context: Context, input: UserProfile): Intent {
return Intent(context, EditProfileActivity::class.java) return Intent(context, EditProfileActivity::class.java).apply {
putExtra(EditProfileActivity.EXTRA_IN_USER_PROFILE, input)
}
} }
override fun parseResult(resultCode: Int, intent: Intent?): UserProfile? { override fun parseResult(resultCode: Int, intent: Intent?): UserProfile? {

View File

@ -1,9 +1,49 @@
package com.isolaatti.profile.presentation package com.isolaatti.profile.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel 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 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 import javax.inject.Inject
@HiltViewModel @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) }.flowOn(Dispatchers.IO).launchIn(this)
} }
} }
fun setProfile(profile: UserProfile) {
_profile.value = profile
}
} }

View File

@ -1,23 +1,93 @@
package com.isolaatti.profile.ui package com.isolaatti.profile.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels
import androidx.core.widget.doOnTextChanged
import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityEditProfileBinding 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 import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
class EditProfileActivity : IsolaattiBaseActivity() { class EditProfileActivity : IsolaattiBaseActivity() {
private lateinit var binding: ActivityEditProfileBinding private lateinit var binding: ActivityEditProfileBinding
private val viewModel: EditProfileViewModel by viewModels()
private var inputProfile: UserProfile? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityEditProfileBinding.inflate(layoutInflater) binding = ActivityEditProfileBinding.inflate(layoutInflater)
setContentView(binding.root) 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 { 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 { private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
@ -278,7 +280,8 @@ class ProfileMainFragment : Fragment() {
viewBinding.topAppBar.setOnMenuItemClickListener { viewBinding.topAppBar.setOnMenuItemClickListener {
when(it.itemId) { when(it.itemId) {
R.id.edit_profile -> { R.id.edit_profile -> {
editProfile.launch(null) viewModel.profile.value?.let { profile -> editProfile.launch(profile) }
true true
} }
R.id.user_link_menu_item -> { R.id.user_link_menu_item -> {

View File

@ -1,6 +1,68 @@
<?xml version="1.0" encoding="utf-8"?> <?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_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="create_a_new_discussion">Create a new discussion</string>
<string name="edit_profile">Edit profile</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="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> </resources>