mirror of
https://github.com/shaulascr/ecommerce_serang.git
synced 2025-08-12 18:22:22 +00:00
add edit profile activity
This commit is contained in:
@ -29,6 +29,9 @@
|
|||||||
android:theme="@style/Theme.Ecommerce_serang"
|
android:theme="@style/Theme.Ecommerce_serang"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
<activity
|
||||||
|
android:name=".ui.profile.editprofile.EditProfileCustActivity"
|
||||||
|
android:exported="false" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.order.history.detailorder.DetailOrderStatusActivity"
|
android:name=".ui.order.history.detailorder.DetailOrderStatusActivity"
|
||||||
android:exported="false" />
|
android:exported="false" />
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
package com.alya.ecommerce_serang.data.api.response.customer.profile
|
||||||
|
|
||||||
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
|
data class EditProfileResponse(
|
||||||
|
|
||||||
|
@field:SerializedName("message")
|
||||||
|
val message: String
|
||||||
|
)
|
@ -41,6 +41,7 @@ import com.alya.ecommerce_serang.data.api.response.customer.product.ReviewProduc
|
|||||||
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.product.StoreResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.AddressResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.CreateAddressResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.ProfileResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
import com.alya.ecommerce_serang.data.api.response.order.AddEvidenceResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
import com.alya.ecommerce_serang.data.api.response.order.ComplaintResponse
|
||||||
@ -129,6 +130,17 @@ interface ApiService {
|
|||||||
@Part evidence: MultipartBody.Part
|
@Part evidence: MultipartBody.Part
|
||||||
): Response<AddEvidenceResponse>
|
): Response<AddEvidenceResponse>
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@PUT("profile/edit")
|
||||||
|
suspend fun editProfileCustomer(
|
||||||
|
@Part("username") username: RequestBody,
|
||||||
|
@Part("name") name: RequestBody,
|
||||||
|
@Part("phone") phone: RequestBody,
|
||||||
|
@Part("birth_date") birthDate: RequestBody,
|
||||||
|
@Part userimg: MultipartBody.Part,
|
||||||
|
@Part("email") email: RequestBody
|
||||||
|
): Response<EditProfileResponse>
|
||||||
|
|
||||||
@GET("order/{status}")
|
@GET("order/{status}")
|
||||||
suspend fun getOrderList(
|
suspend fun getOrderList(
|
||||||
@Path("status") status: String
|
@Path("status") status: String
|
||||||
|
@ -404,7 +404,7 @@ class OrderRepository(private val apiService: ApiService) {
|
|||||||
|
|
||||||
suspend fun confirmOrderCompleted(request: CompletedOrderRequest): Result<CompletedOrderResponse> {
|
suspend fun confirmOrderCompleted(request: CompletedOrderRequest): Result<CompletedOrderResponse> {
|
||||||
return try {
|
return try {
|
||||||
Log.d("OrderRepository", "Cinfroming order request completed: $request")
|
Log.d("OrderRepository", "Conforming order request completed: $request")
|
||||||
val response = apiService.confirmOrder(request)
|
val response = apiService.confirmOrder(request)
|
||||||
|
|
||||||
if(response.isSuccessful) {
|
if(response.isSuccessful) {
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
package com.alya.ecommerce_serang.data.repository
|
package com.alya.ecommerce_serang.data.repository
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
import com.alya.ecommerce_serang.data.api.dto.LoginRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
import com.alya.ecommerce_serang.data.api.dto.OtpRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
import com.alya.ecommerce_serang.data.api.dto.RegisterRequest
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.LoginResponse
|
||||||
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
import com.alya.ecommerce_serang.data.api.response.auth.OtpResponse
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
|
import com.alya.ecommerce_serang.utils.FileUtils
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
|
||||||
class UserRepository(private val apiService: ApiService) {
|
class UserRepository(private val apiService: ApiService) {
|
||||||
|
|
||||||
//post data without message/response
|
//post data without message/response
|
||||||
suspend fun requestOtpRep(email: String): OtpResponse {
|
suspend fun requestOtpRep(email: String): OtpResponse {
|
||||||
return apiService.getOTP(OtpRequest(email))
|
return apiService.getOTP(OtpRequest(email))
|
||||||
@ -56,6 +62,75 @@ class UserRepository(private val apiService: ApiService) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
suspend fun editProfileCust(
|
||||||
|
context: Context,
|
||||||
|
username: String,
|
||||||
|
name: String,
|
||||||
|
phone: String,
|
||||||
|
birthDate: String,
|
||||||
|
email: String,
|
||||||
|
imageUri: Uri?
|
||||||
|
): Result<EditProfileResponse> {
|
||||||
|
return try {
|
||||||
|
// Log the data being sent
|
||||||
|
Log.d(TAG, "Edit Profile - Username: $username, Name: $name, Phone: $phone, Birth Date: $birthDate, Email: $email")
|
||||||
|
Log.d(TAG, "Image URI: $imageUri")
|
||||||
|
|
||||||
|
// Create RequestBody objects for text fields
|
||||||
|
val usernameRequestBody = username.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val nameRequestBody = name.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val phoneRequestBody = phone.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val birthDateRequestBody = birthDate.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
val emailRequestBody = email.toRequestBody("text/plain".toMediaTypeOrNull())
|
||||||
|
|
||||||
|
// Create MultipartBody.Part for the image
|
||||||
|
val imagePart = if (imageUri != null) {
|
||||||
|
// Create a temporary file from the URI using the utility class
|
||||||
|
val imageFile = FileUtils.createTempFileFromUri(context, imageUri, "profile")
|
||||||
|
if (imageFile != null) {
|
||||||
|
// Create MultipartBody.Part from the file
|
||||||
|
FileUtils.createMultipartFromFile("userimg", imageFile)
|
||||||
|
} else {
|
||||||
|
// Fallback to empty part
|
||||||
|
FileUtils.createEmptyMultipart("userimg")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No image selected, use empty part
|
||||||
|
FileUtils.createEmptyMultipart("userimg")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the API call
|
||||||
|
val response = apiService.editProfileCustomer(
|
||||||
|
username = usernameRequestBody,
|
||||||
|
name = nameRequestBody,
|
||||||
|
phone = phoneRequestBody,
|
||||||
|
birthDate = birthDateRequestBody,
|
||||||
|
userimg = imagePart,
|
||||||
|
email = emailRequestBody
|
||||||
|
)
|
||||||
|
|
||||||
|
// Process the response
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val editResponse = response.body()
|
||||||
|
if (editResponse != null) {
|
||||||
|
Log.d(TAG, "Edit profile success: ${editResponse.message}")
|
||||||
|
Result.Success(editResponse)
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Response body is null")
|
||||||
|
Result.Error(Exception("Empty response from server"))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val errorBody = response.errorBody()?.string() ?: "Unknown Error"
|
||||||
|
Log.e(TAG, "Error editing profile: $errorBody")
|
||||||
|
Result.Error(Exception(errorBody))
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Exception in editProfileCust: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// suspend fun sendChatMessage(
|
// suspend fun sendChatMessage(
|
||||||
// storeId: Int,
|
// storeId: Int,
|
||||||
// message: String,
|
// message: String,
|
||||||
@ -152,6 +227,10 @@ class UserRepository(private val apiService: ApiService) {
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
companion object{
|
||||||
|
private const val TAG = "UserRepository"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,29 @@
|
|||||||
package com.alya.ecommerce_serang.ui.profile
|
package com.alya.ecommerce_serang.ui.profile
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
import com.alya.ecommerce_serang.R
|
import com.alya.ecommerce_serang.R
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import com.alya.ecommerce_serang.databinding.ActivityDetailProfileBinding
|
import com.alya.ecommerce_serang.databinding.ActivityDetailProfileBinding
|
||||||
|
import com.alya.ecommerce_serang.ui.profile.editprofile.EditProfileCustActivity
|
||||||
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
import com.alya.ecommerce_serang.utils.SessionManager
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
|
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.gson.Gson
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.TimeZone
|
import java.util.TimeZone
|
||||||
@ -24,6 +32,8 @@ class DetailProfileActivity : AppCompatActivity() {
|
|||||||
private lateinit var binding: ActivityDetailProfileBinding
|
private lateinit var binding: ActivityDetailProfileBinding
|
||||||
private lateinit var apiService: ApiService
|
private lateinit var apiService: ApiService
|
||||||
private lateinit var sessionManager: SessionManager
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private var currentUserProfile: UserProfile? = null
|
||||||
|
|
||||||
|
|
||||||
private val viewModel: ProfileViewModel by viewModels {
|
private val viewModel: ProfileViewModel by viewModels {
|
||||||
BaseViewModelFactory {
|
BaseViewModelFactory {
|
||||||
@ -33,6 +43,15 @@ class DetailProfileActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val editProfileLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.StartActivityForResult()
|
||||||
|
) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
// Refresh profile after edit
|
||||||
|
viewModel.loadUserProfile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
binding = ActivityDetailProfileBinding.inflate(layoutInflater)
|
binding = ActivityDetailProfileBinding.inflate(layoutInflater)
|
||||||
@ -42,24 +61,61 @@ class DetailProfileActivity : AppCompatActivity() {
|
|||||||
apiService = ApiConfig.getApiService(sessionManager)
|
apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
|
||||||
enableEdgeToEdge()
|
enableEdgeToEdge()
|
||||||
// ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
|
|
||||||
// val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
|
setupClickListeners()
|
||||||
// v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
|
|
||||||
// insets
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||||
// }
|
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
view.setPadding(
|
||||||
|
systemBars.left,
|
||||||
|
systemBars.top,
|
||||||
|
systemBars.right,
|
||||||
|
systemBars.bottom
|
||||||
|
)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
|
||||||
viewModel.loadUserProfile()
|
viewModel.loadUserProfile()
|
||||||
|
|
||||||
viewModel.userProfile.observe(this) { user ->
|
viewModel.userProfile.observe(this) { user ->
|
||||||
user?.let { updateProfile(it) }
|
Log.d("DetailProfileActivity", "Observed userProfile: $user")
|
||||||
|
user?.let {
|
||||||
|
updateProfile(it)
|
||||||
|
} ?: run {
|
||||||
|
Log.e("DetailProfileActivity", "Received null user profile from ViewModel")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.errorMessage.observe(this) { error ->
|
viewModel.errorMessage.observe(this) { error ->
|
||||||
|
Log.e("DetailProfileActivity", "Error from ViewModel: $error")
|
||||||
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, error, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners() {
|
||||||
|
binding.btnBack.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnUbahProfil.setOnClickListener {
|
||||||
|
currentUserProfile?.let { profile ->
|
||||||
|
val gson = Gson()
|
||||||
|
val userProfileJson = gson.toJson(currentUserProfile)
|
||||||
|
val intent = Intent(this, EditProfileCustActivity::class.java).apply {
|
||||||
|
putExtra("user_profile_json", userProfileJson)
|
||||||
|
}
|
||||||
|
editProfileLauncher.launch(intent)
|
||||||
|
} ?: run {
|
||||||
|
Toast.makeText(this, "Profile data is not available", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateProfile(user: UserProfile) {
|
private fun updateProfile(user: UserProfile) {
|
||||||
|
Log.d("DetailProfileActivity", "updateProfile called with user: $user")
|
||||||
|
|
||||||
|
// Store the user profile for later use
|
||||||
|
currentUserProfile = user
|
||||||
|
|
||||||
binding.tvNameUser.setText(user.name.toString())
|
binding.tvNameUser.setText(user.name.toString())
|
||||||
binding.tvUsername.setText(user.username)
|
binding.tvUsername.setText(user.username)
|
||||||
@ -69,9 +125,16 @@ class DetailProfileActivity : AppCompatActivity() {
|
|||||||
Log.d("ProfileActivity", "Formatted Birth Date: ${formatDate(user.birthDate)}")
|
Log.d("ProfileActivity", "Formatted Birth Date: ${formatDate(user.birthDate)}")
|
||||||
binding.tvNumberPhoneUser.setText(user.phone)
|
binding.tvNumberPhoneUser.setText(user.phone)
|
||||||
|
|
||||||
if (user.image != null && user.image is String) {
|
val fullImageUrl = when (val img = user.image) {
|
||||||
|
is String -> {
|
||||||
|
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||||
|
}
|
||||||
|
else -> R.drawable.placeholder_image // Default image for null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fullImageUrl != null && fullImageUrl is String) {
|
||||||
Glide.with(this)
|
Glide.with(this)
|
||||||
.load(user.image)
|
.load(fullImageUrl)
|
||||||
.placeholder(R.drawable.baseline_account_circle_24)
|
.placeholder(R.drawable.baseline_account_circle_24)
|
||||||
.into(binding.profileImage)
|
.into(binding.profileImage)
|
||||||
}
|
}
|
||||||
@ -93,4 +156,10 @@ class DetailProfileActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
// Refresh profile data when returning to this screen
|
||||||
|
viewModel.loadUserProfile()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,390 @@
|
|||||||
|
package com.alya.ecommerce_serang.ui.profile.editprofile
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.DatePickerDialog
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import com.alya.ecommerce_serang.BuildConfig.BASE_URL
|
||||||
|
import com.alya.ecommerce_serang.R
|
||||||
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiConfig
|
||||||
|
import com.alya.ecommerce_serang.data.api.retrofit.ApiService
|
||||||
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
|
import com.alya.ecommerce_serang.databinding.ActivityEditProfileCustBinding
|
||||||
|
import com.alya.ecommerce_serang.utils.BaseViewModelFactory
|
||||||
|
import com.alya.ecommerce_serang.utils.SessionManager
|
||||||
|
import com.alya.ecommerce_serang.utils.viewmodel.ProfileViewModel
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
|
import com.google.gson.Gson
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TimeZone
|
||||||
|
|
||||||
|
class EditProfileCustActivity : AppCompatActivity() {
|
||||||
|
private lateinit var binding: ActivityEditProfileCustBinding
|
||||||
|
private lateinit var apiService: ApiService
|
||||||
|
private lateinit var sessionManager: SessionManager
|
||||||
|
private var selectedImageUri: Uri? = null
|
||||||
|
|
||||||
|
private val viewModel: ProfileViewModel by viewModels {
|
||||||
|
BaseViewModelFactory {
|
||||||
|
val apiService = ApiConfig.getApiService(sessionManager)
|
||||||
|
val userRepository = UserRepository(apiService)
|
||||||
|
ProfileViewModel(userRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode == Activity.RESULT_OK) {
|
||||||
|
val data: Intent? = result.data
|
||||||
|
data?.data?.let {
|
||||||
|
selectedImageUri = it
|
||||||
|
|
||||||
|
val fullImageUrl = when (val img = selectedImageUri.toString()) {
|
||||||
|
is String -> {
|
||||||
|
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||||
|
}
|
||||||
|
else -> R.drawable.placeholder_image // Default image for null
|
||||||
|
}
|
||||||
|
|
||||||
|
Glide.with(this)
|
||||||
|
.load(fullImageUrl)
|
||||||
|
.into(binding.profileImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
binding = ActivityEditProfileCustBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
sessionManager = SessionManager(this)
|
||||||
|
|
||||||
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||||
|
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
// Apply insets to your root layout
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.root) { view, windowInsets ->
|
||||||
|
val systemBars = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
view.setPadding(
|
||||||
|
systemBars.left,
|
||||||
|
systemBars.top,
|
||||||
|
systemBars.right,
|
||||||
|
systemBars.bottom
|
||||||
|
)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
|
||||||
|
val userProfileJson = intent.getStringExtra("user_profile_json")
|
||||||
|
val userProfile = if (userProfileJson != null) {
|
||||||
|
val gson = Gson()
|
||||||
|
gson.fromJson(userProfileJson, UserProfile::class.java)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
userProfile?.let {
|
||||||
|
populateFields(it)
|
||||||
|
|
||||||
|
setupClickListeners()
|
||||||
|
observeViewModel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun populateFields(profile: UserProfile) {
|
||||||
|
binding.etNameUser.setText(profile.name)
|
||||||
|
binding.etUsername.setText(profile.username)
|
||||||
|
binding.etEmailUser.setText(profile.email)
|
||||||
|
binding.etNumberPhoneUser.setText(profile.phone)
|
||||||
|
|
||||||
|
// Format birth date for display
|
||||||
|
profile.birthDate?.let {
|
||||||
|
binding.etDateBirth.setText(formatDate(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
val fullImageUrl = when (val img = profile.image) {
|
||||||
|
is String -> {
|
||||||
|
if (img.startsWith("/")) BASE_URL + img.substring(1) else img
|
||||||
|
}
|
||||||
|
else -> R.drawable.placeholder_image // Default image for null
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load profile image
|
||||||
|
if (fullImageUrl != null && fullImageUrl is String) {
|
||||||
|
Glide.with(this)
|
||||||
|
.load(fullImageUrl)
|
||||||
|
.placeholder(R.drawable.baseline_account_circle_24)
|
||||||
|
.into(binding.profileImage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupClickListeners() {
|
||||||
|
binding.btnBack.setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.editIcon.setOnClickListener {
|
||||||
|
openImagePicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.tvSelectImage.setOnClickListener {
|
||||||
|
openImagePicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.etDateBirth.setOnClickListener {
|
||||||
|
showDatePicker()
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.btnSave.setOnClickListener {
|
||||||
|
saveProfile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun openImagePicker() {
|
||||||
|
// Check for permission first
|
||||||
|
if (ContextCompat.checkSelfPermission(
|
||||||
|
this,
|
||||||
|
android.Manifest.permission.READ_EXTERNAL_STORAGE
|
||||||
|
) != PackageManager.PERMISSION_GRANTED
|
||||||
|
) {
|
||||||
|
ActivityCompat.requestPermissions(
|
||||||
|
this,
|
||||||
|
arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
|
||||||
|
REQUEST_STORAGE_PERMISSION
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
launchImagePicker()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchImagePicker() {
|
||||||
|
val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI)
|
||||||
|
getContent.launch(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showDatePicker() {
|
||||||
|
val calendar = Calendar.getInstance()
|
||||||
|
|
||||||
|
// If there's already a date in the field, parse it
|
||||||
|
val dateText = binding.etDateBirth.text.toString()
|
||||||
|
if (dateText.isNotEmpty() && dateText != "N/A" && dateText != "Invalid Date") {
|
||||||
|
try {
|
||||||
|
val displayFormat = SimpleDateFormat("dd-MM-yy", Locale.getDefault())
|
||||||
|
val date = displayFormat.parse(dateText)
|
||||||
|
date?.let {
|
||||||
|
calendar.time = it
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error parsing date: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val year = calendar.get(Calendar.YEAR)
|
||||||
|
val month = calendar.get(Calendar.MONTH)
|
||||||
|
val day = calendar.get(Calendar.DAY_OF_MONTH)
|
||||||
|
|
||||||
|
val datePickerDialog = DatePickerDialog(
|
||||||
|
this,
|
||||||
|
{ _, selectedYear, selectedMonth, selectedDay ->
|
||||||
|
calendar.set(selectedYear, selectedMonth, selectedDay)
|
||||||
|
val displayFormat = SimpleDateFormat("dd-MM-yy", Locale.getDefault())
|
||||||
|
val formattedDate = displayFormat.format(calendar.time)
|
||||||
|
binding.etDateBirth.setText(formattedDate)
|
||||||
|
},
|
||||||
|
year, month, day
|
||||||
|
)
|
||||||
|
datePickerDialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveProfile() {
|
||||||
|
val name = binding.etNameUser.text.toString()
|
||||||
|
val username = binding.etUsername.text.toString()
|
||||||
|
val email = binding.etEmailUser.text.toString()
|
||||||
|
val phone = binding.etNumberPhoneUser.text.toString()
|
||||||
|
val displayDate = binding.etDateBirth.text.toString()
|
||||||
|
|
||||||
|
if (name.isEmpty() || username.isEmpty() || email.isEmpty() || phone.isEmpty() || displayDate.isEmpty()) {
|
||||||
|
Toast.makeText(this, "Semua field harus diisi", Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert date to server format
|
||||||
|
val serverBirthDate = convertToServerDateFormat(displayDate)
|
||||||
|
|
||||||
|
Log.d(TAG, "Starting profile save with direct method")
|
||||||
|
Log.d(TAG, "Selected image URI: $selectedImageUri")
|
||||||
|
|
||||||
|
// Disable the button to prevent multiple clicks
|
||||||
|
binding.btnSave.isEnabled = false
|
||||||
|
|
||||||
|
// Call the repository method via ViewModel
|
||||||
|
viewModel.editProfileDirect(
|
||||||
|
context = this, // Pass context for file operations
|
||||||
|
username = username,
|
||||||
|
name = name,
|
||||||
|
phone = phone,
|
||||||
|
birthDate = serverBirthDate,
|
||||||
|
email = email,
|
||||||
|
imageUri = selectedImageUri
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRealPathFromURI(uri: Uri): String? {
|
||||||
|
Log.d(TAG, "Getting real path from URI: $uri")
|
||||||
|
|
||||||
|
// Handle different URI schemes
|
||||||
|
when {
|
||||||
|
// File URI
|
||||||
|
uri.scheme == "file" -> {
|
||||||
|
val path = uri.path
|
||||||
|
Log.d(TAG, "URI is file scheme, path: $path")
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content URI
|
||||||
|
uri.scheme == "content" -> {
|
||||||
|
try {
|
||||||
|
val projection = arrayOf(MediaStore.Images.Media.DATA)
|
||||||
|
contentResolver.query(uri, projection, null, null, null)?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
val columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA)
|
||||||
|
val path = cursor.getString(columnIndex)
|
||||||
|
Log.d(TAG, "Found path from content URI: $path")
|
||||||
|
return path
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Cursor is empty")
|
||||||
|
}
|
||||||
|
} ?: Log.e(TAG, "Cursor is null")
|
||||||
|
|
||||||
|
// If the above fails, try the documented API way
|
||||||
|
contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
// Create a temp file
|
||||||
|
val fileName = getFileName(uri) ?: "temp_img_${System.currentTimeMillis()}.jpg"
|
||||||
|
val tempFile = File(cacheDir, fileName)
|
||||||
|
tempFile.outputStream().use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Created temporary file: ${tempFile.absolutePath}")
|
||||||
|
return tempFile.absolutePath
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error getting real path: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.e(TAG, "Could not get real path for URI: $uri")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFileName(uri: Uri): String? {
|
||||||
|
var result: String? = null
|
||||||
|
if (uri.scheme == "content") {
|
||||||
|
contentResolver.query(uri, null, null, null, null)?.use { cursor ->
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
val columnIndex = cursor.getColumnIndex(MediaStore.Images.Media.DISPLAY_NAME)
|
||||||
|
if (columnIndex >= 0) {
|
||||||
|
result = cursor.getString(columnIndex)
|
||||||
|
Log.d(TAG, "Found filename from content URI: $result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.path
|
||||||
|
val cut = result?.lastIndexOf('/') ?: -1
|
||||||
|
if (cut != -1) {
|
||||||
|
result = result?.substring(cut + 1)
|
||||||
|
}
|
||||||
|
Log.d(TAG, "Extracted filename from path: $result")
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(dateString: String?): String {
|
||||||
|
if (dateString.isNullOrEmpty()) return "N/A"
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val inputFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault())
|
||||||
|
inputFormat.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
|
val outputFormat = SimpleDateFormat("dd-MM-yy", Locale.getDefault())
|
||||||
|
val date = inputFormat.parse(dateString)
|
||||||
|
outputFormat.format(date ?: return "Invalid Date")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("ERROR", "Date parsing error: ${e.message}")
|
||||||
|
"Invalid Date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertToServerDateFormat(displayDate: String): String {
|
||||||
|
return try {
|
||||||
|
val displayFormat = SimpleDateFormat("dd-MM-yy", Locale.getDefault())
|
||||||
|
val date = displayFormat.parse(displayDate) ?: return ""
|
||||||
|
|
||||||
|
val serverFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||||
|
serverFormat.format(date)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error converting date format: ${e.message}")
|
||||||
|
""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun observeViewModel() {
|
||||||
|
viewModel.editProfileResult.observe(this) { result ->
|
||||||
|
when (result) {
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Loading -> {
|
||||||
|
// Show loading indicator
|
||||||
|
binding.btnSave.isEnabled = false
|
||||||
|
}
|
||||||
|
is com.alya.ecommerce_serang.data.repository.Result.Success -> {
|
||||||
|
// Show success message
|
||||||
|
Toast.makeText(this, result.data.message, Toast.LENGTH_SHORT).show()
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
is Result.Error -> {
|
||||||
|
// Show error message
|
||||||
|
Toast.makeText(this, result.exception.message ?: "Error updating profile", Toast.LENGTH_SHORT).show()
|
||||||
|
binding.btnSave.isEnabled = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRequestPermissionsResult(
|
||||||
|
requestCode: Int,
|
||||||
|
permissions: Array<out String>,
|
||||||
|
grantResults: IntArray
|
||||||
|
) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
|
if (requestCode == REQUEST_STORAGE_PERMISSION && grantResults.isNotEmpty() && grantResults[0] == android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||||
|
launchImagePicker()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(this, "Permission needed to select image", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val REQUEST_STORAGE_PERMISSION = 100
|
||||||
|
private const val TAG = "EditProfileCustActivity"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,88 @@
|
|||||||
|
package com.alya.ecommerce_serang.utils
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import android.webkit.MimeTypeMap
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
|
||||||
|
object FileUtils {
|
||||||
|
private const val TAG = "FileUtils"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a temporary file from a URI in the app's cache directory
|
||||||
|
*/
|
||||||
|
fun createTempFileFromUri(context: Context, uri: Uri, prefix: String = "temp"): File? {
|
||||||
|
try {
|
||||||
|
val fileExtension = getFileExtension(context, uri)
|
||||||
|
val fileName = "${prefix}_${System.currentTimeMillis()}.$fileExtension"
|
||||||
|
val tempFile = File(context.cacheDir, fileName)
|
||||||
|
|
||||||
|
context.contentResolver.openInputStream(uri)?.use { inputStream ->
|
||||||
|
FileOutputStream(tempFile).use { outputStream ->
|
||||||
|
inputStream.copyTo(outputStream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (tempFile.exists() && tempFile.length() > 0) {
|
||||||
|
Log.d(TAG, "Created temp file: ${tempFile.absolutePath}, size: ${tempFile.length()} bytes")
|
||||||
|
tempFile
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Created file is empty or doesn't exist")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error creating temp file: ${e.message}", e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the file extension from a URI using ContentResolver
|
||||||
|
*/
|
||||||
|
fun getFileExtension(context: Context, uri: Uri): String {
|
||||||
|
val mimeType = context.contentResolver.getType(uri)
|
||||||
|
return if (mimeType != null) {
|
||||||
|
MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
|
||||||
|
} else {
|
||||||
|
// Try to extract from the URI path
|
||||||
|
val path = uri.toString()
|
||||||
|
if (path.contains(".")) {
|
||||||
|
path.substring(path.lastIndexOf(".") + 1)
|
||||||
|
} else {
|
||||||
|
"jpg" // Default extension
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a MultipartBody.Part from a File for API requests
|
||||||
|
*/
|
||||||
|
fun createMultipartFromFile(paramName: String, file: File): MultipartBody.Part {
|
||||||
|
val requestFile = file.asRequestBody(getMimeType(file).toMediaTypeOrNull())
|
||||||
|
return MultipartBody.Part.createFormData(paramName, file.name, requestFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty MultipartBody.Part
|
||||||
|
*/
|
||||||
|
fun createEmptyMultipart(paramName: String): MultipartBody.Part {
|
||||||
|
return MultipartBody.Part.createFormData(paramName, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the MIME type for a file based on its extension
|
||||||
|
*/
|
||||||
|
fun getMimeType(file: File): String {
|
||||||
|
return when (file.extension.lowercase()) {
|
||||||
|
"jpg", "jpeg" -> "image/jpeg"
|
||||||
|
"png" -> "image/png"
|
||||||
|
"pdf" -> "application/pdf"
|
||||||
|
else -> "application/octet-stream"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
package com.alya.ecommerce_serang.utils.viewmodel
|
package com.alya.ecommerce_serang.utils.viewmodel
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
import com.alya.ecommerce_serang.data.api.dto.UserProfile
|
||||||
|
import com.alya.ecommerce_serang.data.api.response.customer.profile.EditProfileResponse
|
||||||
import com.alya.ecommerce_serang.data.repository.Result
|
import com.alya.ecommerce_serang.data.repository.Result
|
||||||
import com.alya.ecommerce_serang.data.repository.UserRepository
|
import com.alya.ecommerce_serang.data.repository.UserRepository
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -16,6 +20,9 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
|
|||||||
private val _errorMessage = MutableLiveData<String>()
|
private val _errorMessage = MutableLiveData<String>()
|
||||||
val errorMessage : LiveData<String> = _errorMessage
|
val errorMessage : LiveData<String> = _errorMessage
|
||||||
|
|
||||||
|
private val _editProfileResult = MutableLiveData<Result<EditProfileResponse>>()
|
||||||
|
val editProfileResult: LiveData<Result<EditProfileResponse>> = _editProfileResult
|
||||||
|
|
||||||
fun loadUserProfile(){
|
fun loadUserProfile(){
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
when (val result = userRepository.fetchUserProfile()){
|
when (val result = userRepository.fetchUserProfile()){
|
||||||
@ -25,4 +32,46 @@ class ProfileViewModel(private val userRepository: UserRepository) : ViewModel()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun editProfileDirect(
|
||||||
|
context: Context,
|
||||||
|
username: String,
|
||||||
|
name: String,
|
||||||
|
phone: String,
|
||||||
|
birthDate: String,
|
||||||
|
email: String,
|
||||||
|
imageUri: Uri?
|
||||||
|
) {
|
||||||
|
_editProfileResult.value = Result.Loading
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
Log.d(TAG, "Calling editProfileCust with direct parameters")
|
||||||
|
val result = userRepository.editProfileCust(
|
||||||
|
context = context,
|
||||||
|
username = username,
|
||||||
|
name = name,
|
||||||
|
phone = phone,
|
||||||
|
birthDate = birthDate,
|
||||||
|
email = email,
|
||||||
|
imageUri = imageUri
|
||||||
|
)
|
||||||
|
|
||||||
|
_editProfileResult.value = result
|
||||||
|
|
||||||
|
// Reload user profile after successful update
|
||||||
|
if (result is Result.Success) {
|
||||||
|
Log.d(TAG, "Edit profile successful, reloading profile data")
|
||||||
|
loadUserProfile()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Error in editProfileDirect: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
_editProfileResult.value = Result.Error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "ProfileViewModel"
|
||||||
|
}
|
||||||
}
|
}
|
198
app/src/main/res/layout/activity_edit_profile_cust.xml
Normal file
198
app/src/main/res/layout/activity_edit_profile_cust.xml
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/main"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".ui.profile.editprofile.EditProfileCustActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/top_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btn_back"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:src="@drawable/ic_back_24"
|
||||||
|
android:contentDescription="back"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_profile_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Edit Profil"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:fontFamily="@font/dmsans_bold"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.cardview.widget.CardView
|
||||||
|
android:id="@+id/card_profile"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:cardCornerRadius="8dp"
|
||||||
|
app:cardElevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/top_title"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- Profile Image -->
|
||||||
|
<de.hdodenhof.circleimageview.CircleImageView
|
||||||
|
android:id="@+id/profile_image"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:src="@drawable/baseline_account_circle_24"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
<!-- Edit Icon -->
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/edit_icon"
|
||||||
|
android:layout_width="32dp"
|
||||||
|
android:layout_height="32dp"
|
||||||
|
android:src="@drawable/ic_edit"
|
||||||
|
android:background="@drawable/circle_background"
|
||||||
|
android:padding="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/profile_image"
|
||||||
|
app:layout_constraintEnd_toEndOf="@id/profile_image"
|
||||||
|
android:layout_marginBottom="4dp"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
app:tint="@color/blue_500" />
|
||||||
|
|
||||||
|
<!-- Select Image Text -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tv_select_image"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Pilih Gambar"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:textColor="@color/blue_500"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/profile_image"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
|
</androidx.cardview.widget.CardView>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_nama"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/card_profile">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_name_user"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Nama"
|
||||||
|
android:text="Nama Pengguna"
|
||||||
|
android:inputType="textPersonName"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_nama">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_username"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Username"
|
||||||
|
android:text="Username Pengguna"
|
||||||
|
android:inputType="text"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_email"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_username">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_email_user"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Email"
|
||||||
|
android:text="Email Pengguna"
|
||||||
|
android:inputType="textEmailAddress"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_nomor_handphone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_email">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_number_phone_user"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Nomor Handphone"
|
||||||
|
android:text="Nomor handphone pengguna"
|
||||||
|
android:inputType="phone"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/til_tanggal_lahir"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_nomor_handphone">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/et_date_birth"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Tanggal Lahir"
|
||||||
|
android:text="Tanggal Lahir Pengguna"
|
||||||
|
android:inputType="date"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"/>
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btn_save"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Simpan"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginHorizontal="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/til_tanggal_lahir"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
Reference in New Issue
Block a user