WIP posts en compose

This commit is contained in:
erik-everardo 2025-01-29 00:41:04 -06:00
parent 5775145a69
commit 37837653cc
28 changed files with 237 additions and 593 deletions

View File

@ -101,8 +101,11 @@ dependencies {
// Customtabs
implementation 'androidx.browser:browser:1.8.0'
implementation 'io.coil-kt:coil:2.5.0'
implementation 'io.coil-kt:coil-svg:2.5.0'
// Coil
implementation 'io.coil-kt.coil3:coil:3.0.4'
implementation 'io.coil-kt.coil3:coil-svg:3.0.4'
implementation "io.coil-kt.coil3:coil-compose:3.0.1"
implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.1")
implementation "io.noties.markwon:core:$markwon_version"
@ -156,8 +159,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
implementation 'androidx.compose.runtime:runtime-livedata'
implementation "io.coil-kt.coil3:coil-compose:3.0.1"
implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.1")
implementation("com.google.accompanist:accompanist-permissions:0.36.0")
}

View File

@ -1,35 +1,19 @@
package com.isolaatti.common
import android.content.Context
import coil.ImageLoader
import coil.decode.SvgDecoder
import coil.memory.MemoryCache
import coil3.ImageLoader
import com.isolaatti.MyApplication
object CoilImageLoader {
val imageLoader by lazy {
ImageLoader
.Builder(MyApplication.myApp)
.memoryCache {
MemoryCache.Builder(MyApplication.myApp.applicationContext)
.maxSizePercent(0.25)
.build()
}
.components {
add(SvgDecoder.Factory())
}.build()
}
fun getImageLoader(context: Context): ImageLoader {
return ImageLoader
.Builder(context)
.memoryCache {
MemoryCache.Builder(context)
.maxSizePercent(0.25)
.build()
}
.components {
add(SvgDecoder.Factory())
}.build()
}
}

View File

@ -8,7 +8,7 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import coil3.load
import com.isolaatti.R
import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.databinding.ItemUserListBinding

View File

@ -5,9 +5,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.domain.use_case.GetProfile
import com.isolaatti.utils.Resource

View File

@ -20,7 +20,7 @@ import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import coil.load
import coil3.load
import com.google.android.material.card.MaterialCardView
import com.isolaatti.R
import com.isolaatti.about.AboutActivity

View File

@ -4,8 +4,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.os.BundleCompat
import androidx.fragment.app.Fragment
import coil.load
import coil3.load
import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.databinding.FragmentTouchImageViewWrapperBinding
import com.isolaatti.images.common.domain.entity.RemoteImage
@ -24,7 +25,7 @@ class PictureViewerImageWrapperFragment : Fragment() {
): View {
binding = FragmentTouchImageViewWrapperBinding.inflate(inflater)
image = arguments?.getSerializable(ARGUMENT_IMAGE) as RemoteImage
image = BundleCompat.getParcelable(requireArguments(), ARGUMENT_IMAGE, RemoteImage::class.java)
return binding.root
}

View File

@ -4,6 +4,7 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.IntentCompat
import androidx.fragment.app.Fragment
import androidx.viewpager2.widget.ViewPager2
import com.isolaatti.databinding.FragmentMainPictureViewerBinding
@ -36,7 +37,7 @@ class PictureViewerMainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
images = requireActivity().intent.extras?.getSerializable(PictureViewerActivity.EXTRA_IMAGES) as Array<RemoteImage>
images = IntentCompat.getParcelableArrayExtra(requireActivity().intent, PictureViewerActivity.EXTRA_IMAGES, RemoteImage::class.java) as Array<RemoteImage>
val position = requireActivity().intent.extras?.getInt(PictureViewerActivity.EXTRA_IMAGE_POSITiON) ?: 0
val adapter = PictureViewerViewPagerAdapter(this, images)
binding.viewpager.adapter = adapter

View File

@ -31,7 +31,6 @@ class Module {
.create(BuildConfig.backend))
}
})
.usePlugin(CoilImagesPlugin.create(context, CoilImageLoader.imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()
}

View File

@ -6,7 +6,8 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import coil3.load
import coil3.request.fallback
import com.isolaatti.R
import com.isolaatti.databinding.NotificationItemBinding
import com.isolaatti.notifications.domain.FollowNotification

View File

@ -4,7 +4,7 @@ import android.annotation.SuppressLint
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil3.load
import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.databinding.CommentLayoutBinding
import com.isolaatti.posting.comments.domain.model.Comment

View File

@ -217,7 +217,6 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
.create("https://isolaatti.com/"))
}
})
.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()

