WIP quien dio like a post e historial de post

This commit is contained in:
erik-everardo 2024-04-21 14:17:41 -06:00
parent e12bbb2e94
commit ba9aa4c395
29 changed files with 634 additions and 22 deletions

View File

@ -44,6 +44,7 @@
<activity android:name=".profile.ui.EditProfileActivity" android:theme="@style/Theme.Isolaatti" /> <activity android:name=".profile.ui.EditProfileActivity" android:theme="@style/Theme.Isolaatti" />
<activity android:name=".audio.recorder.ui.AudioRecorderActivity" android:theme="@style/Theme.Isolaatti" /> <activity android:name=".audio.recorder.ui.AudioRecorderActivity" android:theme="@style/Theme.Isolaatti" />
<activity android:name=".audio.audio_selector.ui.AudioSelectorActivity" android:theme="@style/Theme.Isolaatti" /> <activity android:name=".audio.audio_selector.ui.AudioSelectorActivity" android:theme="@style/Theme.Isolaatti" />
<activity android:name=".posting.posts.ui.PostInfoActivity" android:theme="@style/Theme.Isolaatti"/>
<provider <provider
android:authorities="com.isolaatti.provider" android:authorities="com.isolaatti.provider"
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"

View File

@ -8,4 +8,6 @@ interface OnUserInteractedWithPostCallback : OnUserInteractedCallback {
fun onComment(postId: Long) fun onComment(postId: Long)
fun onOpenPost(postId: Long) fun onOpenPost(postId: Long)
fun onPlay(audio: Audio) fun onPlay(audio: Audio)
fun onMoreInfo(postId: Long)
fun onShare(postId: Long)
} }

View File

@ -10,13 +10,13 @@ import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.common.UserItemCallback import com.isolaatti.common.UserItemCallback
import com.isolaatti.common.UserListRecyclerViewAdapter import com.isolaatti.common.UserListRecyclerViewAdapter
import com.isolaatti.databinding.FragmentFollowersBinding import com.isolaatti.databinding.FragmentUserListBinding
import com.isolaatti.followers.presentation.FollowersViewModel import com.isolaatti.followers.presentation.FollowersViewModel
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.profile.ui.ProfileActivity
class FollowersFragment : Fragment(), UserItemCallback { class FollowersFragment : Fragment(), UserItemCallback {
private lateinit var binding: FragmentFollowersBinding private lateinit var binding: FragmentUserListBinding
private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() }) private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() })
private lateinit var adapter: UserListRecyclerViewAdapter private lateinit var adapter: UserListRecyclerViewAdapter
@ -25,7 +25,7 @@ class FollowersFragment : Fragment(), UserItemCallback {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentFollowersBinding.inflate(inflater) binding = FragmentUserListBinding.inflate(inflater)
return binding.root return binding.root
} }

View File

@ -10,13 +10,13 @@ import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.common.UserItemCallback import com.isolaatti.common.UserItemCallback
import com.isolaatti.common.UserListRecyclerViewAdapter import com.isolaatti.common.UserListRecyclerViewAdapter
import com.isolaatti.databinding.FragmentFollowersBinding import com.isolaatti.databinding.FragmentUserListBinding
import com.isolaatti.followers.presentation.FollowersViewModel import com.isolaatti.followers.presentation.FollowersViewModel
import com.isolaatti.profile.domain.entity.ProfileListItem import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.profile.ui.ProfileActivity
class FollowingFragment : Fragment(), UserItemCallback { class FollowingFragment : Fragment(), UserItemCallback {
private lateinit var binding: FragmentFollowersBinding private lateinit var binding: FragmentUserListBinding
private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() }) private val viewModel: FollowersViewModel by viewModels({ requireParentFragment() })
private lateinit var adapter: UserListRecyclerViewAdapter private lateinit var adapter: UserListRecyclerViewAdapter
@ -27,7 +27,7 @@ class FollowingFragment : Fragment(), UserItemCallback {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
binding = FragmentFollowersBinding.inflate(inflater) binding = FragmentUserListBinding.inflate(inflater)
return binding.root return binding.root
} }

