This commit is contained in:
erik-everardo 2025-01-18 11:57:46 -06:00
parent 727007d94f
commit 571b893fcd
18 changed files with 183 additions and 129 deletions

View File

@ -1,9 +1,8 @@
package com.isolaatti.images.common.data.remote package com.isolaatti.images.common.data.remote
data class ImageDto( data class ImageDto(
val id: String, val id: String,
val userId: Int, val userId: Int,
val squadId: String?, val squadId: String?
val username: String,
val idOnFirebase: String
) )

View File

@ -34,7 +34,7 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
val response = imagesApi.getImagesOfUser(userId, lastId).awaitResponse() val response = imagesApi.getImagesOfUser(userId, lastId).awaitResponse()
if(response.isSuccessful) { if(response.isSuccessful) {
val imagesDto = response.body() val imagesDto = response.body()
val images = imagesDto?.data?.map { Image.fromDto(it) } val images = imagesDto?.data?.map { Image(it.id) }
emit(Resource.Success(images)) emit(Resource.Success(images))
@ -93,7 +93,7 @@ class ImagesRepositoryImpl @Inject constructor(private val imagesApi: ImagesApi,
emit(Resource.Error(Resource.Error.ErrorType.ServerError)) emit(Resource.Error(Resource.Error.ErrorType.ServerError))
return@flow return@flow
} }
val image = Image.fromDto(imageDto) val image = Image(imageDto.id)
emit(Resource.Success(image)) emit(Resource.Success(image))
} else { } else {
emit(Resource.Error(Resource.Error.mapErrorCode(response.code()))) emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))

View File

@ -1,5 +1,7 @@
package com.isolaatti.images.common.domain.entity package com.isolaatti.images.common.domain.entity
import android.os.Parcel
import android.os.Parcelable
import com.isolaatti.common.Deletable import com.isolaatti.common.Deletable
import com.isolaatti.images.common.data.remote.ImageDto import com.isolaatti.images.common.data.remote.ImageDto
import com.isolaatti.markdown.Generators import com.isolaatti.markdown.Generators
@ -7,19 +9,16 @@ import com.isolaatti.utils.UrlGen
import java.io.Serializable import java.io.Serializable
data class Image( data class Image(
val id: String, val id: String
val userId: Int, ): Deletable(), Parcelable {
val username: String
): Deletable(), Serializable {
val imageUrl: String get() = UrlGen.imageUrl(id) val imageUrl: String get() = UrlGen.imageUrl(id)
val smallImageUrl : String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_SMALL) val smallImageUrl : String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_SMALL)
val reducedImageUrl: String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_REDUCED) val reducedImageUrl: String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_REDUCED)
val markdown: String get() = Generators.generateImage(imageUrl) val markdown: String get() = Generators.generateImage(imageUrl)
companion object { constructor(parcel: Parcel) : this(parcel.readString()!!)
fun fromDto(imageDto: ImageDto) = Image(imageDto.id, imageDto.userId, imageDto.username)
}
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
@ -28,16 +27,30 @@ data class Image(
other as Image other as Image
if (id != other.id) return false if (id != other.id) return false
if (userId != other.userId) return false
if (username != other.username) return false
return true return true
} }
override fun hashCode(): Int { override fun hashCode(): Int {
var result = id.hashCode() var result = id.hashCode()
result = 31 * result + userId
result = 31 * result + username.hashCode()
return result return result
} }
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(id)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Image> {
override fun createFromParcel(parcel: Parcel): Image {
return Image(parcel)
}
override fun newArray(size: Int): Array<Image?> {
return arrayOfNulls(size)
}
}
} }

View File

