WIP: se agrega configurar foto de perfil y otro trabajo en progreso
This commit is contained in:
parent
b019c79bcb
commit
0e553e05e6
@ -21,8 +21,8 @@ interface ImagesApi {
|
|||||||
@POST("images/create")
|
@POST("images/create")
|
||||||
@Multipart
|
@Multipart
|
||||||
fun postImage(@Part file: MultipartBody.Part,
|
fun postImage(@Part file: MultipartBody.Part,
|
||||||
@Part postId: MultipartBody.Part,
|
@Part postId: MultipartBody.Part? = null,
|
||||||
@Part setAsProfile: MultipartBody.Part? = null,
|
@Query("setAsProfile") setAsProfile: Boolean = false,
|
||||||
@Part squadId: MultipartBody.Part? = null): Call<ImageDto>
|
@Part squadId: MultipartBody.Part? = null): Call<ImageDto>
|
||||||
|
|
||||||
@POST("images/delete/delete_many")
|
@POST("images/delete/delete_many")
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
package com.isolaatti.profile
|
package com.isolaatti.profile
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
import com.isolaatti.auth.data.local.UserInfoDao
|
import com.isolaatti.auth.data.local.UserInfoDao
|
||||||
import com.isolaatti.connectivity.RetrofitClient
|
import com.isolaatti.connectivity.RetrofitClient
|
||||||
import com.isolaatti.database.AppDatabase
|
import com.isolaatti.database.AppDatabase
|
||||||
|
import com.isolaatti.images.common.data.remote.ImagesApi
|
||||||
import com.isolaatti.profile.data.remote.ProfileApi
|
import com.isolaatti.profile.data.remote.ProfileApi
|
||||||
import com.isolaatti.profile.data.repository.ProfileRepositoryImpl
|
import com.isolaatti.profile.data.repository.ProfileRepositoryImpl
|
||||||
import com.isolaatti.profile.domain.ProfileRepository
|
import com.isolaatti.profile.domain.ProfileRepository
|
||||||
@ -25,8 +27,8 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun provideProfileRepository(profileApi: ProfileApi, userInfoDao: UserInfoDao): ProfileRepository {
|
fun provideProfileRepository(profileApi: ProfileApi, userInfoDao: UserInfoDao, imagesApi: ImagesApi, contentResolver: ContentResolver): ProfileRepository {
|
||||||
return ProfileRepositoryImpl(profileApi, userInfoDao)
|
return ProfileRepositoryImpl(profileApi, userInfoDao, imagesApi, contentResolver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -12,8 +12,6 @@ interface ProfileApi {
|
|||||||
@GET("Fetch/UserProfile/{userId}")
|
@GET("Fetch/UserProfile/{userId}")
|
||||||
fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto>
|
fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto>
|
||||||
|
|
||||||
@POST("EditProfile/SetProfilePhoto")
|
|
||||||
fun setProfileImage(@Query("imageId") imageId: String): Call<Void>
|
|
||||||
|
|
||||||
@POST("EditProfile/UpdateProfile")
|
@POST("EditProfile/UpdateProfile")
|
||||||
fun updateProfile(@Body updateProfileDto: UpdateProfileDto): Call<UserProfileDto>
|
fun updateProfile(@Body updateProfileDto: UpdateProfileDto): Call<UserProfileDto>
|
||||||
|
|||||||
@ -1,8 +1,14 @@
|
|||||||
package com.isolaatti.profile.data.repository
|
package com.isolaatti.profile.data.repository
|
||||||
|
|
||||||
|
import android.content.ContentResolver
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.isolaatti.auth.data.local.UserInfoDao
|
import com.isolaatti.auth.data.local.UserInfoDao
|
||||||
import com.isolaatti.auth.data.local.UserInfoEntity
|
import com.isolaatti.auth.data.local.UserInfoEntity
|
||||||
|
import com.isolaatti.images.common.data.remote.ImagesApi
|
||||||
|
import com.isolaatti.images.common.domain.entity.LocalImage
|
||||||
import com.isolaatti.images.common.domain.entity.RemoteImage
|
import com.isolaatti.images.common.domain.entity.RemoteImage
|
||||||
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.data.remote.UpdateProfileDto
|
||||||
@ -11,12 +17,20 @@ 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
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import retrofit2.awaitResponse
|
import retrofit2.awaitResponse
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ProfileRepositoryImpl @Inject constructor(
|
class ProfileRepositoryImpl @Inject constructor(
|
||||||
private val profileApi: ProfileApi,
|
private val profileApi: ProfileApi,
|
||||||
private val userInfoDao: UserInfoDao) : ProfileRepository {
|
private val userInfoDao: UserInfoDao,
|
||||||
|
private val imagesApi: ImagesApi,
|
||||||
|
private val contentResolver: ContentResolver
|
||||||
|
) : ProfileRepository {
|
||||||
override fun getProfile(userId: Int): Flow<Resource<UserProfile>> = flow {
|
override fun getProfile(userId: Int): Flow<Resource<UserProfile>> = flow {
|
||||||
try {
|
try {
|
||||||
val result = profileApi.userProfile(userId).awaitResponse()
|
val result = profileApi.userProfile(userId).awaitResponse()
|
||||||
@ -37,16 +51,41 @@ class ProfileRepositoryImpl @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setProfileImage(image: RemoteImage): Flow<Resource<Boolean>> = flow {
|
override fun setProfileImage(image: LocalImage): Flow<Resource<RemoteImage>> = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
try {
|
try {
|
||||||
val result = profileApi.setProfileImage(image.id).awaitResponse()
|
var imageInputStream: InputStream? = null
|
||||||
|
|
||||||
|
contentResolver.openInputStream(image.uri).use {
|
||||||
|
imageInputStream = it
|
||||||
|
|
||||||
|
val bitmap = BitmapFactory.decodeStream(imageInputStream)
|
||||||
|
|
||||||
|
val outputStream = ByteArrayOutputStream()
|
||||||
|
|
||||||
|
if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q) {
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.WEBP, 50, outputStream)
|
||||||
|
} else {
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSY, 50, outputStream)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val result = imagesApi
|
||||||
|
.postImage(
|
||||||
|
file = MultipartBody.Part.createFormData("file", "profile-${System.currentTimeMillis()}",outputStream.toByteArray().toRequestBody()),
|
||||||
|
setAsProfile = true
|
||||||
|
)
|
||||||
|
.awaitResponse()
|
||||||
|
|
||||||
if(result.isSuccessful) {
|
if(result.isSuccessful) {
|
||||||
emit(Resource.Success(true))
|
val imageDto = result.body()
|
||||||
|
emit(Resource.Success(RemoteImage(imageDto!!.id)))
|
||||||
} else {
|
} else {
|
||||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("ProfileRepositoryImpl", e.message.toString())
|
Log.e("ProfileRepositoryImpl", e.message.toString())
|
||||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package com.isolaatti.profile.domain
|
package com.isolaatti.profile.domain
|
||||||
|
|
||||||
|
import com.isolaatti.images.common.data.remote.ImageDto
|
||||||
|
import com.isolaatti.images.common.domain.entity.LocalImage
|
||||||
import com.isolaatti.images.common.domain.entity.RemoteImage
|
import com.isolaatti.images.common.domain.entity.RemoteImage
|
||||||
import com.isolaatti.profile.domain.entity.UserProfile
|
import com.isolaatti.profile.domain.entity.UserProfile
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
@ -8,7 +10,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
interface ProfileRepository {
|
interface ProfileRepository {
|
||||||
fun getProfile(userId: Int): Flow<Resource<UserProfile>>
|
fun getProfile(userId: Int): Flow<Resource<UserProfile>>
|
||||||
|
|
||||||
fun setProfileImage(image: RemoteImage): Flow<Resource<Boolean>>
|
fun setProfileImage(image: LocalImage): Flow<Resource<RemoteImage>>
|
||||||
|
|
||||||
fun updateProfile(newDisplayName: String, newDescription: String): Flow<Resource<Boolean>>
|
fun updateProfile(newDisplayName: String, newDescription: String): Flow<Resource<Boolean>>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.profile.domain.use_case
|
package com.isolaatti.profile.domain.use_case
|
||||||
|
|
||||||
|
import com.isolaatti.images.common.domain.entity.LocalImage
|
||||||
import com.isolaatti.images.common.domain.entity.RemoteImage
|
import com.isolaatti.images.common.domain.entity.RemoteImage
|
||||||
import com.isolaatti.profile.domain.ProfileRepository
|
import com.isolaatti.profile.domain.ProfileRepository
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
@ -7,5 +8,5 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class SetProfileImage @Inject constructor(private val profileRepository: ProfileRepository) {
|
class SetProfileImage @Inject constructor(private val profileRepository: ProfileRepository) {
|
||||||
operator fun invoke(image: RemoteImage): Flow<Resource<Boolean>> = profileRepository.setProfileImage(image)
|
operator fun invoke(image: LocalImage): Flow<Resource<RemoteImage>> = profileRepository.setProfileImage(image)
|
||||||
}
|
}
|
||||||
@ -1,10 +1,16 @@
|
|||||||
package com.isolaatti.profile.presentation
|
package com.isolaatti.profile.presentation
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.isolaatti.followers.domain.FollowingState
|
import com.isolaatti.followers.domain.FollowingState
|
||||||
|
import com.isolaatti.images.common.domain.entity.LocalImage
|
||||||
import com.isolaatti.images.common.domain.entity.RemoteImage
|
import com.isolaatti.images.common.domain.entity.RemoteImage
|
||||||
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
|
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
|
||||||
import com.isolaatti.profile.domain.entity.UserProfile
|
import com.isolaatti.profile.domain.entity.UserProfile
|
||||||
@ -45,6 +51,12 @@ class ProfileViewModel @Inject constructor(
|
|||||||
|
|
||||||
val isRefreshing: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
val isRefreshing: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val showConfirmChangeProfilePictureBottomSheet: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val newProfileImage: MutableStateFlow<Uri?> = MutableStateFlow(null)
|
||||||
|
|
||||||
|
val uploadingProfilePicture: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
val errorProfilePicture: MutableStateFlow<Resource.Error<RemoteImage>?> = MutableStateFlow(null)
|
||||||
|
|
||||||
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
||||||
// caller should report as handled
|
// caller should report as handled
|
||||||
@ -91,13 +103,31 @@ class ProfileViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProfileImage(image: RemoteImage) {
|
fun setProfileImage() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
setProfileImageUC(image).onEach {
|
newProfileImage.value?.let {
|
||||||
_profile.postValue(_profile.value?.copy(profileImageId = image.id))
|
setProfileImageUC(LocalImage(it)).onEach { resource ->
|
||||||
|
when(resource) {
|
||||||
|
is Resource.Error<RemoteImage> -> {
|
||||||
|
errorProfilePicture.value = resource
|
||||||
|
uploadingProfilePicture.value = false
|
||||||
|
}
|
||||||
|
is Resource.Loading<RemoteImage> -> {
|
||||||
|
errorProfilePicture.value = null
|
||||||
|
uploadingProfilePicture.value = true
|
||||||
|
}
|
||||||
|
is Resource.Success<RemoteImage> -> {
|
||||||
|
errorProfilePicture.value = null
|
||||||
|
uploadingProfilePicture.value = false
|
||||||
|
showConfirmChangeProfilePictureBottomSheet.value = false
|
||||||
|
_profile.postValue(_profile.value?.copy(profileImageId = resource.data?.id))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun followUser() {
|
fun followUser() {
|
||||||
val currentProfile = _profile.value ?: return
|
val currentProfile = _profile.value ?: return
|
||||||
@ -181,4 +211,18 @@ class ProfileViewModel @Inject constructor(
|
|||||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hideConfirmPictureSheet() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
showConfirmChangeProfilePictureBottomSheet.value = false
|
||||||
|
newProfileImage.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onProfilePictureSelected(uri: Uri) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
showConfirmChangeProfilePictureBottomSheet.value = true
|
||||||
|
newProfileImage.value = uri
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -2,12 +2,17 @@ package com.isolaatti.profile.ui
|
|||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.media.MediaRecorder
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.PickVisualMediaRequest
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
@ -23,10 +28,12 @@ import androidx.compose.material3.ExperimentalMaterial3Api
|
|||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
@ -34,6 +41,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
@ -45,6 +53,8 @@ import androidx.compose.ui.platform.ViewCompositionStrategy
|
|||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.core.content.FileProvider
|
||||||
|
import androidx.core.net.toUri
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
@ -54,10 +64,13 @@ import androidx.lifecycle.repeatOnLifecycle
|
|||||||
import androidx.media3.session.MediaController
|
import androidx.media3.session.MediaController
|
||||||
import androidx.media3.session.SessionToken
|
import androidx.media3.session.SessionToken
|
||||||
import androidx.navigation.findNavController
|
import androidx.navigation.findNavController
|
||||||
|
import coil3.toUri
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
|
import com.isolaatti.MyApplication
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.audio.common.components.AudioRecorder
|
||||||
import com.isolaatti.audio.player.MediaService
|
import com.isolaatti.audio.player.MediaService
|
||||||
import com.isolaatti.common.Dialogs
|
import com.isolaatti.common.Dialogs
|
||||||
import com.isolaatti.common.ErrorMessageViewModel
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
@ -67,22 +80,30 @@ import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOpt
|
|||||||
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
||||||
import com.isolaatti.followers.domain.FollowingState
|
import com.isolaatti.followers.domain.FollowingState
|
||||||
import com.isolaatti.hashtags.ui.HashtagsPostsActivity
|
import com.isolaatti.hashtags.ui.HashtagsPostsActivity
|
||||||
|
import com.isolaatti.images.common.domain.entity.LocalImage
|
||||||
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
|
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
|
||||||
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
||||||
import com.isolaatti.posting.posts.components.PostComponent
|
import com.isolaatti.posting.posts.components.PostComponent
|
||||||
import com.isolaatti.posting.posts.domain.entity.Post
|
import com.isolaatti.posting.posts.domain.entity.Post
|
||||||
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
||||||
|
import com.isolaatti.posting.posts.ui.CreatePostActivity
|
||||||
import com.isolaatti.posting.posts.ui.PostInfoActivity
|
import com.isolaatti.posting.posts.ui.PostInfoActivity
|
||||||
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
|
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
|
||||||
import com.isolaatti.profile.domain.entity.UserProfile
|
import com.isolaatti.profile.domain.entity.UserProfile
|
||||||
import com.isolaatti.profile.presentation.EditProfileContract
|
import com.isolaatti.profile.presentation.EditProfileContract
|
||||||
import com.isolaatti.profile.presentation.ProfileViewModel
|
import com.isolaatti.profile.presentation.ProfileViewModel
|
||||||
|
import com.isolaatti.profile.ui.components.ConfirmChangeProfilePictureBottomSheet
|
||||||
import com.isolaatti.profile.ui.components.ProfileHeader
|
import com.isolaatti.profile.ui.components.ProfileHeader
|
||||||
|
import com.isolaatti.profile.ui.components.ProfilePictureBottomSheet
|
||||||
|
import com.isolaatti.profile.ui.components.ProfilePictureBottomSheetActions
|
||||||
|
import com.isolaatti.profile.ui.components.ProfilePictureState
|
||||||
import com.isolaatti.reports.data.ContentType
|
import com.isolaatti.reports.data.ContentType
|
||||||
import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
|
import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.guava.await
|
import kotlinx.coroutines.guava.await
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ProfileMainFragment : Fragment() {
|
class ProfileMainFragment : Fragment() {
|
||||||
@ -91,6 +112,19 @@ class ProfileMainFragment : Fragment() {
|
|||||||
val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
private var userId: Int? = null
|
private var userId: Int? = null
|
||||||
|
|
||||||
|
private var cameraPhotoUri: Uri? = null
|
||||||
|
|
||||||
|
private fun makePhotoUri(): Uri {
|
||||||
|
val cacheFile = File(requireContext().filesDir, "temp_picture_${Calendar.getInstance().timeInMillis}")
|
||||||
|
return FileProvider.getUriForFile(requireContext(), "${MyApplication.myApp.packageName}.provider", cacheFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val takePhotoLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {
|
||||||
|
if(it && cameraPhotoUri != null) {
|
||||||
|
viewModel.onProfilePictureSelected(cameraPhotoUri!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
||||||
if(it == null)
|
if(it == null)
|
||||||
return@registerForActivityResult
|
return@registerForActivityResult
|
||||||
@ -106,7 +140,11 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val choosePictureLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) {
|
||||||
|
if(it != null) {
|
||||||
|
viewModel.onProfilePictureSelected(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getData() {
|
private fun getData() {
|
||||||
|
|
||||||
@ -119,6 +157,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
|
|
||||||
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
||||||
private lateinit var mediaController: MediaController
|
private lateinit var mediaController: MediaController
|
||||||
|
private lateinit var mediaRecorder: MediaRecorder
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@ -131,6 +170,8 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
@ -174,8 +215,25 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Profile picture bottom sheet */
|
||||||
|
var showProfilePictureBottomSheet by remember { mutableStateOf(false) }
|
||||||
|
val profilePictureSheetState = rememberModalBottomSheetState()
|
||||||
|
var profilePictureState by remember { mutableStateOf(ProfilePictureState()) }
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val showConfirmChangeProfilePicture by viewModel.showConfirmChangeProfilePictureBottomSheet.collectAsState()
|
||||||
|
val newProfilePicture by viewModel.newProfileImage.collectAsState()
|
||||||
|
val uploadingProfilePicture by viewModel.uploadingProfilePicture.collectAsState()
|
||||||
|
|
||||||
|
var showAudioRecorder by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(profile) {
|
LaunchedEffect(profile) {
|
||||||
showAddPostButton = profile?.isUserItself == true
|
showAddPostButton = profile?.isUserItself == true
|
||||||
|
profilePictureState = ProfilePictureState(
|
||||||
|
showViewImage = profile?.profileImage != null,
|
||||||
|
showChangeProfileImage = profile?.isUserItself == true,
|
||||||
|
showRemoveImage = profile?.isUserItself == true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val followingText = when(followingState) {
|
val followingText = when(followingState) {
|
||||||
@ -199,7 +257,6 @@ class ProfileMainFragment : Fragment() {
|
|||||||
val posts by viewModel.posts.collectAsState()
|
val posts by viewModel.posts.collectAsState()
|
||||||
val loadingProfile by viewModel.loadingProfile.collectAsState()
|
val loadingProfile by viewModel.loadingProfile.collectAsState()
|
||||||
|
|
||||||
val playingAudio by remember { mutableStateOf(true) }
|
|
||||||
IsolaattiTheme {
|
IsolaattiTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -245,6 +302,80 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
) { insets ->
|
) { insets ->
|
||||||
|
|
||||||
|
if(showProfilePictureBottomSheet) {
|
||||||
|
ProfilePictureBottomSheet(
|
||||||
|
sheetState = profilePictureSheetState,
|
||||||
|
onDismissRequest = {
|
||||||
|
showProfilePictureBottomSheet = false
|
||||||
|
},
|
||||||
|
onAction = { action ->
|
||||||
|
when(action) {
|
||||||
|
ProfilePictureBottomSheetActions.ViewImage -> {
|
||||||
|
profile?.profileImage?.let {
|
||||||
|
PictureViewerActivity.startActivityWithImages(requireContext(), arrayOf(it))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ProfilePictureBottomSheetActions.ChangeImage -> {
|
||||||
|
choosePictureLauncher
|
||||||
|
.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||||
|
}
|
||||||
|
ProfilePictureBottomSheetActions.RemoveImage -> {
|
||||||
|
showRemoveProfileImageDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfilePictureBottomSheetActions.TakeAPicture -> {
|
||||||
|
cameraPhotoUri = makePhotoUri()
|
||||||
|
takePhotoLauncher.launch(cameraPhotoUri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scope.launch { profilePictureSheetState.hide() }.invokeOnCompletion {
|
||||||
|
if (!profilePictureSheetState.isVisible) {
|
||||||
|
showProfilePictureBottomSheet = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = profilePictureState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(showConfirmChangeProfilePicture) {
|
||||||
|
newProfilePicture?.let {
|
||||||
|
ConfirmChangeProfilePictureBottomSheet(
|
||||||
|
onDismiss = {
|
||||||
|
viewModel.hideConfirmPictureSheet()
|
||||||
|
},
|
||||||
|
oldImage = profile?.profileImage?.imageUrl?.toUri(),
|
||||||
|
newImage = it,
|
||||||
|
onAccept = {
|
||||||
|
viewModel.setProfileImage()
|
||||||
|
},
|
||||||
|
loading = uploadingProfilePicture
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(showAudioRecorder) {
|
||||||
|
ModalBottomSheet(onDismissRequest = { showAudioRecorder = false }) {
|
||||||
|
AudioRecorder(
|
||||||
|
onDismiss = {},
|
||||||
|
onPlay = TODO(),
|
||||||
|
onPause = TODO(),
|
||||||
|
onStopRecording = TODO(),
|
||||||
|
onPauseRecording = TODO(),
|
||||||
|
onStartRecording = TODO(),
|
||||||
|
isRecording = TODO(),
|
||||||
|
recordIsStopped = TODO(),
|
||||||
|
recordIsPaused = TODO(),
|
||||||
|
isPlaying = TODO(),
|
||||||
|
isLoading = TODO(),
|
||||||
|
durationSeconds = TODO(),
|
||||||
|
position = TODO(),
|
||||||
|
onSeek = TODO(),
|
||||||
|
modifier = TODO()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PullToRefreshBox(
|
PullToRefreshBox(
|
||||||
isRefreshing,
|
isRefreshing,
|
||||||
onRefresh = {
|
onRefresh = {
|
||||||
@ -273,9 +404,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
followerCount = it.numberOfFollowers,
|
followerCount = it.numberOfFollowers,
|
||||||
loading = loadingProfile,
|
loading = loadingProfile,
|
||||||
onImageClick = {
|
onImageClick = {
|
||||||
optionsViewModel.setOptions(Options.PROFILE_PHOTO_OPTIONS, CALLER_ID, it)
|
showProfilePictureBottomSheet = true
|
||||||
val fragment = BottomSheetPostOptionsFragment()
|
|
||||||
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
|
||||||
},
|
},
|
||||||
onFollowersClick = {
|
onFollowersClick = {
|
||||||
findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToMainFollowersFragment(it.userId))
|
findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToMainFollowersFragment(it.userId))
|
||||||
@ -290,6 +419,11 @@ class ProfileMainFragment : Fragment() {
|
|||||||
showProfileAudioProgress = true,
|
showProfileAudioProgress = true,
|
||||||
onEditProfileClick = {
|
onEditProfileClick = {
|
||||||
editProfile.launch(it)
|
editProfile.launch(it)
|
||||||
|
},
|
||||||
|
onAddAudioDescription = {
|
||||||
|
mediaRecorder = if (Build.VERSION.SDK_INT >= 31) MediaRecorder(requireContext()) else MediaRecorder()
|
||||||
|
showAudioRecorder = true
|
||||||
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -376,23 +510,6 @@ class ProfileMainFragment : Fragment() {
|
|||||||
Log.d("ProfileMainFragment", optionClicked.toString())
|
Log.d("ProfileMainFragment", optionClicked.toString())
|
||||||
|
|
||||||
when(optionClicked.optionsId) {
|
when(optionClicked.optionsId) {
|
||||||
Options.PROFILE_PHOTO_OPTIONS -> {
|
|
||||||
val profile = optionClicked.payload as? UserProfile
|
|
||||||
when(optionClicked.optionId) {
|
|
||||||
Options.Option.OPTION_PROFILE_PHOTO_CHANGE_PHOTO -> {
|
|
||||||
|
|
||||||
}
|
|
||||||
Options.Option.OPTION_PROFILE_PHOTO_REMOVE_PHOTO -> {
|
|
||||||
showRemoveProfileImageDialog()
|
|
||||||
}
|
|
||||||
Options.Option.OPTION_PROFILE_PHOTO_VIEW_PHOTO -> {
|
|
||||||
profile?.profileImage?.let {
|
|
||||||
PictureViewerActivity.startActivityWithImages(requireContext(), arrayOf(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
optionsViewModel.handle()
|
|
||||||
}
|
|
||||||
Options.POST_OPTIONS -> {
|
Options.POST_OPTIONS -> {
|
||||||
// post id should come as payload
|
// post id should come as payload
|
||||||
val post = optionClicked.payload as? Post ?: return@observe
|
val post = optionClicked.payload as? Post ?: return@observe
|
||||||
|
|||||||
@ -0,0 +1,120 @@
|
|||||||
|
package com.isolaatti.profile.ui.components
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ArrowForward
|
||||||
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
import com.isolaatti.R
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ConfirmChangeProfilePictureBottomSheet(
|
||||||
|
onDismiss: () -> Unit,
|
||||||
|
oldImage: Uri?,
|
||||||
|
newImage: Uri,
|
||||||
|
onAccept: () -> Unit,
|
||||||
|
loading: Boolean
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(onDismissRequest = onDismiss) {
|
||||||
|
|
||||||
|
Column(modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.change_profile_photo_question),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(16.dp).fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
if(!loading) {
|
||||||
|
Box(Modifier.padding(8.dp).fillMaxWidth()) {
|
||||||
|
if(oldImage != null) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = oldImage,
|
||||||
|
contentDescription = stringResource(R.string.old_picture),
|
||||||
|
modifier = Modifier.size(70.dp)
|
||||||
|
.clip(RoundedCornerShape(50))
|
||||||
|
)
|
||||||
|
|
||||||
|
Icon(Icons.Default.ArrowForward, contentDescription = null, modifier = Modifier.size(40.dp))
|
||||||
|
|
||||||
|
AsyncImage(
|
||||||
|
model = newImage,
|
||||||
|
contentDescription = stringResource(R.string.old_picture),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(50))
|
||||||
|
.size(70.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
){
|
||||||
|
AsyncImage(
|
||||||
|
model = newImage,
|
||||||
|
contentDescription = stringResource(R.string.old_picture),
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(RoundedCornerShape(50))
|
||||||
|
.size(70.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Row(modifier = Modifier.padding(8.dp).fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
onDismiss()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.cancel))
|
||||||
|
}
|
||||||
|
Spacer(Modifier.width(8.dp))
|
||||||
|
Button(
|
||||||
|
onClick = {
|
||||||
|
onAccept()
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.accept))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CircularProgressIndicator()
|
||||||
|
Text(stringResource(R.string.uploading_photo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -6,7 +6,9 @@ import androidx.compose.animation.core.animateValue
|
|||||||
import androidx.compose.animation.core.infiniteRepeatable
|
import androidx.compose.animation.core.infiniteRepeatable
|
||||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.border
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -18,8 +20,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Edit
|
import androidx.compose.material.icons.filled.Edit
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
|
import androidx.compose.material3.FilledTonalButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.OutlinedButton
|
import androidx.compose.material3.OutlinedButton
|
||||||
|
import androidx.compose.material3.OutlinedCard
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
@ -70,6 +74,7 @@ fun ProfileHeader(
|
|||||||
onFollowersClick: () -> Unit,
|
onFollowersClick: () -> Unit,
|
||||||
onEditProfileClick: () -> Unit,
|
onEditProfileClick: () -> Unit,
|
||||||
onFollowChange: (Boolean) -> Unit,
|
onFollowChange: (Boolean) -> Unit,
|
||||||
|
onAddAudioDescription: () -> Unit,
|
||||||
loading: Boolean = false,
|
loading: Boolean = false,
|
||||||
isOwnUser: Boolean,
|
isOwnUser: Boolean,
|
||||||
followingThisUser: Boolean,
|
followingThisUser: Boolean,
|
||||||
@ -125,6 +130,27 @@ fun ProfileHeader(
|
|||||||
Text(stringResource(R.string.go_to_followers_btn_text, followerCount, followingCount))
|
Text(stringResource(R.string.go_to_followers_btn_text, followerCount, followingCount))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
when {
|
||||||
|
isOwnUser && audio == null -> {
|
||||||
|
OutlinedCard(modifier = Modifier.padding(16.dp).fillMaxWidth()) {
|
||||||
|
Column(modifier = Modifier.padding(8.dp).fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
Text(stringResource(R.string.you_have_now_audio))
|
||||||
|
FilledTonalButton(
|
||||||
|
onClick = {
|
||||||
|
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.set_audio_description))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
audio != null -> {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
when {
|
when {
|
||||||
isOwnUser -> {
|
isOwnUser -> {
|
||||||
OutlinedButton(onClick = onEditProfileClick) {
|
OutlinedButton(onClick = onEditProfileClick) {
|
||||||
@ -168,6 +194,7 @@ fun ProfileHeaderPreview() {
|
|||||||
showProfileAudioProgress = true,
|
showProfileAudioProgress = true,
|
||||||
onFollowersClick = {},
|
onFollowersClick = {},
|
||||||
onFollowChange = {},
|
onFollowChange = {},
|
||||||
onEditProfileClick = {}
|
onEditProfileClick = {},
|
||||||
|
onAddAudioDescription = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -0,0 +1,106 @@
|
|||||||
|
package com.isolaatti.profile.ui.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.SheetState
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.isolaatti.R
|
||||||
|
|
||||||
|
|
||||||
|
data class ProfilePictureState(
|
||||||
|
val showViewImage: Boolean = true,
|
||||||
|
val showChangeProfileImage: Boolean = true,
|
||||||
|
val showRemoveImage: Boolean = true
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ProfilePictureBottomSheetActions {
|
||||||
|
ViewImage, ChangeImage, RemoveImage, TakeAPicture
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun ProfilePictureBottomSheet(
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
sheetState: SheetState = rememberModalBottomSheetState(),
|
||||||
|
state: ProfilePictureState,
|
||||||
|
onAction: (ProfilePictureBottomSheetActions) -> Unit
|
||||||
|
) {
|
||||||
|
ModalBottomSheet(onDismissRequest = onDismissRequest, sheetState = sheetState) {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.profile_photo),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
modifier = Modifier.padding(16.dp).fillMaxWidth()
|
||||||
|
)
|
||||||
|
|
||||||
|
if(state.showViewImage) {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onAction(ProfilePictureBottomSheetActions.ViewImage) },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.view_photo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(state.showChangeProfileImage) {
|
||||||
|
Box {
|
||||||
|
var expanded by remember { mutableStateOf(false) }
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
expanded = true
|
||||||
|
},
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.change_profile_photo))
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = expanded,
|
||||||
|
onDismissRequest = { expanded = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.upload_a_picture)) },
|
||||||
|
onClick = {
|
||||||
|
onAction(ProfilePictureBottomSheetActions.ChangeImage)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
DropdownMenuItem(
|
||||||
|
text = { Text(stringResource(R.string.take_a_photo)) },
|
||||||
|
onClick = {
|
||||||
|
onAction(ProfilePictureBottomSheetActions.TakeAPicture)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if(state.showRemoveImage) {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onAction(ProfilePictureBottomSheetActions.RemoveImage) },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.remove_photo))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -230,6 +230,10 @@
|
|||||||
<string name="profile">Profile</string>
|
<string name="profile">Profile</string>
|
||||||
<string name="add_post_button_desc">Add post button</string>
|
<string name="add_post_button_desc">Add post button</string>
|
||||||
<string name="you_will_see_what_user_posts_here">When %s posts something you will see it here</string>
|
<string name="you_will_see_what_user_posts_here">When %s posts something you will see it here</string>
|
||||||
|
<string name="old_picture">Old picture</string>
|
||||||
|
<string name="change_profile_photo_question">Change profile picture?</string>
|
||||||
|
<string name="uploading_photo">Uploading photo</string>
|
||||||
|
<string name="you_have_now_audio">You have no audio description</string>
|
||||||
<string-array name="report_reasons">
|
<string-array name="report_reasons">
|
||||||
<item>Spam</item>
|
<item>Spam</item>
|
||||||
<item>Explicit content</item>
|
<item>Explicit content</item>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user