View File

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.viewModels
import coil.load
import coil3.load
import com.isolaatti.R
import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.databinding.FragmentEditCommentBinding
@ -51,7 +51,7 @@ class EditCommentDialogFragment : DialogFragment() {
.create("https://isolaatti.com/"))
}
})
.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
//.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()

View File

@ -4,25 +4,22 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.FilledTonalButton
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.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@ -31,11 +28,14 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Devices
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
import com.isolaatti.R
import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.common.domain.entity.RemoteImage
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.UrlGen.userProfileImage
@ -44,13 +44,17 @@ fun Post(
post: Post,
onClick: () -> Unit = {},
onLike: () -> Unit = {},
onDislike: () -> Unit = {},
onComments: () -> Unit = {},
onShare: () -> Unit = {},
onInfo: () -> Unit = {},
onOptions: () -> Unit = {},
onUsernameClick: () -> Unit = {}
onUsernameClick: () -> Unit = {},
onImageClick: (images: List<RemoteImage>, index: Int) -> Unit = {_, _ -> },
onHashtagClick: (hashtag: String) -> Unit = {},
modifier: Modifier = Modifier
) {
Card {
Card(modifier = modifier.padding(8.dp)) {
Row(modifier = Modifier.fillMaxWidth().padding(8.dp), verticalAlignment = Alignment.CenterVertically) {
AsyncImage(
model = userProfileImage(post.userId),
@ -78,37 +82,80 @@ fun Post(
// TODO audio player here
}
if(post.textContent.isNotBlank()) {
Text(post.textContent, modifier = Modifier.padding(8.dp))
}
// TODO pager
if(post.images.isNotEmpty()) {
Box(Modifier.fillMaxWidth().aspectRatio(1f))
Box {
val pagerState = rememberPagerState(pageCount = {post.images.size})
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
) {
val image = post.images[it]
AsyncImage(
image.reducedImageUrl, null,
modifier = Modifier
.fillMaxSize()
.padding(8.dp)
.clip(RoundedCornerShape(20.dp))
.clickable {
onImageClick(post.images, it)
}
)
}
if(post.images.size > 1) {
Text("${pagerState.currentPage + 1} of ${pagerState.pageCount}",
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(20.dp)
.background(Color.Black.copy(alpha = 0.5f)),
color = Color.White
)
}
}
}
Row(modifier = Modifier.height(50.dp).padding(4.dp)) {
FilledTonalIconButton(onClick = onLike, modifier = Modifier.padding(4.dp).weight(1f)) {
FilledTonalIconButton(
onClick = { if(post.liked) { onDislike() } else { onLike() } },
modifier = Modifier.padding(4.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(painterResource(R.drawable.hands_clapping_solid), null, modifier = Modifier.size(30.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Clap")
Text(post.numberOfLikes.toString())
}
Icon(
painterResource(R.drawable.hands_clapping_solid),
null,
modifier = Modifier.padding(horizontal = 8.dp).size(30.dp),
tint = if(post.liked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface
)
Text(
text = post.numberOfLikes.toString(),
modifier = Modifier.weight(1f).padding(horizontal = 4.dp),
textAlign = TextAlign.Center
)
}
}
FilledTonalIconButton(onClick = onComments, modifier = Modifier.padding(4.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(painterResource(R.drawable.comments_solid), null, modifier = Modifier.size(30.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text("Comments")
Text(post.numberOfComments.toString())
Icon(
painterResource(R.drawable.comments_solid),
null,
modifier = Modifier.padding(horizontal = 8.dp).size(30.dp)
)
Text(
text = post.numberOfComments.toString(),
modifier = Modifier.weight(1f).padding(horizontal = 4.dp),
textAlign = TextAlign.Center
)
}
}
}
FilledTonalIconButton(onClick = onShare, modifier = Modifier.padding(4.dp).weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(painterResource(R.drawable.baseline_share_24), null, modifier = Modifier.size(30.dp))
Text("Share")
}
FilledTonalIconButton(onClick = onShare, modifier = Modifier.padding(4.dp)) {
Icon(
painterResource(R.drawable.baseline_share_24),
null,
)
}
FilledTonalIconButton(onClick = onInfo, modifier = Modifier.padding(4.dp)) {
Icon(painterResource(R.drawable.baseline_info_24), null)

View File

@ -2,28 +2,39 @@ package com.isolaatti.posting.posts.domain.entity
import android.os.Parcel
import android.os.Parcelable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.common.Ownable
import com.isolaatti.common.hashtagRegex
import com.isolaatti.images.common.domain.entity.RemoteImage
import com.isolaatti.posting.posts.data.remote.FeedDto
data class Post(
class Post(
val id: Long,
var textContent: String,
textContent: String,
override val userId: Int,
val privacy: Int,
val date: String,
var audioId: String? = null,
val squadId: String? = null,
var numberOfLikes: Int,
var numberOfComments: Int,
numberOfLikes: Int,
numberOfComments: Int,
val userName: String,
val squadName: String? = null,
var liked: Boolean,
liked: Boolean,
val audio: Audio? = null,
val images: List<RemoteImage>
images: List<RemoteImage>
) : Ownable, Parcelable {
var liked by mutableStateOf(liked)
var images by mutableStateOf(images)
var textContent by mutableStateOf(textContent)
var numberOfLikes by mutableIntStateOf(numberOfLikes)
var numberOfComments by mutableStateOf(numberOfComments)
constructor(parcel: Parcel) : this(
parcel.readLong(),
parcel.readString()!!,

View File

@ -4,7 +4,7 @@ import android.view.ViewGroup
import android.widget.ImageView
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import coil3.load
import com.isolaatti.images.common.domain.entity.RemoteImage
class PostImagesViewPagerAdapter(private val images: List<RemoteImage>) :

View File

@ -16,17 +16,13 @@ class PostListingViewModel @Inject constructor(private val postsRepository: Post
override fun getFeed(refresh: Boolean, hashtag: String?) {
viewModelScope.launch {
if (refresh) {
posts.value = null
posts.value = emptyList()
}
postsRepository.getFeed(getLastId(), hashtag).onEach { listResource ->
when (listResource) {
is Resource.Success -> {
val eventType = if((postsList?.size ?: 0) > 0) UpdateEvent.UpdateType.PAGE_ADDED else UpdateEvent.UpdateType.REFRESH
loadingPosts.postValue(false)
posts.postValue(Pair(postsList?.apply {
addAll(listResource.data ?: listOf())
} ?: listResource.data,
UpdateEvent(eventType, null)))
posts.value += listResource.data ?: emptyList()
noMoreContent.postValue(listResource.data?.size == 0)
}

View File

@ -11,6 +11,7 @@ import com.isolaatti.posting.posts.domain.use_case.DeletePost
import com.isolaatti.profile.domain.use_case.GetProfilePosts
import com.isolaatti.utils.Resource
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -30,9 +31,8 @@ abstract class PostListingViewModelBase : ViewModel() {
@Inject
lateinit var deletePostUseCase: DeletePost
val posts: MutableLiveData<Pair<MutableList<Post>?, UpdateEvent>?> = MutableLiveData()
val posts: MutableStateFlow<List<Post>> = MutableStateFlow(emptyList())
val postsList get() = posts.value?.first
val loadingPosts = MutableLiveData(false)
@ -41,7 +41,7 @@ abstract class PostListingViewModelBase : ViewModel() {
val errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
var isLoadingFromScrolling = false
fun getLastId(): Long = try { posts.value?.first?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
fun getLastId(): Long = try { posts.value?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
abstract fun getFeed(refresh: Boolean, hashtag: String?)
@ -58,18 +58,9 @@ abstract class PostListingViewModelBase : ViewModel() {
Log.i(TAG, "Loading likePost($postId)")
}
is Resource.Success -> {
val likedPost = posts.value?.first?.find { post -> post.id == like.data?.postId }
val index = posts.value?.first?.indexOf(likedPost)
if(index != null){
val temp = posts.value?.first?.toMutableList()
Log.d("***", temp.toString())
temp?.set(index, likedPost!!.apply {
liked = true
numberOfLikes = like.data?.likesCount ?: 0
})
}
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, index)))
val likedPost = posts.value?.find { post -> post.id == like.data?.postId }
likedPost?.liked = true
likedPost?.numberOfLikes = like.data!!.likesCount
}
}
@ -90,16 +81,9 @@ abstract class PostListingViewModelBase : ViewModel() {
Log.i(TAG, "Loading unLikePost($postId)")
}
is Resource.Success -> {
val likedPost = posts.value?.first?.find { post -> post.id == like.data?.postId }
val index = posts.value?.first?.indexOf(likedPost)
if(index != null){
val temp = posts.value?.first?.toMutableList()
temp?.set(index, likedPost!!.apply {
liked = false
numberOfLikes = like.data?.likesCount ?: 0
})
}
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, index)))
val likedPost = posts.value?.find { post -> post.id == like.data?.postId }
likedPost?.liked = false
likedPost?.numberOfLikes = like.data!!.likesCount
}
}
@ -113,12 +97,9 @@ abstract class PostListingViewModelBase : ViewModel() {
deletePostUseCase(postId).onEach { res ->
when(res) {
is Resource.Success -> {
val postDeleted = posts.value?.first?.find { post -> post.id == postId }
val postDeleted = posts.value?.find { post -> post.id == postId }
?: return@onEach
val index = posts.value?.first?.indexOf(postDeleted)
posts.value?.first?.removeAt(index!!)
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_REMOVED, index)))
posts.value = posts.value.toMutableList().apply { remove(postDeleted) }
}
is Resource.Loading -> {}
is Resource.Error -> {}

View File

@ -1,402 +0,0 @@
package com.isolaatti.posting.posts.presentation
import android.annotation.SuppressLint
import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.text.set
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView
import com.isolaatti.R
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.common.OnUserInteractedWithPostCallback
import com.isolaatti.databinding.PostLayoutBinding
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.UrlGen.userProfileImage
class PostsRecyclerViewAdapter (
private val callback: OnUserInteractedWithPostCallback
) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
private var postList: List<Post>? = null
inner class FeedViewHolder(val itemBinding: PostLayoutBinding) : ViewHolder(itemBinding.root) {
fun bindView(post: Post, payloads: List<Any>) {
if(payloads.isNotEmpty()) {
for(payload in payloads) {
when {
payload is LikeCountUpdatePayload -> {
itemBinding.likeButton.isEnabled = true
if(post.liked) {
itemBinding.likeButton.setIconTintResource(R.color.purple_lighter)
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter))
} else {
itemBinding.likeButton.setIconTintResource(R.color.on_surface)
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.on_surface))
}
itemBinding.likeButton.text = post.numberOfLikes.toString()
}
payload is CommentsCountUpdatePayload -> {
itemBinding.commentButton.text = post.numberOfComments.toString()
}
payload is AudioEventPayload && payload == AudioEventPayload.IsPLaying -> {
val audio = post.audio
if(audio != null){
itemBinding.audio.playButton.icon =
AppCompatResources.getDrawable(
itemView.context,
if(audio.isPlaying) R.drawable.baseline_pause_circle_24 else R.drawable.baseline_play_circle_24
)
}
}
payload is AudioEventPayload && payload == AudioEventPayload.ProgressChanged -> {
val audio = post.audio
if(audio != null){
itemBinding.audio.audioProgress.progress = audio.progress
}
}
payload is AudioEventPayload && payload == AudioEventPayload.IsLoading -> {
val audio = post.audio
if(audio != null){
itemBinding.audio.audioProgress.isIndeterminate = audio.isLoading
}
}
payload is AudioEventPayload && payload == AudioEventPayload.DurationChanged -> {
val audio = post.audio
if(audio != null){
itemBinding.audio.audioProgress.max = audio.duration
}
}
payload is AudioEventPayload && payload == AudioEventPayload.Ended -> {
val audio = post.audio
if(audio != null){
itemBinding.audio.audioProgress.progress = 0
itemBinding.audio.playButton.icon = AppCompatResources.getDrawable(itemView.context, R.drawable.baseline_play_circle_24)
}
}
}
}
} else {
val username: TextView = itemView.findViewById(R.id.text_view_username)
username.text = post.userName
username.setOnClickListener {
callback.onProfileClick(post.userId)
}
val profileImageView: ImageView = itemView.findViewById(R.id.avatar_picture)
profileImageView.load(userProfileImage(post.userId), imageLoader)
val dateTextView: TextView = itemView.findViewById(R.id.text_view_date)
dateTextView.text = post.date
val content: TextView = itemView.findViewById(R.id.post_content)
content.movementMethod = LinkMovementMethod.getInstance()
val spannableString = SpannableString(post.textContent).apply {
post.hashtagsSpans.forEach {
set(it.first, it.last + 1, object: ClickableSpan() {
override fun onClick(widget: View) {
callback.hashtagClicked(post.textContent.substring(it.first + 1, it.last + 1))
}
})
}
}
content.text = spannableString
itemBinding.likeButton.isEnabled = true
if(post.liked) {
itemBinding.likeButton.setIconTintResource(R.color.purple_lighter)
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter))
} else {
itemBinding.likeButton.setIconTintResource(R.color.on_surface)
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.on_surface))
}
itemBinding.likeButton.text = post.numberOfLikes.toString()
itemBinding.commentButton.text = post.numberOfComments.toString()
val moreButton: MaterialButton = itemView.findViewById(R.id.more_button)
moreButton.setOnClickListener {
callback.onOptions(post)
}
itemBinding.likeButton.setOnClickListener {
itemBinding.likeButton.isEnabled = false
if(post.liked){
callback.onUnLiked(post.id)
} else {
callback.onLiked(post.id)
}
}
itemBinding.commentButton.setOnClickListener {
callback.onComment(post.id)
}
itemView.findViewById<MaterialCardView>(R.id.card).setOnClickListener {
callback.onOpenPost(post.id)
}
if(post.audio != null){
itemBinding.audio.apply {
root.visibility = View.VISIBLE
textViewDescription.text = post.audio.name
}
itemBinding.audio.playButton.setOnClickListener {
callback.onPlay(post.audio)
}
} else {
itemBinding.audio.root.visibility = View.GONE
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 {
callback.onShare(post.id)
}
itemBinding.infoButton.setOnClickListener {
callback.onMoreInfo(post.id)
}
}
}
}
data class LikeCountUpdatePayload(val likeCount: Int)
data class CommentsCountUpdatePayload(val commentsCount: Int)
private var currentAudio: Audio? = null
private var currentAudioPosition: Int = -1
enum class AudioEventPayload {
ProgressChanged, IsLoading, IsPLaying, DurationChanged, Ended
}
fun setIsPlaying(isPlaying: Boolean, audio: Audio) {
if(audio == currentAudio) {
currentAudio?.isPlaying = isPlaying
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsPLaying)
}
return
}
currentAudio?.isPlaying = false
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsPLaying)
} else {
if(postList != null) {
for((index, post) in postList!!.withIndex()){
post.audio?.isPlaying = false
post.audio?.progress = 0
post.audio?.isLoading = false
if(post.audio != null) {
notifyItemChanged(index)
}
}
}
}
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
Log.d(LOG_TAG, "setIsPlaying currentAudioPosition: $currentAudioPosition")
if(currentAudioPosition > -1) {
currentAudio = postList?.get(currentAudioPosition)?.audio?.also { it.isPlaying = isPlaying }
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsPLaying)
}
}
fun setIsLoading(isLoading: Boolean, audio: Audio) {
if(audio == currentAudio) {
currentAudio?.isLoading = isLoading
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsLoading)
}
return
}
currentAudio?.isPlaying = false
currentAudio?.isLoading = false
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsLoading)
}
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
Log.d(LOG_TAG, "setIsLoading currentAudioPosition: $currentAudioPosition")
if(currentAudioPosition > -1) {
postList?.get(currentAudioPosition)?.audio?.isLoading = isLoading
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsLoading)
}
}
fun setProgress(progress: Int, audio: Audio){
if(audio == currentAudio) {
audio.progress = progress
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
return
}
currentAudio?.isPlaying = false
currentAudio?.progress = 0
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
if(currentAudioPosition > -1) {
postList?.get(currentAudioPosition)?.audio?.progress = progress
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
}
fun setDuration(duration: Int, audio: Audio) {
if(audio == currentAudio) {
audio.duration = duration
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
return
}
currentAudio?.isPlaying = false
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
if(currentAudioPosition > -1) {
postList?.get(currentAudioPosition)?.audio?.duration = duration
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
}
fun setEnded(audio: Audio) {
if(audio == currentAudio) {
audio.isPlaying = false
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
return
}
currentAudio?.isPlaying = false
if(currentAudioPosition > -1) {
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
if(currentAudioPosition > -1) {
postList?.get(currentAudioPosition)?.audio?.isPlaying = false
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder {
return FeedViewHolder(PostLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
var previousSize = 0
override fun getItemCount(): Int = postList?.size ?: 0
override fun setHasStableIds(hasStableIds: Boolean) {
super.setHasStableIds(true)
}
@SuppressLint("NotifyDataSetChanged")
fun updateList(updatedFeed: List<Post>, updateEvent: UpdateEvent? = null) {
if(updateEvent == null) {
postList = updatedFeed
notifyDataSetChanged()
return
}
val postUpdated = updateEvent.affectedPosition?.let {
if(updateEvent.updateType == UpdateEvent.UpdateType.POST_REMOVED) {
null
} else {
postList?.get(it)
}
}
val position = updateEvent.affectedPosition
previousSize = itemCount
postList = updatedFeed
when(updateEvent.updateType) {
UpdateEvent.UpdateType.POST_LIKED -> {
if(postUpdated != null && position != null)
notifyItemChanged(position, LikeCountUpdatePayload(postUpdated.numberOfLikes))
}
UpdateEvent.UpdateType.POST_COMMENTED -> {
if(postUpdated != null && position != null)
notifyItemChanged(position, CommentsCountUpdatePayload(postUpdated.numberOfComments))
}
UpdateEvent.UpdateType.POST_REMOVED -> {
if(position != null)
notifyItemRemoved(position)
}
UpdateEvent.UpdateType.POST_ADDED -> {
notifyItemInserted(0)
}
UpdateEvent.UpdateType.PAGE_ADDED -> {
notifyItemInserted(previousSize)
}
UpdateEvent.UpdateType.REFRESH -> {
notifyDataSetChanged()
}
}
}
override fun onBindViewHolder(holder: FeedViewHolder, position: Int) {}
private var requestedNewContent = false
/**
* Call this method when new content has been added on onLoadMore() callback
*/
fun newContentRequestFinished() {
requestedNewContent = false
}
override fun onBindViewHolder(holder: FeedViewHolder, position: Int, payloads: List<Any>) {
holder.bindView(postList?.get(position) ?: return, payloads)
val totalItems = postList?.size
if(totalItems != null && totalItems > 0 && !requestedNewContent) {
if(position == totalItems - 1) {
requestedNewContent = true
if(payloads.isEmpty()) {
callback.onLoadMore()
}
}
}
}
companion object {
const val LOG_TAG = "PostsRecyclerViewAdapter"
}
}

View File

@ -1,12 +0,0 @@
package com.isolaatti.posting.posts.presentation
data class UpdateEvent(val updateType: UpdateType, val affectedPosition: Int?) {
enum class UpdateType {
POST_LIKED,
POST_COMMENTED,
POST_REMOVED,
POST_ADDED,
PAGE_ADDED,
REFRESH
}
}

View File

@ -1,36 +1,48 @@
package com.isolaatti.posting.posts.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.gestures.FlingBehavior
import androidx.compose.foundation.gestures.ScrollableDefaults
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.common.Dialogs
import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.common.IsolaattiTheme
import com.isolaatti.common.OnUserInteractedWithPostCallback
import com.isolaatti.common.Ownable
import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.databinding.FragmentPostListingBinding
import com.isolaatti.hashtags.ui.HashtagsPostsActivity
import com.isolaatti.home.ui.FeedFragment
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.posting.posts.presentation.EditPostContract
import com.isolaatti.posting.posts.presentation.PostListingViewModel
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.reports.data.ContentType
@ -38,7 +50,7 @@ import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
class PostListingFragment : Fragment() {
companion object {
const val ARG_HASHTAG = "hashtag"
const val LOG_TAG = "PostListingFragment"
@ -46,11 +58,9 @@ class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
var hashtag: String? = null
private lateinit var viewBinding: FragmentPostListingBinding
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
private val viewModel: PostListingViewModel by viewModels()
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
private lateinit var adapter: PostsRecyclerViewAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -59,43 +69,75 @@ class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
viewModel.getFeed(false, hashtag)
}
@OptIn(ExperimentalMaterial3Api::class)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewBinding = FragmentPostListingBinding.inflate(inflater, container, false)
return viewBinding.root
return ComposeView(requireContext()).apply {
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
setContent {
var isRefreshing by remember { mutableStateOf(false) }
val posts by viewModel.posts.collectAsState(emptyList())
IsolaattiTheme {
PullToRefreshBox(isRefreshing, onRefresh = {viewModel.getFeed(true, null)}) {
LazyColumn(flingBehavior = ScrollableDefaults.flingBehavior()) {
items(count = posts.size, key = {posts[it].id}) {
val post = posts[it]
com.isolaatti.posting.posts.components.Post(
modifier = Modifier.animateItem(),
post = post,
onClick = {
PostViewerActivity.startActivity(requireContext(), post.id)
},
onComments = {
val modalBottomSheet = BottomSheetPostComments.getInstance(post.id)
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostComments.TAG)
},
onOptions = {
optionsViewModel.setOptions(Options.POST_OPTIONS, FeedFragment.CALLER_ID, post)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
},
onInfo = {
PostInfoActivity.startActivity(requireContext(), post.id)
},
onLike = {
viewModel.likePost(post.id)
},
onDislike = {
viewModel.unLikePost(post.id)
},
onShare = {
val intent = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "${BuildConfig.backend}/pub/${post.id}")
type = "text/plain"
}, getString(R.string.share_post))
startActivity(intent)
},
onImageClick = { images, index ->
PictureViewerActivity.startActivityWithImages(requireContext(), images.toTypedArray(), index)
},
onUsernameClick = {
ProfileActivity.startActivity(requireContext(), post.userId)
},
onHashtagClick = { hashtag ->
HashtagsPostsActivity.startActivity(requireContext(), hashtag)
}
)
}
}
}
}
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = PostsRecyclerViewAdapter(this)
viewBinding.feedRecyclerView.adapter = adapter
viewBinding.feedRecyclerView.setItemViewCacheSize(7)
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext())
viewBinding.swipeToRefresh.setOnRefreshListener {
viewModel.getFeed(refresh = true, hashtag)
}
viewModel.posts.observe(viewLifecycleOwner){
if (it?.first != null) {
adapter.updateList(it.first!!, it.second)
adapter.newContentRequestFinished()
}
}
viewModel.loadingPosts.observe(viewLifecycleOwner) {
viewBinding.loadingIndicator.visibility = if(it) View.VISIBLE else View.GONE
if(!it) {
viewBinding.swipeToRefresh.isRefreshing = false
}
}
viewModel.errorLoading.observe(viewLifecycleOwner) {
errorViewModel.error.postValue(it)
}
@ -103,19 +145,17 @@ class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
}
/*
override fun onLiked(postId: Long) = viewModel.likePost(postId)
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
override fun onOptions(post: Ownable) {
optionsViewModel.setOptions(Options.POST_OPTIONS, FeedFragment.CALLER_ID, post)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
override fun onComment(postId: Long) {
val modalBottomSheet = BottomSheetPostComments.getInstance(postId)
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostComments.TAG)
}
override fun onOpenPost(postId: Long) {
@ -126,7 +166,7 @@ class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
}
override fun onMoreInfo(postId: Long) {
PostInfoActivity.startActivity(requireContext(), postId)
}
override fun onShare(postId: Long) {
@ -150,6 +190,8 @@ class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
viewModel.getFeed(false, hashtag)
}
*/
private val editDiscussion = registerForActivityResult(EditPostContract()) {
if(it != null) {
viewModel.onPostUpdate(it)

View File

@ -60,10 +60,10 @@ class PostViewerViewModel @Inject constructor(private val loadSinglePost: LoadSi
}
private fun updateLikesCount(likesCount: Int) {
val updatedPost = post.value?.copy(numberOfLikes = likesCount)
if(updatedPost != null) {
post.postValue(updatedPost!!)
}
// val updatedPost = post.value?.copy(numberOfLikes = likesCount)
// if(updatedPost != null) {
// post.postValue(updatedPost!!)
// }
}
fun likeDislikePost() {

View File

@ -8,8 +8,8 @@ import android.util.Log
import androidx.activity.viewModels
import androidx.core.app.TaskStackBuilder
import androidx.core.content.res.ResourcesCompat
import coil.imageLoader
import coil.load
import coil3.imageLoader
import coil3.load
import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.common.IsolaattiBaseActivity
@ -153,7 +153,6 @@ class PostViewerActivity : IsolaattiBaseActivity() {
.create(BuildConfig.backend))
}
})
.usePlugin(CoilImagesPlugin.create(this, imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()

View File

@ -1,12 +1,12 @@
package com.isolaatti.profile.presentation
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.isolaatti.followers.domain.FollowingState
import com.isolaatti.images.common.domain.entity.RemoteImage
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.domain.use_case.FollowUser
import com.isolaatti.profile.domain.use_case.GetProfile
@ -116,14 +116,14 @@ class ProfileViewModel @Inject constructor(
override fun getFeed(refresh: Boolean, hashtag: String?) {
viewModelScope.launch {
if(refresh) {
posts.value = Pair(null, UpdateEvent(UpdateEvent.UpdateType.REFRESH, null))
getLastId()
posts.value = emptyList()
}
Log.d(TAG, "getFeed")
getProfilePostsUseCase(profileId, getLastId(), false, null).onEach { feedDtoResource ->
when (feedDtoResource) {
is Resource.Success -> {
loadingPosts.postValue(false)
posts.postValue(Pair(posts.value?.first?.apply { addAll(feedDtoResource.data ?: listOf()) } ?: feedDtoResource.data, UpdateEvent(if(refresh) UpdateEvent.UpdateType.REFRESH else UpdateEvent.UpdateType.PAGE_ADDED, null)))
posts.value += feedDtoResource.data ?: emptyList()
noMoreContent.postValue(feedDtoResource.data?.size == 0)
}

View File

@ -17,7 +17,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import coil.load
import coil3.load
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.BuildConfig
import com.isolaatti.R
@ -40,8 +40,6 @@ import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.posting.posts.presentation.CreatePostContract
import com.isolaatti.posting.posts.presentation.EditPostContract
import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.posting.posts.ui.PostInfoActivity
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.profile.domain.entity.UserProfile
@ -67,7 +65,6 @@ class ProfileMainFragment : Fragment() {
val errorViewModel: ErrorMessageViewModel by activityViewModels()
private var userId: Int? = null
lateinit var postsAdapter: PostsRecyclerViewAdapter
private var audioDescriptionAudio: Audio? = null
@ -129,13 +126,13 @@ class ProfileMainFragment : Fragment() {
setupUiForUserType(profile.isUserItself)
}
private val postsObserver: Observer<Pair<List<Post>?, UpdateEvent>?> = Observer {
if(it?.first != null) {
postsAdapter.updateList(it.first!!, it.second)
postsAdapter.newContentRequestFinished()
}
}
// private val postsObserver: Observer<Pair<List<Post>?, UpdateEvent>?> = Observer {
// if(it?.first != null) {
// postsAdapter.updateList(it.first!!, it.second)
// postsAdapter.newContentRequestFinished()
// }
//
// }
private val followingStateObserver: Observer<FollowingState> = Observer {
when(it) {
@ -310,8 +307,8 @@ class ProfileMainFragment : Fragment() {
findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToMainFollowersFragment(userId!!))
}
viewBinding.feedRecyclerView.adapter = postsAdapter
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
// viewBinding.feedRecyclerView.adapter = postsAdapter
// viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
viewBinding.swipeToRefresh.setOnRefreshListener {
viewModel.getFeed(true, null)
@ -330,7 +327,7 @@ class ProfileMainFragment : Fragment() {
private fun setObservers() {
viewModel.profile.observe(viewLifecycleOwner, profileObserver)
viewModel.posts.observe(viewLifecycleOwner, postsObserver)
//viewModel.posts.observe(viewLifecycleOwner, postsObserver)
viewModel.followingState.observe(viewLifecycleOwner, followingStateObserver)
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
viewModel.loadingPosts.observe(viewLifecycleOwner) {
@ -366,11 +363,10 @@ class ProfileMainFragment : Fragment() {
.create(BuildConfig.backend))
}
})
.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()
postsAdapter = PostsRecyclerViewAdapter(postListingRecyclerViewAdapterWiring )
//postsAdapter = PostsRecyclerViewAdapter(postListingRecyclerViewAdapterWiring )
}

View File

@ -7,7 +7,9 @@ import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.graphics.drawable.toBitmap
import coil.request.ImageRequest
import coil3.request.ImageRequest
import coil3.request.fallback
import coil3.toBitmap
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import com.isolaatti.MyApplication

View File

@ -5,7 +5,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import coil3.load
import com.isolaatti.R
import com.isolaatti.databinding.SearchResultItemBinding
import com.isolaatti.search.data.SearchResultDto

View File

@ -5,7 +5,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load
import coil3.load
import com.isolaatti.R
import com.isolaatti.databinding.UsersCarouselItemBinding
import com.isolaatti.profile.domain.entity.ProfileListItem

View File

@ -7,7 +7,7 @@ import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController
import coil.load
import coil3.load
import com.isolaatti.databinding.FragmentSettingsBinding
import com.isolaatti.settings.presentation.SettingsViewModel
import dagger.hilt.android.AndroidEntryPoint