WIP
This commit is contained in:
parent
46e12db1fd
commit
727007d94f
@ -0,0 +1,34 @@
|
||||
package com.isolaatti.audio.common.components
|
||||
|
||||
import android.media.MediaRecorder
|
||||
import android.net.Uri
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun AudioRecorder(
|
||||
onAudioRecorded: (Uri) -> Unit,
|
||||
dismissible: Boolean,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
|
||||
var context = LocalContext.current
|
||||
var mediaRecorder: MediaRecorder?
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
mediaRecorder = MediaRecorder()
|
||||
|
||||
onDispose {
|
||||
mediaRecorder?.release()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun AudioRecorderPreview() {
|
||||
|
||||
}
|
||||
@ -21,7 +21,7 @@ interface ImagesApi {
|
||||
@POST("images/create")
|
||||
@Multipart
|
||||
fun postImage(@Part file: MultipartBody.Part,
|
||||
@Part name: MultipartBody.Part,
|
||||
@Part postId: MultipartBody.Part,
|
||||
@Part setAsProfile: MultipartBody.Part? = null,
|
||||
@Part squadId: MultipartBody.Part? = null): Call<ImageDto>
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
|
||||
}
|
||||
}
|
||||
|
||||
override fun uploadImage(name: String, imageUri: Uri, squadId: String?): Flow<Resource<Image>> = flow {
|
||||
override fun uploadImage(imageUri: Uri, postId: Long, squadId: String?): Flow<Resource<Image>> = flow {
|
||||
emit(Resource.Loading())
|
||||
var imageInputStream: InputStream? = null
|
||||
try {
|
||||
@ -84,8 +84,8 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
|
||||
|
||||
|
||||
val response = imagesApi.postImage(
|
||||
MultipartBody.Part.createFormData("file", name,outputStream.toByteArray().toRequestBody()),
|
||||
MultipartBody.Part.createFormData("name", name)
|
||||
MultipartBody.Part.createFormData("file", "$postId-${System.currentTimeMillis()}",outputStream.toByteArray().toRequestBody()),
|
||||
MultipartBody.Part.createFormData("postId", postId.toString())
|
||||
).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
val imageDto = response.body()
|
||||
@ -106,12 +106,4 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
|
||||
imageInputStream?.close()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getDetachedDraftImages(): Flow<Resource<List<ImageDraftEntity>>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getDraftImagesOfPost(postId: Long): Flow<Resource<List<ImageDraftEntity>>> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
@ -9,7 +9,5 @@ import kotlinx.coroutines.flow.Flow
|
||||
interface ImagesRepository {
|
||||
fun getImagesOfUser(userId: Int, lastId: String? = null): Flow<Resource<List<Image>>>
|
||||
fun deleteImages(images: List<Image>): Flow<Resource<Boolean>>
|
||||
fun uploadImage(name: String, imageUri: Uri, squadId: String?): Flow<Resource<Image>>
|
||||
fun getDetachedDraftImages(): Flow<Resource<List<ImageDraftEntity>>>
|
||||
fun getDraftImagesOfPost(postId: Long): Flow<Resource<List<ImageDraftEntity>>>
|
||||
fun uploadImage(imageUri: Uri, postId: Long, squadId: String?): Flow<Resource<Image>>
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.isolaatti.images.image_maker.presentation
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.images.common.domain.entity.Image
|
||||
import com.isolaatti.images.common.domain.repository.ImagesRepository
|
||||
import com.isolaatti.utils.Resource
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ImageMakerViewModel @Inject constructor(private val imagesRepository: ImagesRepository) : ViewModel() {
|
||||
var imageUri: Uri? = null
|
||||
var name: String? = null
|
||||
val image: MutableLiveData<Resource<Image>> = MutableLiveData()
|
||||
fun uploadPicture() {
|
||||
if(imageUri == null || name == null) {
|
||||
return
|
||||
}
|
||||
viewModelScope.launch {
|
||||
imagesRepository.uploadImage(name!!.trim(), imageUri!!, null).onEach {
|
||||
image.postValue(it)
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,84 +0,0 @@
|
||||
package com.isolaatti.images.image_maker.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import coil.load
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.common.IsolaattiBaseActivity
|
||||
import com.isolaatti.databinding.ActivityImageMakerBinding
|
||||
import com.isolaatti.images.image_maker.presentation.ImageMakerViewModel
|
||||
import com.isolaatti.utils.Resource
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ImageMakerActivity : IsolaattiBaseActivity() {
|
||||
private lateinit var binding: ActivityImageMakerBinding
|
||||
private val viewModel: ImageMakerViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityImageMakerBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
viewModel.imageUri = intent.data
|
||||
binding.imagePreview.load(intent.data)
|
||||
onBackPressedDispatcher.addCallback(onBackPressedCallback)
|
||||
setupListeners()
|
||||
setupObservers()
|
||||
}
|
||||
|
||||
private val onBackPressedCallback = object: OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
showExitConfirmationDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private fun showExitConfirmationDialog() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setTitle(R.string.discard_image)
|
||||
.setPositiveButton(R.string.yes_discard_image) {_, _ ->
|
||||
finish()
|
||||
}.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
binding.uploadPhotoFab.setOnClickListener {
|
||||
viewModel.uploadPicture()
|
||||
}
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
showExitConfirmationDialog()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
viewModel.image.observe(this) {
|
||||
when(it) {
|
||||
is Resource.Error -> {
|
||||
errorViewModel.error.value = it.errorType
|
||||
binding.progressBarLoading.visibility = View.GONE
|
||||
binding.uploadPhotoFab.visibility = View.VISIBLE
|
||||
}
|
||||
is Resource.Loading -> {
|
||||
binding.progressBarLoading.visibility = View.VISIBLE
|
||||
binding.uploadPhotoFab.visibility = View.INVISIBLE
|
||||
}
|
||||
is Resource.Success -> {
|
||||
binding.progressBarLoading.visibility = View.GONE
|
||||
binding.uploadPhotoFab.visibility = View.VISIBLE
|
||||
setResult(Activity.RESULT_OK, Intent().putExtra(EXTRA_IMAGE, it.data))
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_IMAGE = "image"
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
package com.isolaatti.images.image_maker.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import androidx.activity.result.contract.ActivityResultContract
|
||||
import com.isolaatti.images.common.domain.entity.Image
|
||||
|
||||
class ImageMakerContract : ActivityResultContract<Uri, Image?>() {
|
||||
override fun createIntent(context: Context, input: Uri): Intent {
|
||||
val intent = Intent(context, ImageMakerActivity::class.java)
|
||||
intent.data = input
|
||||
|
||||
return intent
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Image? {
|
||||
if(resultCode == Activity.RESULT_OK) {
|
||||
return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
|
||||
intent?.getSerializableExtra(ImageMakerActivity.EXTRA_IMAGE) as Image?
|
||||
} else {
|
||||
intent?.getSerializableExtra(ImageMakerActivity.EXTRA_IMAGE, Image::class.java)
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
package com.isolaatti.posting
|
||||
|
||||
import android.content.ContentResolver
|
||||
import com.isolaatti.connectivity.RetrofitClient
|
||||
import com.isolaatti.images.common.data.remote.ImagesApi
|
||||
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
||||
import com.isolaatti.posting.posts.data.remote.PostApi
|
||||
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
||||
@ -25,7 +27,7 @@ class Module {
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun providePostsRepository(feedsApi: FeedsApi, postApi: PostApi): PostsRepository {
|
||||
return PostsRepositoryImpl(feedsApi, postApi)
|
||||
fun providePostsRepository(feedsApi: FeedsApi, postApi: PostApi, imagesApi: ImagesApi, contentResolver: ContentResolver): PostsRepository {
|
||||
return PostsRepositoryImpl(feedsApi, postApi, imagesApi, contentResolver)
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.isolaatti.images.common.components
|
||||
package com.isolaatti.posting.posts.components
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.compose.foundation.Image
|
||||
@ -9,15 +9,12 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.FilledIconButton
|
||||
import androidx.compose.material3.FilledTonalIconButton
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -33,26 +30,24 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Devices
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Popup
|
||||
import androidx.compose.ui.zIndex
|
||||
import coil3.compose.AsyncImage
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.images.common.domain.entity.Image
|
||||
|
||||
@Composable
|
||||
fun ImagesRow(
|
||||
fun PostAttachments(
|
||||
modifier: Modifier = Modifier,
|
||||
images: List<Uri>,
|
||||
deletable: Boolean,
|
||||
addable: Boolean,
|
||||
onClick: (index: Int) -> Unit,
|
||||
onDeleteClick: (index: Int) -> Unit = {},
|
||||
imageAddable: Boolean,
|
||||
onImageClick: (index: Int) -> Unit,
|
||||
onImageDeleteClick: (index: Int) -> Unit = {},
|
||||
onTakePicture: () -> Unit = {},
|
||||
onUploadPicture: () -> Unit = {}
|
||||
onUploadPicture: () -> Unit = {},
|
||||
) {
|
||||
var showAddPhotoPopUp by remember { mutableStateOf(false) }
|
||||
LazyRow(modifier, contentPadding = PaddingValues(horizontal = 8.dp)) {
|
||||
if(addable) {
|
||||
if(imageAddable) {
|
||||
item {
|
||||
Card(modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
@ -93,7 +88,7 @@ fun ImagesRow(
|
||||
.padding(horizontal = 4.dp)
|
||||
.size(120.dp),
|
||||
onClick = {
|
||||
onClick(index)
|
||||
onImageClick(index)
|
||||
}) {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@ -101,7 +96,7 @@ fun ImagesRow(
|
||||
) {
|
||||
if(deletable) {
|
||||
FilledTonalIconButton(
|
||||
onClick = { onDeleteClick(index) },
|
||||
onClick = { onImageDeleteClick(index) },
|
||||
modifier = Modifier.zIndex(2f)
|
||||
) {
|
||||
Icon(imageVector = Icons.Default.Close, contentDescription = null)
|
||||
@ -124,5 +119,5 @@ fun ImagesRow(
|
||||
@Preview(device = Devices.PIXEL_5)
|
||||
@Composable
|
||||
fun ImagesRowPreview() {
|
||||
ImagesRow(images = emptyList(), deletable = true, addable = true, onClick = {})
|
||||
PostAttachments(images = emptyList(), deletable = true, imageAddable = true, onImageClick = {})
|
||||
}
|
||||
@ -4,5 +4,6 @@ data class CreatePostDto(
|
||||
val privacy: Int,
|
||||
val content: String,
|
||||
val audioId: String?,
|
||||
val squadId: String?
|
||||
val squadId: String?,
|
||||
val isDraft: Boolean = false
|
||||
)
|
||||
@ -24,4 +24,7 @@ interface PostApi {
|
||||
@GET("Posting/Post/{postId}/Versions")
|
||||
fun getVersions(@Path("postId") postId: Long): Call<ResultDto<List<VersionDto>>>
|
||||
|
||||
@POST("Posting/Post/{postId}/SetIsDraft")
|
||||
fun setIsDraft(@Path("postId") postId: Long, @Query("isDraft") isDraft: Boolean): Call<FeedDto.PostDto>
|
||||
|
||||
}
|
||||
@ -1,7 +1,16 @@
|
||||
package com.isolaatti.posting.posts.data.repository
|
||||
|
||||
import android.content.ContentResolver
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.util.Log
|
||||
import com.google.gson.Gson
|
||||
import com.isolaatti.images.common.data.remote.DeleteImagesDto
|
||||
import com.isolaatti.images.common.data.remote.ImageDto
|
||||
import com.isolaatti.images.common.data.remote.ImagesApi
|
||||
import com.isolaatti.images.common.domain.entity.Image
|
||||
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||
import com.isolaatti.posting.posts.data.remote.DeletePostDto
|
||||
import com.isolaatti.posting.posts.data.remote.EditPostDto
|
||||
@ -17,10 +26,19 @@ import com.isolaatti.profile.domain.entity.ProfileListItem
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import retrofit2.awaitResponse
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import javax.inject.Inject
|
||||
|
||||
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
||||
class PostsRepositoryImpl @Inject constructor(
|
||||
private val feedsApi: FeedsApi,
|
||||
private val postApi: PostApi,
|
||||
private val imagesApi: ImagesApi,
|
||||
private val contentResolver: ContentResolver
|
||||
) : PostsRepository {
|
||||
companion object {
|
||||
const val LOG_TAG = "PostsRepositoryImpl"
|
||||
}
|
||||
@ -64,16 +82,91 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
||||
}
|
||||
}
|
||||
|
||||
override fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>> = flow {
|
||||
override fun makePost(createPostDto: CreatePostDto, images: List<Uri>): Flow<Resource<FeedDto.PostDto>> = flow {
|
||||
emit(Resource.Loading())
|
||||
try {
|
||||
val result = postApi.makePost(createPostDto).awaitResponse()
|
||||
val hasImages = images.isNotEmpty()
|
||||
val result = postApi.makePost(
|
||||
if(hasImages) {
|
||||
createPostDto.copy(isDraft = true)
|
||||
} else {
|
||||
createPostDto
|
||||
}
|
||||
).awaitResponse()
|
||||
if(result.isSuccessful) {
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
|
||||
if(hasImages) {
|
||||
val successfulImageUploads: MutableList<ImageDto> = mutableListOf()
|
||||
|
||||
val postId = result.body()?.post?.id
|
||||
if(postId == null) {
|
||||
emit(Resource.Error())
|
||||
return@flow
|
||||
}
|
||||
images.forEach { imageUri ->
|
||||
var imageInputStream: InputStream? = null
|
||||
try {
|
||||
imageInputStream = contentResolver.openInputStream(imageUri)
|
||||
val bitmap = BitmapFactory.decodeStream(imageInputStream)
|
||||
|
||||
if(bitmap == null) {
|
||||
Log.e(LOG_TAG, "Error resolving image with content resolver")
|
||||
return@forEach
|
||||
}
|
||||
|
||||
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 response = imagesApi.postImage(
|
||||
MultipartBody.Part.createFormData("file", "$postId-${System.currentTimeMillis()}",outputStream.toByteArray().toRequestBody()),
|
||||
MultipartBody.Part.createFormData("postId", postId.toString())
|
||||
).awaitResponse()
|
||||
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { successfulImageUploads.add(it) }
|
||||
|
||||
}
|
||||
|
||||
} catch(e: Exception) {
|
||||
Log.e("ImagesRepository", e.toString())
|
||||
|
||||
} finally {
|
||||
imageInputStream?.close()
|
||||
}
|
||||
}
|
||||
|
||||
// undo post and emit error
|
||||
if(successfulImageUploads.size != images.size) {
|
||||
postApi.deletePost(DeletePostDto(postId)).awaitResponse()
|
||||
emit(Resource.Error(Resource.Error.ErrorType.ServerError, "Some images were not processed correctly"))
|
||||
} else {
|
||||
postApi.setIsDraft(postId, false)
|
||||
val response = postApi.getPost(postId).awaitResponse()
|
||||
|
||||
if(response.isSuccessful) {
|
||||
emit(Resource.Success(response.body()))
|
||||
} else {
|
||||
val msg = "Post posted but could not retrieve updated post data from server"
|
||||
Log.e(LOG_TAG, msg)
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code()), msg))
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
emit(Resource.Success(result.body()))
|
||||
return@flow
|
||||
}
|
||||
|
||||
}
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
|
||||
} catch(_: Exception) {
|
||||
} catch(e: Exception) {
|
||||
Log.d(LOG_TAG, e.toString())
|
||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.isolaatti.posting.posts.domain
|
||||
|
||||
import android.net.Uri
|
||||
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||
import com.isolaatti.posting.posts.data.remote.EditPostDto
|
||||
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||
@ -17,7 +18,7 @@ interface PostsRepository {
|
||||
|
||||
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
|
||||
|
||||
fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>>
|
||||
fun makePost(createPostDto: CreatePostDto, images: List<Uri>): Flow<Resource<FeedDto.PostDto>>
|
||||
fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>>
|
||||
fun deletePost(postId: Long): Flow<Resource<PostDeletedDto>>
|
||||
fun loadPost(postId: Long): Flow<Resource<Post>>
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.isolaatti.posting.posts.domain.use_case
|
||||
|
||||
import android.net.Uri
|
||||
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||
@ -11,9 +12,10 @@ class MakePost @Inject constructor(private val postsRepository: PostsRepository)
|
||||
operator fun invoke(
|
||||
privacy: Int,
|
||||
content: String,
|
||||
images: List<Uri>,
|
||||
audioId: String?,
|
||||
squadId: String?
|
||||
): Flow<Resource<FeedDto.PostDto>> {
|
||||
return postsRepository.makePost(CreatePostDto(privacy, content, audioId, squadId))
|
||||
): Flow<Resource<FeedDto.PostDto>> {
|
||||
return postsRepository.makePost(CreatePostDto(privacy, content, audioId, squadId), images)
|
||||
}
|
||||
}
|
||||
@ -62,7 +62,13 @@ class CreatePostViewModel @Inject constructor(
|
||||
private fun sendDiscussion() {
|
||||
Log.d(LOG_TAG, "postDiscussion#send()")
|
||||
viewModelScope.launch {
|
||||
makePost(EditPostDto.PRIVACY_ISOLAATTI, content, audioId, null).onEach {
|
||||
makePost(
|
||||
privacy = EditPostDto.PRIVACY_ISOLAATTI,
|
||||
content = content,
|
||||
images = photos.value,
|
||||
audioId = audioId,
|
||||
squadId = null
|
||||
).onEach {
|
||||
when(it) {
|
||||
is Resource.Success -> {
|
||||
sendingPost.postValue(false)
|
||||
|
||||
@ -1,22 +1,57 @@
|
||||
package com.isolaatti.posting.posts.ui
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.gestures.ScrollableState
|
||||
import androidx.compose.foundation.gestures.rememberScrollableState
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.CenterAlignedTopAppBar
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.OutlinedTextFieldDefaults
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
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.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.isolaatti.MyApplication
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.audio.common.components.AudioRecorder
|
||||
import com.isolaatti.common.IsolaattiBaseActivity
|
||||
import com.isolaatti.databinding.ActivityCreatePostBinding
|
||||
import com.isolaatti.common.IsolaattiTheme
|
||||
import com.isolaatti.posting.posts.components.PostAttachments
|
||||
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
|
||||
@AndroidEntryPoint
|
||||
class CreatePostActivity : IsolaattiBaseActivity() {
|
||||
@ -36,94 +71,157 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
||||
const val EXTRA_KEY_POST_POSTED = "post"
|
||||
}
|
||||
|
||||
lateinit var binding: ActivityCreatePostBinding
|
||||
val viewModel: CreatePostViewModel by viewModels()
|
||||
var mode: Int = EXTRA_MODE_CREATE
|
||||
var postId: Long = 0L
|
||||
|
||||
private val choosePictureLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) {
|
||||
if(it != null) {
|
||||
viewModel.addPicture(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makePhotoUri(): Uri {
|
||||
val cacheFile = File(filesDir, "temp_picture_${Calendar.getInstance().timeInMillis}")
|
||||
return FileProvider.getUriForFile(this, "${MyApplication.myApp.packageName}.provider", cacheFile)
|
||||
}
|
||||
|
||||
private var cameraPhotoUri: Uri? = null
|
||||
|
||||
private val takePhotoLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {
|
||||
if(it && cameraPhotoUri != null) {
|
||||
viewModel.addPicture(cameraPhotoUri!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
setContent {
|
||||
var text by remember { mutableStateOf("") }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
val pictures by viewModel.photos.collectAsStateWithLifecycle()
|
||||
|
||||
val editMode = remember {
|
||||
mode == EXTRA_MODE_EDIT && postId != 0L
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
|
||||
IsolaattiTheme {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
CenterAlignedTopAppBar(
|
||||
title = { Text(stringResource(if(editMode) R.string.edit else R.string.new_post)) },
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = {
|
||||
exit()
|
||||
}
|
||||
) {
|
||||
Icon(Icons.Default.Close, null)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
Button(
|
||||
enabled = text.isNotBlank() || pictures.isNotEmpty(),
|
||||
onClick = {
|
||||
if(editMode) {
|
||||
viewModel.editDiscussion(postId)
|
||||
} else {
|
||||
viewModel.postDiscussion()
|
||||
}
|
||||
|
||||
}
|
||||
) {
|
||||
Text(stringResource(if(editMode) R.string.save else R.string.post))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) {
|
||||
val scrollState = rememberScrollState()
|
||||
Column(modifier = Modifier.padding(it).verticalScroll(scrollState)) {
|
||||
OutlinedTextField(
|
||||
value = text,
|
||||
onValueChange = {
|
||||
text = it
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 8.dp)
|
||||
.fillMaxWidth()
|
||||
.focusRequester(focusRequester),
|
||||
placeholder = { Text(stringResource(R.string.what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want)) },
|
||||
colors = OutlinedTextFieldDefaults
|
||||
.colors(
|
||||
focusedBorderColor = Color.Transparent,
|
||||
unfocusedBorderColor = Color.Transparent
|
||||
),
|
||||
trailingIcon = {
|
||||
IconButton(onClick = {}) {
|
||||
Icon(painterResource(id = R.drawable.baseline_mic_24), null)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
AnimatedVisibility(false) {
|
||||
AudioRecorder(
|
||||
onAudioRecorded = {
|
||||
|
||||
},
|
||||
dismissible = true,
|
||||
onDismiss = {
|
||||
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
PostAttachments(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
images = pictures,
|
||||
deletable = true,
|
||||
imageAddable = true,
|
||||
onImageClick = {
|
||||
|
||||
},
|
||||
onImageDeleteClick = {
|
||||
viewModel.removePicture(it)
|
||||
},
|
||||
onTakePicture = {
|
||||
cameraPhotoUri = makePhotoUri()
|
||||
takePhotoLauncher.launch(cameraPhotoUri!!)
|
||||
},
|
||||
onUploadPicture = {
|
||||
choosePictureLauncher.launch(
|
||||
PickVisualMediaRequest(
|
||||
ActivityResultContracts.PickVisualMedia.ImageOnly)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
intent.extras?.let {
|
||||
mode = it.getInt(EXTRA_KEY_MODE, EXTRA_MODE_CREATE)
|
||||
postId = it.getLong(EXTRA_KEY_POST_ID)
|
||||
}
|
||||
|
||||
|
||||
binding = ActivityCreatePostBinding.inflate(layoutInflater)
|
||||
|
||||
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||
viewModel.loadDiscussion(postId)
|
||||
}
|
||||
|
||||
|
||||
|
||||
setupUI()
|
||||
setListeners()
|
||||
setObservers()
|
||||
setContentView(binding.root)
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
errorViewModel.retry.collect {
|
||||
if(!it) {
|
||||
return@collect
|
||||
}
|
||||
|
||||
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||
viewModel.editDiscussion(postId)
|
||||
} else {
|
||||
viewModel.postDiscussion()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
binding.toolbar.setTitle(if(mode == EXTRA_MODE_EDIT && postId != 0L) R.string.edit else R.string.new_post)
|
||||
|
||||
binding.pager.adapter = CreatePostFragmentStateAdapter(this)
|
||||
|
||||
}
|
||||
|
||||
private fun setListeners() {
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
exit()
|
||||
}
|
||||
|
||||
|
||||
binding.postButton.setOnClickListener {
|
||||
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||
viewModel.editDiscussion(postId)
|
||||
} else {
|
||||
viewModel.postDiscussion()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun setObservers() {
|
||||
viewModel.validation.observe(this@CreatePostActivity) {
|
||||
binding.postButton.isEnabled = it
|
||||
}
|
||||
|
||||
viewModel.error.observe(this@CreatePostActivity) {
|
||||
errorViewModel.error.postValue(it)
|
||||
}
|
||||
|
||||
viewModel.posted.observe(this@CreatePostActivity) {
|
||||
setResult(Activity.RESULT_OK, Intent().apply{
|
||||
putExtra(EXTRA_KEY_POST_POSTED, it)
|
||||
})
|
||||
finish()
|
||||
}
|
||||
|
||||
viewModel.sendingPost.observe(this@CreatePostActivity) {
|
||||
binding.progressBarLoading.visibility = if(it) View.VISIBLE else View.GONE
|
||||
}
|
||||
}
|
||||
|
||||
private fun exit() {
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
package com.isolaatti.posting.posts.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
|
||||
class CreatePostFragmentStateAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
|
||||
override fun getItemCount(): Int {
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return PostEditingFragment()
|
||||
}
|
||||
}
|
||||
@ -1,60 +0,0 @@
|
||||
package com.isolaatti.posting.posts.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.isolaatti.common.CoilImageLoader
|
||||
import com.isolaatti.databinding.FragmentMarkdownEditingBinding
|
||||
import com.isolaatti.databinding.FragmentMarkdownPreviewBinding
|
||||
import com.isolaatti.markdown.HashtagMarkwonPlugin
|
||||
import com.isolaatti.markdown.RelativePathMarkwonPlugin
|
||||
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
||||
import dagger.hilt.EntryPoint
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import io.noties.markwon.image.coil.CoilImagesPlugin
|
||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
|
||||
class MarkdownPreviewFragment : Fragment() {
|
||||
private lateinit var binding: FragmentMarkdownPreviewBinding
|
||||
private val viewModel: CreatePostViewModel by activityViewModels()
|
||||
private var markwon: Markwon? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
markwon = Markwon.builder(requireContext())
|
||||
.usePlugin(RelativePathMarkwonPlugin())
|
||||
.usePlugin(CoilImagesPlugin.create(requireContext(), CoilImageLoader.imageLoader))
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.usePlugin(HashtagMarkwonPlugin())
|
||||
.build()
|
||||
}
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = FragmentMarkdownPreviewBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
|
||||
markwon = null
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
viewModel.liveContent.observe(viewLifecycleOwner) {
|
||||
markwon?.setMarkdown(binding.textView, it)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
package com.isolaatti.posting.posts.ui
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.PickVisualMediaRequest
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.isolaatti.MyApplication
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.audio.common.domain.Audio
|
||||
import com.isolaatti.audio.common.domain.Playable
|
||||
import com.isolaatti.common.IsolaattiTheme
|
||||
import com.isolaatti.databinding.FragmentMarkdownEditingBinding
|
||||
import com.isolaatti.images.common.components.ImagesRow
|
||||
import com.isolaatti.posting.link_creator.presentation.LinkCreatorViewModel
|
||||
import com.isolaatti.posting.link_creator.ui.LinkCreatorFragment
|
||||
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
||||
import java.io.File
|
||||
import java.util.Calendar
|
||||
|
||||
class PostEditingFragment : Fragment(){
|
||||
companion object {
|
||||
const val LOG_TAG = "PostEditingFragment"
|
||||
}
|
||||
|
||||
|
||||
private lateinit var binding: FragmentMarkdownEditingBinding
|
||||
private val viewModel: CreatePostViewModel by activityViewModels()
|
||||
private val linkCreatorViewModel: LinkCreatorViewModel by viewModels()
|
||||
|
||||
|
||||
private val choosePictureLauncher = registerForActivityResult(ActivityResultContracts.PickVisualMedia()) {
|
||||
if(it != null) {
|
||||
viewModel.addPicture(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun makePhotoUri(): Uri {
|
||||
val cacheFile = File(requireContext().filesDir, "temp_picture_${Calendar.getInstance().timeInMillis}")
|
||||
return FileProvider.getUriForFile(requireContext(), "${MyApplication.myApp.packageName}.provider", cacheFile)
|
||||
}
|
||||
|
||||
private var cameraPhotoUri: Uri? = null
|
||||
|
||||
private val takePhotoLauncher = registerForActivityResult(ActivityResultContracts.TakePicture()) {
|
||||
if(it && cameraPhotoUri != null) {
|
||||
viewModel.addPicture(cameraPhotoUri!!)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
|
||||
binding = FragmentMarkdownEditingBinding.inflate(layoutInflater)
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewLifecycleOwner.lifecycle.addObserver(object: LifecycleEventObserver {
|
||||
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
|
||||
Log.d(LOG_TAG, event.toString())
|
||||
}
|
||||
})
|
||||
|
||||
setupListeners()
|
||||
setupObservers()
|
||||
|
||||
binding.composeView.apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setContent {
|
||||
val pictures by viewModel.photos.collectAsState()
|
||||
IsolaattiTheme {
|
||||
ImagesRow(
|
||||
modifier = Modifier.padding(16.dp),
|
||||
images = pictures,
|
||||
deletable = true,
|
||||
addable = true,
|
||||
onClick = {
|
||||
|
||||
},
|
||||
onDeleteClick = {
|
||||
viewModel.removePicture(it)
|
||||
},
|
||||
onTakePicture = {
|
||||
cameraPhotoUri = makePhotoUri()
|
||||
takePhotoLauncher.launch(cameraPhotoUri)
|
||||
},
|
||||
onUploadPicture = {
|
||||
choosePictureLauncher.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
binding.filledTextField.editText?.setText(viewModel.content)
|
||||
binding.filledTextField.requestFocus()
|
||||
binding.filledTextField.editText?.doOnTextChanged { text, _, _, _ ->
|
||||
// make better validation :)
|
||||
viewModel.validation.postValue(!text.isNullOrEmpty())
|
||||
viewModel.content = text.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers(){
|
||||
viewModel.postToEdit.observe(viewLifecycleOwner) {
|
||||
binding.filledTextField.editText?.setText(it.content)
|
||||
}
|
||||
linkCreatorViewModel.inserted.observe(viewLifecycleOwner) {
|
||||
if(it) {
|
||||
viewModel.content += " ${linkCreatorViewModel.markdown}"
|
||||
binding.filledTextField.editText?.setText(viewModel.content)
|
||||
linkCreatorViewModel.inserted.value = false
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.audioAttachment.observe(viewLifecycleOwner) { playable ->
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -5,66 +5,55 @@
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:fitsSystemWindows="true">
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
app:navigationIcon="@drawable/baseline_close_24"
|
||||
app:navigationIconTint="@color/on_surface"
|
||||
app:title="@string/new_post"
|
||||
app:titleCentered="true">
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="100dp"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="?attr/textInputFilledStyle"
|
||||
android:id="@+id/filledTextField"
|
||||
android:layout_width="match_parent"
|
||||
app:boxBackgroundMode="none"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/baseline_close_24"
|
||||
app:navigationIconTint="@color/on_surface"
|
||||
app:title="@string/new_post"
|
||||
app:titleCentered="true">
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
app:hintEnabled="false">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/compose_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"/>
|
||||
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appBarLayout" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progress_bar_loading"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="@+id/postButton"
|
||||
app:layout_constraintTop_toTopOf="@+id/postButton"
|
||||
tools:visibility="visible" />
|
||||
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/postButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/baseline_send_24"
|
||||
android:fitsSystemWindows="true"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@ -1,33 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="100dp"
|
||||
android:layout_marginTop="?attr/actionBarSize"
|
||||
android:clipToPadding="false">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="?attr/textInputFilledStyle"
|
||||
android:id="@+id/filledTextField"
|
||||
android:layout_width="match_parent"
|
||||
app:boxBackgroundMode="none"
|
||||
android:layout_height="wrap_content"
|
||||
app:hintEnabled="false">
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/what_do_you_want_to_talk_about_you_can_record_an_audio_if_you_want"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/compose_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"/>
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
@ -1,15 +0,0 @@
|
||||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Loading…
x
Reference in New Issue
Block a user