View File

@ -0,0 +1,38 @@
package com.isolaatti.markdown
import android.content.Context
import com.isolaatti.BuildConfig
import com.isolaatti.common.CoilImageLoader
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.components.ActivityComponent
import dagger.hilt.android.qualifiers.ActivityContext
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
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
@Module
@InstallIn(SingletonComponent::class)
class Module {
@Provides
fun provideMarkwon(@ApplicationContext context: Context): Markwon {
return Markwon.builder(context)
.usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder
.imageDestinationProcessor(
ImageDestinationProcessorRelativeToAbsolute
.create(BuildConfig.backend))
}
})
.usePlugin(CoilImagesPlugin.create(context, CoilImageLoader.imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()
}
}

View File

@ -15,11 +15,9 @@ interface FeedsApi {
@Query("olderFirst") olderFirst: Boolean, @Query("olderFirst") olderFirst: Boolean,
@Query(value = "filterJson", encoded = false) filter: String): Call<FeedDto> @Query(value = "filterJson", encoded = false) filter: String): Call<FeedDto>
@GET("Fetch/Post/{postId}")
fun getPost(@Path("postId") postId: Long): Call<FeedDto>
@GET("Fetch/Post/{postId}/LikedBy") @GET("Fetch/Post/{postId}/LikedBy")
fun getLikedBy(@Path("postId") postId: Long): Call<List<ProfileListItemDto>> fun getLikedBy(@Path("postId") postId: Long): Call<ResultDto<List<ProfileListItemDto>>>
@GET("Feed") @GET("Feed")
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto> fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>

View File

@ -1,10 +1,12 @@
package com.isolaatti.posting.posts.data.remote package com.isolaatti.posting.posts.data.remote
import com.isolaatti.common.ResultDto
import retrofit2.Call import retrofit2.Call
import retrofit2.http.Body import retrofit2.http.Body
import retrofit2.http.GET import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
import retrofit2.http.Query
interface PostApi { interface PostApi {
@POST("Posting/Make") @POST("Posting/Make")
@ -19,4 +21,7 @@ interface PostApi {
@GET("Fetch/Post/{postId}") @GET("Fetch/Post/{postId}")
fun getPost(@Path("postId") postId: Long): Call<FeedDto.PostDto> fun getPost(@Path("postId") postId: Long): Call<FeedDto.PostDto>
@GET("Posting/Post/{postId}/Versions")
fun getVersions(@Path("postId") postId: Long): Call<ResultDto<List<VersionDto>>>
} }

View File

@ -0,0 +1,11 @@
package com.isolaatti.posting.posts.data.remote
import java.time.ZonedDateTime
data class VersionDto(
val id: Long,
val postId: Long,
val textContent: String,
val dateTime: ZonedDateTime,
val audioId: String
)

View File

@ -10,8 +10,10 @@ import com.isolaatti.posting.posts.data.remote.FeedFilterDto
import com.isolaatti.posting.posts.data.remote.FeedsApi 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.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.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
@ -120,4 +122,40 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
} }
} }
override fun getUsersLikedPost(postId: Long): Flow<Resource<List<ProfileListItem>>> = flow {
emit(Resource.Loading())
try {
val response = feedsApi.getLikedBy(postId).awaitResponse()
if(response.isSuccessful) {
val dto = response.body()
if(dto != null) {
emit(Resource.Success(dto.result.map { ProfileListItem.fromDto(it) }))
return@flow
}
}
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
} catch(_: Exception) {
emit(Resource.Error(Resource.Error.ErrorType.OtherError))
}
}
override fun getPostVersions(postId: Long): Flow<Resource<List<VersionDto>>> = flow {
emit(Resource.Loading())
try {
val response = postApi.getVersions(postId).awaitResponse()
if(response.isSuccessful) {
val dto = response.body()
if(dto != null) {
emit(Resource.Success(dto.result))
}
return@flow
}
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
} catch(e: Exception) {
Log.e(LOG_TAG, "Error getting post versions. postId: $postId. \n ${e.message}")
emit(Resource.Error(Resource.Error.ErrorType.OtherError))
}
}
} }