@ -53,7 +53,7 @@ class PictureViewerImageWrapperFragment : Fragment() {
fun getInstance(image: Image): PictureViewerImageWrapperFragment { fun getInstance(image: Image): PictureViewerImageWrapperFragment {
val fragment = PictureViewerImageWrapperFragment() val fragment = PictureViewerImageWrapperFragment()
fragment.arguments = Bundle().apply { fragment.arguments = Bundle().apply {
putSerializable(ARGUMENT_IMAGE, image) putParcelable(ARGUMENT_IMAGE, image)
} }
return fragment return fragment

View File

@ -19,7 +19,7 @@ class PictureViewerMainFragment : Fragment() {
private val onPageChangeCallback = object: ViewPager2.OnPageChangeCallback() { private val onPageChangeCallback = object: ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) { override fun onPageSelected(position: Int) {
super.onPageSelected(position) super.onPageSelected(position)
binding.imageAuthor.text = images[position].username //binding.imageAuthor.text = images[position].username
} }
} }
@ -42,7 +42,7 @@ class PictureViewerMainFragment : Fragment() {
binding.viewpager.adapter = adapter binding.viewpager.adapter = adapter
binding.viewpager.setCurrentItem(position, false) binding.viewpager.setCurrentItem(position, false)
binding.viewpager.registerOnPageChangeCallback(onPageChangeCallback) binding.viewpager.registerOnPageChangeCallback(onPageChangeCallback)
binding.imageAuthor.text = images[position].username //binding.imageAuthor.text = images[position].username
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -1,9 +1,7 @@
package com.isolaatti.posting.posts.data.remote package com.isolaatti.posting.posts.data.remote
import android.os.Parcel
import android.os.Parcelable
import com.isolaatti.audio.common.data.AudioDto import com.isolaatti.audio.common.data.AudioDto
import java.io.Serializable import com.isolaatti.images.common.data.remote.ImageDto
data class FeedDto( data class FeedDto(
val data: MutableList<PostDto>, val data: MutableList<PostDto>,
@ -18,6 +16,7 @@ data class FeedDto(
return this return this
} }
data class PostDto( data class PostDto(
val post: Post, val post: Post,
var numberOfLikes: Int, var numberOfLikes: Int,
@ -26,17 +25,9 @@ data class FeedDto(
val squadName: String?, val squadName: String?,
var liked: Boolean, var liked: Boolean,
var audio: AudioDto? var audio: AudioDto?
): Parcelable { ){
constructor(parcel: Parcel) : this( val list: MutableList<ImageDto> = mutableListOf()
parcel.readParcelable(Post::class.java.classLoader)!!,
parcel.readInt(),
parcel.readInt(),
parcel.readString()!!,
parcel.readString(),
parcel.readByte() != 0.toByte(),
parcel.readParcelable(AudioDto::class.java.classLoader)
)
data class Post( data class Post(
val id: Long, val id: Long,
@ -47,70 +38,9 @@ data class FeedDto(
var audioId: String?, var audioId: String?,
val squadId: String?, val squadId: String?,
val linkedDiscussionId: Long, val linkedDiscussionId: Long,
val linkedCommentId: Long val linkedCommentId: Long,
) : Parcelable { var images: List<ImageDto>
constructor(parcel: Parcel) : this( )
parcel.readLong(),
parcel.readString() ?: "",
parcel.readInt(),
parcel.readInt(),
parcel.readString() ?: "",
parcel.readString(),
parcel.readString(),
parcel.readLong(),
parcel.readLong()
) {
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeLong(id)
parcel.writeString(textContent)
parcel.writeInt(userId)
parcel.writeInt(privacy)
parcel.writeString(date)
parcel.writeString(audioId)
parcel.writeString(squadId)
parcel.writeLong(linkedDiscussionId)
parcel.writeLong(linkedCommentId)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<Post> {
override fun createFromParcel(parcel: Parcel): Post {
return Post(parcel)
}
override fun newArray(size: Int): Array<Post?> {
return arrayOfNulls(size)
}
}
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(post, flags)
parcel.writeInt(numberOfLikes)
parcel.writeInt(numberOfComments)
parcel.writeString(userName)
parcel.writeString(squadName)
parcel.writeByte(if (liked) 1 else 0)
parcel.writeSerializable(audio)
}
override fun describeContents(): Int {
return 0
}
companion object CREATOR : Parcelable.Creator<PostDto> {
override fun createFromParcel(parcel: Parcel): PostDto {
return PostDto(parcel)
}
override fun newArray(size: Int): Array<PostDto?> {
return arrayOfNulls(size)
}
}
} }
} }

View File

@ -7,10 +7,8 @@ import android.net.Uri
import android.os.Build import android.os.Build
import android.util.Log import android.util.Log
import com.google.gson.Gson 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.ImageDto
import com.isolaatti.images.common.data.remote.ImagesApi 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.CreatePostDto
import com.isolaatti.posting.posts.data.remote.DeletePostDto import com.isolaatti.posting.posts.data.remote.DeletePostDto
import com.isolaatti.posting.posts.data.remote.EditPostDto import com.isolaatti.posting.posts.data.remote.EditPostDto
@ -20,6 +18,7 @@ import com.isolaatti.posting.posts.data.remote.FeedsApi
import com.isolaatti.posting.posts.data.remote.PostApi import com.isolaatti.posting.posts.data.remote.PostApi
import com.isolaatti.posting.posts.data.remote.PostDeletedDto import com.isolaatti.posting.posts.data.remote.PostDeletedDto
import com.isolaatti.posting.posts.data.remote.VersionDto import com.isolaatti.posting.posts.data.remote.VersionDto
import com.isolaatti.posting.posts.domain.PostingSteps
import com.isolaatti.posting.posts.domain.PostsRepository import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.posting.posts.domain.entity.Post import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
@ -82,7 +81,7 @@ class PostsRepositoryImpl @Inject constructor(
} }
} }
override fun makePost(createPostDto: CreatePostDto, images: List<Uri>): Flow<Resource<FeedDto.PostDto>> = flow { override fun makePost(createPostDto: CreatePostDto, images: List<Uri>): Flow<Resource<PostingSteps>> = flow {
emit(Resource.Loading()) emit(Resource.Loading())
try { try {
val hasImages = images.isNotEmpty() val hasImages = images.isNotEmpty()
@ -103,6 +102,7 @@ class PostsRepositoryImpl @Inject constructor(
emit(Resource.Error()) emit(Resource.Error())
return@flow return@flow
} }
emit(Resource.Success(PostingSteps.UploadingPhotos))
images.forEach { imageUri -> images.forEach { imageUri ->
var imageInputStream: InputStream? = null var imageInputStream: InputStream? = null
try { try {
@ -146,20 +146,19 @@ class PostsRepositoryImpl @Inject constructor(
postApi.deletePost(DeletePostDto(postId)).awaitResponse() postApi.deletePost(DeletePostDto(postId)).awaitResponse()
emit(Resource.Error(Resource.Error.ErrorType.ServerError, "Some images were not processed correctly")) emit(Resource.Error(Resource.Error.ErrorType.ServerError, "Some images were not processed correctly"))
} else { } else {
postApi.setIsDraft(postId, false) val setIsDraftCall = postApi.setIsDraft(postId, false).awaitResponse()
val response = postApi.getPost(postId).awaitResponse()
if(response.isSuccessful) { if(setIsDraftCall.isSuccessful) {
emit(Resource.Success(response.body())) emit(Resource.Success(PostingSteps.Finished))
} else { } else {
val msg = "Post posted but could not retrieve updated post data from server" val code = setIsDraftCall.code()
Log.e(LOG_TAG, msg) Log.e(LOG_TAG, "Could not set as \"not draft\"")
emit(Resource.Error(Resource.Error.mapErrorCode(response.code()), msg)) emit(Resource.Error(Resource.Error.mapErrorCode(code), "Could not set as \"not draft\""))
} }
} }
} else { } else {
emit(Resource.Success(result.body())) emit(Resource.Success(PostingSteps.Finished))
return@flow return@flow
} }

View File

@ -0,0 +1,5 @@
package com.isolaatti.posting.posts.domain
enum class PostingSteps {
PostContent, UploadingPhotos, Finished, Unspecified
}

View File

@ -18,7 +18,7 @@ interface PostsRepository {
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>> fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
fun makePost(createPostDto: CreatePostDto, images: List<Uri>): Flow<Resource<FeedDto.PostDto>> fun makePost(createPostDto: CreatePostDto, images: List<Uri>): Flow<Resource<PostingSteps>>
fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>> fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>>
fun deletePost(postId: Long): Flow<Resource<PostDeletedDto>> fun deletePost(postId: Long): Flow<Resource<PostDeletedDto>>
fun loadPost(postId: Long): Flow<Resource<Post>> fun loadPost(postId: Long): Flow<Resource<Post>>

View File

@ -5,6 +5,7 @@ import android.os.Parcelable
import com.isolaatti.audio.common.domain.Audio import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.common.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.common.hashtagRegex import com.isolaatti.common.hashtagRegex
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.posting.posts.data.remote.FeedDto
data class Post( data class Post(
@ -20,7 +21,8 @@ data class Post(
val userName: String, val userName: String,
val squadName: String?, val squadName: String?,
var liked: Boolean, var liked: Boolean,
val audio: Audio? = null val audio: Audio? = null,
val images: List<Image>
) : Ownable, Parcelable { ) : Ownable, Parcelable {
constructor(parcel: Parcel) : this( constructor(parcel: Parcel) : this(
parcel.readLong(), parcel.readLong(),
@ -35,7 +37,8 @@ data class Post(
parcel.readString()!!, parcel.readString()!!,
parcel.readString(), parcel.readString(),
parcel.readByte() != 0.toByte(), parcel.readByte() != 0.toByte(),
parcel.readSerializable() as? Audio parcel.readSerializable() as? Audio,
parcel.readParcelableArray(Image::class.java.classLoader)?.toList() as? List<Image> ?: emptyList()
) { ) {
} }
@ -55,7 +58,8 @@ data class Post(
userName = it.userName, userName = it.userName,
squadName = it.squadName, squadName = it.squadName,
liked = it.liked, liked = it.liked,
audio = it.audio?.let { audioDto -> Audio.fromDto(audioDto) } audio = it.audio?.let { audioDto -> Audio.fromDto(audioDto) },
images = it.post.images.map { imageDto -> Image(imageDto.id) }
) )
}.toMutableList() }.toMutableList()
} }
@ -73,7 +77,8 @@ data class Post(
numberOfLikes = postDto.numberOfLikes, numberOfLikes = postDto.numberOfLikes,
userName = postDto.userName, userName = postDto.userName,
squadName = postDto.squadName, squadName = postDto.squadName,
liked = postDto.liked liked = postDto.liked,
images = postDto.post.images.map { imageDto -> Image(imageDto.id) }
) )
} }
@ -103,6 +108,7 @@ data class Post(
parcel.writeString(squadName) parcel.writeString(squadName)
parcel.writeByte(if (liked) 1 else 0) parcel.writeByte(if (liked) 1 else 0)
parcel.writeSerializable(audio) parcel.writeSerializable(audio)
parcel.writeParcelableArray(images.toTypedArray(), 0)
} }
override fun describeContents(): Int { override fun describeContents(): Int {

View File

@ -2,7 +2,7 @@ package com.isolaatti.posting.posts.domain.use_case
import android.net.Uri import android.net.Uri
import com.isolaatti.posting.posts.data.remote.CreatePostDto import com.isolaatti.posting.posts.data.remote.CreatePostDto
import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.posting.posts.domain.PostingSteps
import com.isolaatti.posting.posts.domain.PostsRepository import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -15,7 +15,7 @@ class MakePost @Inject constructor(private val postsRepository: PostsRepository)
images: List<Uri>, images: List<Uri>,
audioId: String?, audioId: String?,
squadId: String? squadId: String?
): Flow<Resource<FeedDto.PostDto>> { ): Flow<Resource<PostingSteps>> {
return postsRepository.makePost(CreatePostDto(privacy, content, audioId, squadId), images) return postsRepository.makePost(CreatePostDto(privacy, content, audioId, squadId), images)
} }
} }