View File

@ -5,7 +5,9 @@ import com.isolaatti.posting.posts.data.remote.EditPostDto
import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.data.remote.FeedFilterDto import com.isolaatti.posting.posts.data.remote.FeedFilterDto
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.domain.entity.Post import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -19,4 +21,7 @@ interface PostsRepository {
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>>
fun getUsersLikedPost(postId: Long): Flow<Resource<List<ProfileListItem>>>
fun getPostVersions(postId: Long): Flow<Resource<List<VersionDto>>>
} }

View File

@ -0,0 +1,8 @@
package com.isolaatti.posting.posts.domain.use_case
import com.isolaatti.posting.posts.domain.PostsRepository
import javax.inject.Inject
class GetPostLikedBy @Inject constructor(private val postsRepository: PostsRepository) {
operator fun invoke(postId: Long) = postsRepository.getUsersLikedPost(postId)
}

View File

@ -0,0 +1,17 @@
package com.isolaatti.posting.posts.presentation
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.isolaatti.posting.posts.ui.PostLikesFragment
import com.isolaatti.posting.posts.ui.PostVersionsFragment
class PostInfoViewPagerAdapter(fragmentActivity: FragmentActivity, private val postId: Long) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment = if(position == 0) {
PostLikesFragment.getInstance(postId)
} else{
PostVersionsFragment.getInstance(postId)
}
}

View File

@ -0,0 +1,36 @@
package com.isolaatti.posting.posts.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.posts.domain.use_case.GetPostLikedBy
import com.isolaatti.profile.domain.entity.ProfileListItem
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 PostLikesViewModel @Inject constructor(private val getPostLikedBy: GetPostLikedBy) : ViewModel() {
var postId: Long = 0
val users: MutableLiveData<List<ProfileListItem>> = MutableLiveData()
val loading: MutableLiveData<Boolean> = MutableLiveData()
fun getUsers() {
viewModelScope.launch {
getPostLikedBy(postId).onEach { resource ->
when(resource) {
is Resource.Error -> {}
is Resource.Loading -> loading.postValue(true)
is Resource.Success -> {
loading.postValue(false)
users.postValue(resource.data!!)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -0,0 +1,39 @@
package com.isolaatti.posting.posts.presentation
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.isolaatti.R
import com.isolaatti.databinding.ItemPostVersionBinding
import com.isolaatti.posting.posts.data.remote.VersionDto
import io.noties.markwon.Markwon
class PostVersionsAdapter(private val markwon: Markwon) : ListAdapter<VersionDto, PostVersionsAdapter.PostVersionViewHolder>(itemCallback) {
inner class PostVersionViewHolder(val binding: ItemPostVersionBinding) : ViewHolder(binding.root)
companion object {
val itemCallback = object: DiffUtil.ItemCallback<VersionDto>() {
override fun areItemsTheSame(oldItem: VersionDto, newItem: VersionDto): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: VersionDto, newItem: VersionDto): Boolean {
return oldItem == newItem
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostVersionViewHolder {
return PostVersionViewHolder(ItemPostVersionBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: PostVersionViewHolder, position: Int) {
val item = getItem(position)
markwon.setMarkdown(holder.binding.postVersionContent, item.textContent)
holder.binding.postVersionDate.text = holder.itemView.context?.getString(R.string.edited_at, item.dateTime.toString())
}
}

View File

@ -0,0 +1,36 @@
package com.isolaatti.posting.posts.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.posts.data.remote.VersionDto
import com.isolaatti.posting.posts.domain.PostsRepository
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 PostVersionsViewModel @Inject constructor(private val postsRepository: PostsRepository) : ViewModel() {
val versions: MutableLiveData<List<VersionDto>> = MutableLiveData()
val loading: MutableLiveData<Boolean> = MutableLiveData()
var postId = 0L
fun getPostVersions() {
viewModelScope.launch {
postsRepository.getPostVersions(postId).onEach { resource ->
when(resource) {
is Resource.Error -> {}
is Resource.Loading -> loading.postValue(true)
is Resource.Success -> {
loading.postValue(false)
versions.postValue(resource.data!!)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -150,6 +150,12 @@ class PostsRecyclerViewAdapter (
itemBinding.audio.root.visibility = View.GONE itemBinding.audio.root.visibility = View.GONE
itemBinding.audio.playButton.setOnClickListener(null) itemBinding.audio.playButton.setOnClickListener(null)
} }
itemBinding.shareButton.setOnClickListener {
callback.onShare(post.id)
}
itemBinding.infoButton.setOnClickListener {
callback.onMoreInfo(post.id)
}
} }
} }
} }

View File

@ -0,0 +1,46 @@
package com.isolaatti.posting.posts.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.google.android.material.tabs.TabLayoutMediator
import com.isolaatti.R
import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityPostInfoBinding
import com.isolaatti.posting.posts.presentation.PostInfoViewPagerAdapter
class PostInfoActivity : IsolaattiBaseActivity() {
private lateinit var binding: ActivityPostInfoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPostInfoBinding.inflate(layoutInflater)
setContentView(binding.root)
val postId = intent.extras?.getLong(EXTRA_POST_ID) ?: 0
if(postId != 0L) {
binding.viewPagerPostInfo.adapter = PostInfoViewPagerAdapter(this, postId)
TabLayoutMediator(binding.tabsPostInfo, binding.viewPagerPostInfo) { tab, position ->
if(position == 0) {
tab.text = getString(R.string.people_who_clapped)
} else {
tab.text = getString(R.string.post_history)
}
}.attach()
} else {
finish()
}
}
companion object {
const val EXTRA_POST_ID = "postId"
fun startActivity(context: Context, postId: Long) {
val intent = Intent(context, PostInfoActivity::class.java).apply {
putExtra(EXTRA_POST_ID, postId)
}
context.startActivity(intent)
}
}
}

View File

@ -0,0 +1,82 @@
package com.isolaatti.posting.posts.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.common.ListUpdateEvent
import com.isolaatti.common.UpdateEvent
import com.isolaatti.common.UserItemCallback
import com.isolaatti.common.UserListRecyclerViewAdapter
import com.isolaatti.databinding.FragmentUserListBinding
import com.isolaatti.posting.posts.presentation.PostLikesViewModel
import com.isolaatti.profile.domain.entity.ProfileListItem
import com.isolaatti.profile.ui.ProfileActivity
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class PostLikesFragment : Fragment() {
private lateinit var binding: FragmentUserListBinding
private val viewModel: PostLikesViewModel by viewModels()
private var adapter: UserListRecyclerViewAdapter? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentUserListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.postId = arguments?.getLong(ARG_POST_ID) ?: 0
if(viewModel.postId != 0L)
viewModel.getUsers()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = UserListRecyclerViewAdapter(object: UserItemCallback {
override fun itemClick(userId: Int) {
ProfileActivity.startActivity(requireContext(), userId)
}
override fun followButtonClick(
user: ProfileListItem,
action: UserItemCallback.FollowButtonAction
) {
}
})
binding.recyclerUsers.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.recyclerUsers.adapter = adapter
binding.swipeToRefresh.setOnRefreshListener {
viewModel.getUsers()
}
viewModel.users.observe(viewLifecycleOwner) {
adapter?.updateData(it, UpdateEvent(ListUpdateEvent.Refresh, arrayOf()))
}
viewModel.loading.observe(viewLifecycleOwner) {
binding.swipeToRefresh.isRefreshing = it
}
}
companion object {
const val ARG_POST_ID = "postId"
fun getInstance(postId: Long): PostLikesFragment {
return PostLikesFragment().apply {
arguments = Bundle().apply {
putLong(ARG_POST_ID, postId)
}
}
}
}
}

View File

@ -1,5 +1,7 @@
package com.isolaatti.posting.posts.ui package com.isolaatti.posting.posts.ui
import android.content.Intent
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -11,6 +13,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.BuildConfig import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.audio.common.domain.Audio import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.common.domain.Playable import com.isolaatti.audio.common.domain.Playable
import com.isolaatti.audio.player.AudioPlayerConnector import com.isolaatti.audio.player.AudioPlayerConnector
@ -24,7 +27,6 @@ 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.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.databinding.FragmentPostListingBinding import com.isolaatti.databinding.FragmentPostListingBinding
import com.isolaatti.home.presentation.FeedViewModel
import com.isolaatti.home.ui.FeedFragment import com.isolaatti.home.ui.FeedFragment
import com.isolaatti.posting.comments.ui.BottomSheetPostComments import com.isolaatti.posting.comments.ui.BottomSheetPostComments
import com.isolaatti.posting.posts.domain.entity.Post import com.isolaatti.posting.posts.domain.entity.Post
@ -177,6 +179,19 @@ class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
audioPlayerConnector.playPauseAudio(audio) audioPlayerConnector.playPauseAudio(audio)
} }
override fun onMoreInfo(postId: Long) {
PostInfoActivity.startActivity(requireContext(), postId)
}
override fun onShare(postId: Long) {
val intent = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "${BuildConfig.backend}/pub/${postId}")
type = "text/plain"
}, getString(R.string.share_post))
startActivity(intent)
}
override fun onProfileClick(userId: Int) { override fun onProfileClick(userId: Int) {
ProfileActivity.startActivity(requireContext(), userId) ProfileActivity.startActivity(requireContext(), userId)
} }

View File

@ -0,0 +1,77 @@
package com.isolaatti.posting.posts.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.databinding.FragmentPostVersionsBinding
import com.isolaatti.posting.posts.presentation.PostVersionsAdapter
import com.isolaatti.posting.posts.presentation.PostVersionsViewModel
import dagger.hilt.android.AndroidEntryPoint
import io.noties.markwon.Markwon
import javax.inject.Inject
@AndroidEntryPoint
class PostVersionsFragment : Fragment() {
private lateinit var binding: FragmentPostVersionsBinding
private val viewModel: PostVersionsViewModel by viewModels()
private var adapter: PostVersionsAdapter? = null
@Inject lateinit var markwon: Markwon
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentPostVersionsBinding.inflate(inflater, container, false)
return binding.root
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.postId = arguments?.getLong(ARG_POST_ID) ?: 0
if(viewModel.postId != 0L) {
viewModel.getPostVersions()
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = PostVersionsAdapter(markwon)
binding.recyclerView.adapter = adapter
binding.recyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
binding.swipeToRefresh.setOnRefreshListener {
viewModel.getPostVersions()
}
viewModel.versions.observe(viewLifecycleOwner) {
adapter?.submitList(it)
}
viewModel.loading.observe(viewLifecycleOwner) {
binding.swipeToRefresh.isRefreshing = it
}
}
companion object {
const val LOG_TAG = "PostVersionsFragment"
const val ARG_POST_ID = "postId"
fun getInstance(postId: Long): PostVersionsFragment {
return PostVersionsFragment().apply {
arguments = Bundle().apply {
putLong(ARG_POST_ID, postId)
}
}
}
}
}

View File

@ -15,6 +15,7 @@ import com.isolaatti.R
import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityPostViewerBinding import com.isolaatti.databinding.ActivityPostViewerBinding
import com.isolaatti.posting.comments.ui.BottomSheetPostComments import com.isolaatti.posting.comments.ui.BottomSheetPostComments
import com.isolaatti.posting.posts.ui.PostInfoActivity
import com.isolaatti.posting.posts.viewer.presentation.PostViewerViewModel import com.isolaatti.posting.posts.viewer.presentation.PostViewerViewModel
import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
@ -121,6 +122,10 @@ class PostViewerActivity : IsolaattiBaseActivity() {
finish() finish()
} }
binding.likesInfo.setOnClickListener {
PostInfoActivity.startActivity(this, postId)
}
} }
override fun onNewIntent(intent: Intent?) { override fun onNewIntent(intent: Intent?) {

View File

@ -1,6 +1,7 @@
package com.isolaatti.profile.ui package com.isolaatti.profile.ui
import android.content.Context import android.content.Context
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -37,7 +38,6 @@ import com.isolaatti.databinding.FragmentDiscussionsBinding
import com.isolaatti.followers.domain.FollowingState import com.isolaatti.followers.domain.FollowingState
import com.isolaatti.images.image_chooser.ui.ImageChooserContract import com.isolaatti.images.image_chooser.ui.ImageChooserContract
import com.isolaatti.images.image_list.ui.ImagesFragment import com.isolaatti.images.image_list.ui.ImagesFragment
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
import com.isolaatti.posting.comments.ui.BottomSheetPostComments import com.isolaatti.posting.comments.ui.BottomSheetPostComments
import com.isolaatti.posting.posts.domain.entity.Post import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.posting.posts.presentation.CreatePostContract import com.isolaatti.posting.posts.presentation.CreatePostContract
@ -45,6 +45,7 @@ import com.isolaatti.posting.posts.presentation.EditPostContract
import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.posting.posts.presentation.UpdateEvent 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.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.profile.domain.entity.UserProfile import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.presentation.EditProfileContract import com.isolaatti.profile.presentation.EditProfileContract
@ -472,6 +473,19 @@ class ProfileMainFragment : Fragment() {
override fun onLoadMore() { override fun onLoadMore() {
viewModel.getFeed(false, null) viewModel.getFeed(false, null)
} }
override fun onMoreInfo(postId: Long) {
PostInfoActivity.startActivity(requireContext(), postId)
}
override fun onShare(postId: Long) {
val intent = Intent.createChooser(Intent().apply {
action = Intent.ACTION_SEND
putExtra(Intent.EXTRA_TEXT, "${BuildConfig.backend}/pub/${postId}")
type = "text/plain"
}, getString(R.string.share_post))
startActivity(intent)
}
} }
} }

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:title="Discussion info"
app:navigationIcon="@drawable/baseline_close_24"/>
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs_post_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_scrollFlags="scroll|snap"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/view_pager_post_info"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,16 +1,14 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_to_refresh" android:id="@+id/swipe_to_refresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_users" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,21 @@
<?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">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_users"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<com.google.android.material.card.MaterialCardView
android:id="@+id/post_version_date_card"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/post_version_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:text="@string/edited_at"
android:textAlignment="center"
tools:text="date" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
android:id="@+id/post_version_content_card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/post_version_date_card"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginHorizontal="16dp"
android:layout_marginTop="32dp"
style="@style/Widget.Material3.CardView.Filled">
<TextView
android:id="@+id/post_version_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="content"
android:layout_margin="16dp"/>
</com.google.android.material.card.MaterialCardView>
<FrameLayout
android:layout_width="1dp"
android:layout_height="0dp"
android:background="@color/on_surface"
app:layout_constraintTop_toBottomOf="@id/post_version_date_card"
app:layout_constraintBottom_toTopOf="@id/post_version_content_card"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:layout_width="1dp"
android:layout_height="0dp"
android:background="@color/on_surface"
app:layout_constraintTop_toBottomOf="@id/post_version_content_card"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:layout_width="1dp"
android:layout_height="0dp"
android:background="@color/on_surface"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/post_version_date_card"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -87,6 +87,19 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:icon="@drawable/comments_solid" /> app:icon="@drawable/comments_solid" />
<com.google.android.material.button.MaterialButton
android:id="@+id/share_button"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/baseline_share_24"
android:gravity="end"/>
<com.google.android.material.button.MaterialButton
android:id="@+id/info_button"
style="?attr/materialIconButtonFilledTonalStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:icon="@drawable/baseline_info_24" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -206,6 +206,10 @@
<string name="app_version">v%s</string> <string name="app_version">v%s</string>
<string name="new_report">New report</string> <string name="new_report">New report</string>
<string name="report_submitted_successfully">Report submitted successfully</string> <string name="report_submitted_successfully">Report submitted successfully</string>
<string name="share_post">Share post</string>
<string name="people_who_clapped">People who clapped</string>
<string name="post_history">History</string>
<string name="edited_at">Edited at %s</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>