View File

@ -11,6 +11,7 @@ import com.isolaatti.posting.posts.data.remote.CreatePostDto
import com.isolaatti.posting.posts.data.remote.EditPostDto import com.isolaatti.posting.posts.data.remote.EditPostDto
import com.isolaatti.posting.posts.data.remote.EditPostDto.Companion.PRIVACY_ISOLAATTI import com.isolaatti.posting.posts.data.remote.EditPostDto.Companion.PRIVACY_ISOLAATTI
import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.domain.PostingSteps
import com.isolaatti.posting.posts.domain.entity.Post import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.posting.posts.domain.use_case.EditPost import com.isolaatti.posting.posts.domain.use_case.EditPost
import com.isolaatti.posting.posts.domain.use_case.LoadSinglePost import com.isolaatti.posting.posts.domain.use_case.LoadSinglePost
@ -39,7 +40,6 @@ class CreatePostViewModel @Inject constructor(
} }
val validation: MutableLiveData<Boolean> = MutableLiveData(false) val validation: MutableLiveData<Boolean> = MutableLiveData(false)
val posted: MutableLiveData<Post?> = MutableLiveData()
val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData() val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
val sendingPost: MutableLiveData<Boolean> = MutableLiveData(false) val sendingPost: MutableLiveData<Boolean> = MutableLiveData(false)
val postToEdit: MutableLiveData<EditPostDto> = MutableLiveData() val postToEdit: MutableLiveData<EditPostDto> = MutableLiveData()
@ -49,6 +49,9 @@ class CreatePostViewModel @Inject constructor(
private val _photos: MutableStateFlow<List<Uri>> = MutableStateFlow(emptyList()) private val _photos: MutableStateFlow<List<Uri>> = MutableStateFlow(emptyList())
val photos: StateFlow<List<Uri>> get() = _photos val photos: StateFlow<List<Uri>> get() = _photos
private val _postingStep = MutableStateFlow(PostingSteps.Unspecified)
val postingStep: StateFlow<PostingSteps> get() = _postingStep
val audioAttachment: MutableLiveData<Playable?> = MutableLiveData() val audioAttachment: MutableLiveData<Playable?> = MutableLiveData()
private var audioDraft: Long? = null private var audioDraft: Long? = null
@ -71,8 +74,12 @@ class CreatePostViewModel @Inject constructor(
).onEach { ).onEach {
when(it) { when(it) {
is Resource.Success -> { is Resource.Success -> {
if(it.data == PostingSteps.Finished) {
sendingPost.postValue(false) sendingPost.postValue(false)
posted.postValue(Post.fromPostDto(it.data!!)) }
_postingStep.value = it.data!!
} }
is Resource.Error -> { is Resource.Error -> {
sendingPost.postValue(false) sendingPost.postValue(false)
@ -103,7 +110,6 @@ class CreatePostViewModel @Inject constructor(
when(it) { when(it) {
is Resource.Success -> { is Resource.Success -> {
sendingPost.postValue(false) sendingPost.postValue(false)
posted.postValue(Post.fromPostDto(it.data!!))
} }
is Resource.Error -> { is Resource.Error -> {
sendingPost.postValue(false) sendingPost.postValue(false)

View File

@ -0,0 +1,35 @@
package com.isolaatti.posting.posts.presentation
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import com.isolaatti.images.common.domain.entity.Image
class PostImagesViewPagerAdapter(private val images: List<Image>) :
RecyclerView.Adapter<PostImagesViewPagerAdapter.PostImagesViewPagerAdapterItemViewHolder>() {
class PostImagesViewPagerAdapterItemViewHolder(val imageView: ImageView) : ViewHolder(imageView)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): PostImagesViewPagerAdapterItemViewHolder {
return PostImagesViewPagerAdapterItemViewHolder(
imageView = ImageView(parent.context).apply {
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
}
)
}
override fun getItemCount(): Int = images.size
override fun onBindViewHolder(holder: PostImagesViewPagerAdapterItemViewHolder, position: Int) {
val image = images[position]
holder.imageView.load(image.reducedImageUrl)
}
}

View File

@ -12,6 +12,7 @@ import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.core.text.set import androidx.core.text.set
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load import coil.load
@ -162,6 +163,14 @@ class PostsRecyclerViewAdapter (
itemBinding.audio.root.visibility = View.GONE itemBinding.audio.root.visibility = View.GONE
itemBinding.audio.playButton.setOnClickListener(null) itemBinding.audio.playButton.setOnClickListener(null)
} }
if(post.images.isNotEmpty()) {
itemBinding.photosViewPager.isVisible = true
itemBinding.photosViewPager.adapter = PostImagesViewPagerAdapter(post.images)
} else {
itemBinding.photosViewPager.isVisible = false
itemBinding.photosViewPager.adapter = null
}
itemBinding.shareButton.setOnClickListener { itemBinding.shareButton.setOnClickListener {
callback.onShare(post.id) callback.onShare(post.id)
} }

View File

@ -13,14 +13,18 @@ import androidx.compose.foundation.gestures.ScrollableState
import androidx.compose.foundation.gestures.rememberScrollableState import androidx.compose.foundation.gestures.rememberScrollableState
import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.gestures.scrollable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.BasicAlertDialog
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
@ -29,7 +33,9 @@ import androidx.compose.material3.OutlinedTextFieldDefaults
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
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.setValue import androidx.compose.runtime.setValue
@ -40,6 +46,7 @@ import androidx.compose.ui.graphics.Color
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.compose.ui.window.DialogProperties
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.isolaatti.MyApplication import com.isolaatti.MyApplication
@ -48,8 +55,10 @@ import com.isolaatti.audio.common.components.AudioRecorder
import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.common.IsolaattiTheme import com.isolaatti.common.IsolaattiTheme
import com.isolaatti.posting.posts.components.PostAttachments import com.isolaatti.posting.posts.components.PostAttachments
import com.isolaatti.posting.posts.domain.PostingSteps
import com.isolaatti.posting.posts.presentation.CreatePostViewModel import com.isolaatti.posting.posts.presentation.CreatePostViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import java.io.File import java.io.File
import java.util.Calendar import java.util.Calendar
@ -146,6 +155,33 @@ class CreatePostActivity : IsolaattiBaseActivity() {
} }
) { ) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
val posting by viewModel.sendingPost.observeAsState()
if(posting == true) {
val postingStep by viewModel.postingStep.collectAsState()
AlertDialog(
onDismissRequest = {},
text = {
Row {
CircularProgressIndicator()
Text(
when(postingStep) {
PostingSteps.PostContent -> getString(R.string.posting)
PostingSteps.UploadingPhotos -> getString(R.string.uploading_photos)
PostingSteps.Finished -> ""
PostingSteps.Unspecified -> ""
}
)
}
},
dismissButton = null,
title = {
Text(getString(R.string.posting))
},
confirmButton = {}
)
}
Column(modifier = Modifier.padding(it).verticalScroll(scrollState)) { Column(modifier = Modifier.padding(it).verticalScroll(scrollState)) {
OutlinedTextField( OutlinedTextField(
value = text, value = text,

View File

@ -182,10 +182,7 @@ class ProfileMainFragment : Fragment() {
val profilePictureUrl = profile?.profilePictureUrl val profilePictureUrl = profile?.profilePictureUrl
if(profilePictureUrl != null) { if(profilePictureUrl != null) {
PictureViewerActivity.startActivityWithImages(requireContext(), arrayOf( PictureViewerActivity.startActivityWithImages(requireContext(), arrayOf(
Image( Image(profile.profileImageId ?: "")
profile.profileImageId ?: "",
profile.userId,
profile.uniqueUsername)
)) ))
} }
} }

View File

@ -15,15 +15,20 @@
android:layout_margin="8dp" android:layout_margin="8dp"
> >
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical" android:orientation="vertical"
android:padding="4dp"> android:padding="4dp">
<RelativeLayout android:layout_width="match_parent" <RelativeLayout
android:id="@+id/post_header_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/avatar_picture" android:id="@+id/avatar_picture"
android:layout_width="40dp" android:layout_width="40dp"
@ -65,7 +70,17 @@
layout="@layout/audio_attachment" layout="@layout/audio_attachment"
android:layout_marginHorizontal="16dp" android:layout_marginHorizontal="16dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/post_header_container"/>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/photos_view_pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/audio"
app:layout_constraintDimensionRatio="H,1:1"
android:visibility="gone"
tools:visibility="visible"/>
<TextView <TextView
android:id="@+id/post_content" android:id="@+id/post_content"
@ -76,13 +91,15 @@
android:linksClickable="true" android:linksClickable="true"
android:textSize="16sp" android:textSize="16sp"
tools:text="Hola" tools:text="Hola"
android:fontFamily="sans-serif"/> android:fontFamily="sans-serif"
app:layout_constraintTop_toBottomOf="@id/photos_view_pager"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="8dp" android:layout_marginTop="8dp"
android:gravity="start"> android:gravity="start"
app:layout_constraintTop_toBottomOf="@id/post_content">
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/like_button" android:id="@+id/like_button"
@ -110,7 +127,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:icon="@drawable/baseline_info_24" /> app:icon="@drawable/baseline_info_24" />
</LinearLayout> </LinearLayout>
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
</LinearLayout> </LinearLayout>

View File

@ -218,6 +218,8 @@
<string name="ascending_by_creation_date">older-newer</string> <string name="ascending_by_creation_date">older-newer</string>
<string name="descending_by_creation_date">newer-older</string> <string name="descending_by_creation_date">newer-older</string>
<string name="invalid_arg">Invalid value passed</string> <string name="invalid_arg">Invalid value passed</string>
<string name="posting">Posting</string>
<string name="uploading_photos">Uploading photos</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>