389 lines
15 KiB
Kotlin
Raw Normal View History

2023-02-11 23:41:23 -06:00
package com.isolaatti.profile.ui
2023-08-06 22:11:28 -06:00
import android.content.Context
2023-02-11 23:41:23 -06:00
import android.os.Bundle
2023-09-12 22:31:52 -06:00
import android.util.Log
2023-02-11 23:41:23 -06:00
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
2023-08-27 23:03:40 -06:00
import android.widget.Toast
2023-02-11 23:41:23 -06:00
import androidx.fragment.app.Fragment
2023-08-06 22:11:28 -06:00
import androidx.fragment.app.activityViewModels
2023-07-31 00:25:25 -06:00
import androidx.fragment.app.viewModels
2023-09-10 13:17:50 -06:00
import androidx.lifecycle.Lifecycle
2023-07-31 00:25:25 -06:00
import androidx.lifecycle.Observer
2023-09-10 13:17:50 -06:00
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
2023-07-31 00:25:25 -06:00
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import coil.load
2023-07-31 00:25:25 -06:00
import com.isolaatti.BuildConfig
import com.isolaatti.R
2023-11-25 22:10:35 -06:00
import com.isolaatti.audio.audios_list.ui.AudiosFragment
2023-12-11 23:10:57 -06:00
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.player.AudioPlayerConnector
import com.isolaatti.common.CoilImageLoader.imageLoader
2023-08-27 23:03:40 -06:00
import com.isolaatti.common.Dialogs
2023-09-09 16:09:22 -06:00
import com.isolaatti.common.ErrorMessageViewModel
2023-09-12 22:31:52 -06:00
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
2023-11-25 22:10:35 -06:00
import com.isolaatti.databinding.FragmentDiscussionsBinding
import com.isolaatti.followers.domain.FollowingState
2023-11-20 22:53:14 -06:00
import com.isolaatti.images.image_list.ui.ImagesFragment
2023-11-25 22:10:35 -06:00
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
2023-08-27 20:00:43 -06:00
import com.isolaatti.posting.posts.domain.entity.Post
2023-08-27 23:03:40 -06:00
import com.isolaatti.posting.posts.presentation.CreatePostContract
import com.isolaatti.posting.posts.presentation.EditPostContract
2023-08-06 22:11:28 -06:00
import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring
2023-07-31 00:25:25 -06:00
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
2023-08-06 22:11:28 -06:00
import com.isolaatti.posting.posts.presentation.UpdateEvent
2023-11-25 22:10:35 -06:00
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
2023-09-12 22:31:52 -06:00
import com.isolaatti.profile.domain.entity.UserProfile
2023-07-31 00:25:25 -06:00
import com.isolaatti.profile.presentation.ProfileViewModel
import com.isolaatti.utils.UrlGen
import dagger.hilt.android.AndroidEntryPoint
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.image.coil.CoilImagesPlugin
2023-07-31 00:25:25 -06:00
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
import io.noties.markwon.linkify.LinkifyPlugin
2023-09-10 13:17:50 -06:00
import kotlinx.coroutines.launch
2023-02-11 23:41:23 -06:00
2023-07-31 00:25:25 -06:00
@AndroidEntryPoint
2023-08-06 23:22:53 -06:00
class ProfileMainFragment : Fragment() {
2023-02-11 23:41:23 -06:00
lateinit var viewBinding: FragmentDiscussionsBinding
2023-07-31 00:25:25 -06:00
private val viewModel: ProfileViewModel by viewModels()
2023-08-06 22:11:28 -06:00
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
2023-09-09 16:09:22 -06:00
val errorViewModel: ErrorMessageViewModel by activityViewModels()
2023-07-31 00:25:25 -06:00
private var userId: Int? = null
2023-08-06 22:11:28 -06:00
lateinit var postsAdapter: PostsRecyclerViewAdapter
2023-12-11 23:10:57 -06:00
private var audioDescriptionAudio: Audio? = null
private lateinit var audioPlayerConnector: AudioPlayerConnector
2023-08-06 22:11:28 -06:00
// collapsing bar
2023-07-31 00:25:25 -06:00
private var title = ""
2023-08-06 22:11:28 -06:00
private var scrollRange = -1
private var isShow = false
2023-08-27 23:03:40 -06:00
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
if(it != null) {
Toast.makeText(requireContext(), R.string.posted_successfully, Toast.LENGTH_SHORT).show()
}
// TODO add post to recycler view
2023-08-27 23:03:40 -06:00
}
private val editDiscussion = registerForActivityResult(EditPostContract()) {
if(it != null) {
viewModel.onPostUpdate(it)
}
}
2023-09-12 22:31:52 -06:00
private val profileObserver = Observer<UserProfile> { profile ->
viewBinding.profileImageView.load(UrlGen.userProfileImage(profile.userId), imageLoader)
2023-07-31 00:25:25 -06:00
title = profile.name
viewBinding.textViewUsername.text = profile.name
viewBinding.textViewDescription.text = profile.descriptionText
2023-08-27 23:21:01 -06:00
if(profile.descriptionText.isNullOrBlank()) {
viewBinding.descriptionCard.visibility = View.GONE
}
2023-08-12 00:59:02 -06:00
viewBinding.goToFollowersBtn.text = getString(
R.string.go_to_followers_btn_text,
profile.numberOfFollowers.toString(),
profile.numberOfFollowing.toString()
)
2023-08-27 23:21:01 -06:00
2023-09-12 22:31:52 -06:00
viewBinding.profileImageView.setOnClickListener {
optionsViewModel.setOptions(Options.PROFILE_PHOTO_OPTIONS, CALLER_ID, profile)
val fragment = BottomSheetPostOptionsFragment()
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
2023-12-11 23:10:57 -06:00
audioDescriptionAudio = profile.descriptionAudio
viewBinding.playButton.visibility = if(profile.descriptionAudio != null) View.VISIBLE else View.GONE
2023-08-27 23:21:01 -06:00
setupUiForUserType(profile.isUserItself)
2023-07-31 00:25:25 -06:00
}
2023-08-27 20:00:43 -06:00
private val postsObserver: Observer<Pair<List<Post>?, UpdateEvent>?> = Observer {
2023-08-06 23:22:53 -06:00
if(it?.first != null) {
2023-08-06 22:11:28 -06:00
postsAdapter.updateList(it.first!!, it.second)
2023-08-15 00:02:29 -06:00
postsAdapter.newContentRequestFinished()
2023-08-06 22:11:28 -06:00
}
2023-07-31 00:25:25 -06:00
}
2023-08-10 23:32:47 -06:00
private val followingStateObserver: Observer<FollowingState> = Observer {
when(it) {
FollowingState.FollowingThisUser -> {
viewBinding.textViewFollowingState.setText(R.string.following_user)
viewBinding.followButton.setText(R.string.unfollow)
2023-08-12 00:59:02 -06:00
viewBinding.followButton.isChecked = true
2023-08-10 23:32:47 -06:00
}
FollowingState.MutuallyFollowing -> {
viewBinding.textViewFollowingState.setText(R.string.mutually_following)
viewBinding.followButton.setText(R.string.unfollow)
2023-08-12 00:59:02 -06:00
viewBinding.followButton.isChecked = true
2023-08-10 23:32:47 -06:00
}
FollowingState.ThisUserIsFollowingMe -> {
viewBinding.textViewFollowingState.setText(R.string.following_you)
viewBinding.followButton.setText(R.string.follow)
2023-08-12 00:59:02 -06:00
viewBinding.followButton.isChecked = false
2023-08-10 23:32:47 -06:00
}
FollowingState.NotMutuallyFollowing -> {
viewBinding.textViewFollowingState.text = ""
viewBinding.followButton.setText(R.string.follow)
2023-08-12 00:59:02 -06:00
viewBinding.followButton.isChecked = false
2023-08-10 23:32:47 -06:00
}
}
}
2023-08-27 23:03:40 -06:00
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
2023-09-12 22:31:52 -06:00
if(optionClicked?.callerId == CALLER_ID) {
Log.d("ProfileMainFragment", optionClicked.toString())
when(optionClicked.optionsId) {
Options.PROFILE_PHOTO_OPTIONS -> {
val profile = optionClicked.payload as? UserProfile
when(optionClicked.optionId) {
Options.Option.OPTION_PROFILE_PHOTO_CHANGE_PHOTO -> {}
Options.Option.OPTION_PROFILE_PHOTO_REMOVE_PHOTO -> {}
Options.Option.OPTION_PROFILE_PHOTO_VIEW_PHOTO -> {
val profilePictureUrl = profile?.profilePictureUrl
if(profilePictureUrl != null) {
2023-11-20 22:53:14 -06:00
//PictureViewerActivity.startActivityWithUrls(requireContext(), arrayOf(profilePictureUrl))
2023-09-12 22:31:52 -06:00
}
2023-08-27 23:03:40 -06:00
}
2023-09-12 22:31:52 -06:00
}
2023-08-27 23:03:40 -06:00
optionsViewModel.handle()
}
2023-09-12 22:31:52 -06:00
Options.POST_OPTIONS -> {
// post id should come as payload
val post = optionClicked.payload as? Post ?: return@Observer
when(optionClicked.optionId) {
Options.Option.OPTION_DELETE -> {
Dialogs.buildDeletePostDialog(requireContext()) { delete ->
optionsViewModel.handle()
if(delete) {
viewModel.deletePost(post.id)
}
}.show()
}
Options.Option.OPTION_EDIT -> {
optionsViewModel.handle()
editDiscussion.launch(post.id)
}
Options.Option.OPTION_REPORT -> {
optionsViewModel.handle()
}
}
2023-08-27 23:03:40 -06:00
}
}
2023-09-12 22:31:52 -06:00
2023-08-27 23:03:40 -06:00
}
}
2023-08-06 22:11:28 -06:00
private lateinit var postListingRecyclerViewAdapterWiring: PostListingRecyclerViewAdapterWiring
2023-02-11 23:41:23 -06:00
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
private fun setupCollapsingBar() {
2023-07-31 00:25:25 -06:00
viewBinding.topAppBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
if (scrollRange == -1) scrollRange = appBarLayout.totalScrollRange
if (scrollRange + verticalOffset == 0) {
2023-09-12 22:31:52 -06:00
viewBinding.collapsingToolbarLayout.title = viewModel.profile.value?.name
2023-07-31 00:25:25 -06:00
isShow = true
} else if (isShow) {
viewBinding.collapsingToolbarLayout.title = " "
}
}
2023-08-06 22:11:28 -06:00
}
private fun bind() {
2023-08-15 00:02:29 -06:00
if(userId == null) {
return
}
2023-07-31 00:25:25 -06:00
viewBinding.topAppBar.setNavigationOnClickListener {
2023-08-19 17:31:50 -06:00
requireActivity().finish()
2023-07-31 00:25:25 -06:00
}
2023-08-06 22:11:28 -06:00
viewBinding.bottomAppBar.setOnMenuItemClickListener {
when(it.itemId) {
R.id.audios_menu_item -> {
2023-11-25 22:10:35 -06:00
findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToAudiosFragment(AudiosFragment.SOURCE_PROFILE, userId.toString()))
2023-08-06 22:11:28 -06:00
true
}
R.id.images_menu_item -> {
2023-11-20 22:53:14 -06:00
if(userId != null) {
findNavController().navigate(
ProfileMainFragmentDirections.actionDiscussionsFragmentToImagesFragment(ImagesFragment.SOURCE_PROFILE, userId.toString())
)
true
} else {
false
}
2023-08-06 22:11:28 -06:00
}
else -> { false }
}
}
viewBinding.newPost.setOnClickListener {
createDiscussion.launch(Unit)
}
2023-08-12 00:59:02 -06:00
viewBinding.goToFollowersBtn.setOnClickListener {
2023-08-15 00:02:29 -06:00
findNavController().navigate(ProfileMainFragmentDirections.actionDiscussionsFragmentToMainFollowersFragment(userId!!))
2023-08-12 00:59:02 -06:00
}
2023-08-06 22:11:28 -06:00
viewBinding.feedRecyclerView.adapter = postsAdapter
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
2023-08-06 23:22:53 -06:00
viewBinding.swipeToRefresh.setOnRefreshListener {
viewModel.getFeed(true)
}
2023-12-11 23:10:57 -06:00
viewBinding.playButton.setOnClickListener {
audioDescriptionAudio?.let { audio ->
audioPlayerConnector.playAudio(audio)
}
}
2023-08-06 22:11:28 -06:00
}
private fun setObservers() {
2023-07-31 00:25:25 -06:00
viewModel.profile.observe(viewLifecycleOwner, profileObserver)
viewModel.posts.observe(viewLifecycleOwner, postsObserver)
2023-08-10 23:32:47 -06:00
viewModel.followingState.observe(viewLifecycleOwner, followingStateObserver)
2023-08-27 23:03:40 -06:00
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
2023-08-06 23:22:53 -06:00
viewModel.loadingPosts.observe(viewLifecycleOwner) {
2023-08-15 00:02:29 -06:00
viewBinding.loadingIndicator.visibility = if(it) View.VISIBLE else View.GONE
2023-08-15 23:11:15 -06:00
if(!it) {
viewBinding.swipeToRefresh.isRefreshing = false
}
2023-08-06 23:22:53 -06:00
}
2023-09-09 16:09:22 -06:00
viewModel.errorLoading.observe(viewLifecycleOwner) {
errorViewModel.error.postValue(it)
}
2023-08-06 22:11:28 -06:00
}
private fun getData() {
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
userId?.let { profileId ->
viewModel.profileId = profileId
viewModel.getProfile()
viewModel.getFeed(true)
2023-07-31 00:25:25 -06:00
}
2023-08-06 22:11:28 -06:00
}
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
private fun setupPostsAdapter() {
2023-07-31 00:25:25 -06:00
val markwon = Markwon.builder(requireContext())
.usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder
.imageDestinationProcessor(
ImageDestinationProcessorRelativeToAbsolute
2023-08-06 22:11:28 -06:00
.create(BuildConfig.backend))
2023-07-31 00:25:25 -06:00
}
})
.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
2023-07-31 00:25:25 -06:00
.usePlugin(LinkifyPlugin.create())
.build()
2023-08-27 20:00:43 -06:00
postsAdapter = PostsRecyclerViewAdapter(markwon,postListingRecyclerViewAdapterWiring )
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
}
2023-07-31 00:25:25 -06:00
2023-08-27 23:21:01 -06:00
private fun setupUiForUserType(isOwnProfile: Boolean) {
if(isOwnProfile) {
viewBinding.followButton.visibility = View.GONE
} else {
viewBinding.newPost.visibility = View.GONE
}
}
2023-08-06 22:11:28 -06:00
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
userId = (requireActivity()).intent.extras?.getInt(ProfileActivity.EXTRA_USER_ID)
getData()
2023-07-31 00:25:25 -06:00
}
2023-08-06 22:11:28 -06:00
override fun onAttach(context: Context) {
super.onAttach(context)
2023-12-11 23:10:57 -06:00
2023-08-06 22:11:28 -06:00
postListingRecyclerViewAdapterWiring = object: PostListingRecyclerViewAdapterWiring(viewModel) {
override fun onComment(postId: Long) {
val modalBottomSheet = BottomSheetPostComments.getInstance(postId)
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostComments.TAG)
}
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
override fun onOpenPost(postId: Long) {
PostViewerActivity.startActivity(requireContext(), postId)
}
2023-07-31 00:25:25 -06:00
2023-08-27 20:00:43 -06:00
override fun onOptions(post: Ownable) {
2023-09-12 22:31:52 -06:00
optionsViewModel.setOptions(Options.POST_OPTIONS, CALLER_ID, post)
2023-08-06 22:11:28 -06:00
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
2023-07-31 00:25:25 -06:00
2023-08-06 22:11:28 -06:00
override fun onProfileClick(userId: Int) {
//ProfileActivity.startActivity(requireContext(), userId)
}
2023-08-15 00:02:29 -06:00
override fun onLoadMore() {
viewModel.getFeed(false)
}
2023-08-06 22:11:28 -06:00
}
2023-07-31 00:25:25 -06:00
}
2023-08-06 22:11:28 -06:00
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewBinding = FragmentDiscussionsBinding.inflate(inflater)
return viewBinding.root
2023-07-31 00:25:25 -06:00
}
2023-08-06 22:11:28 -06:00
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
2023-09-12 22:31:52 -06:00
2023-08-06 22:11:28 -06:00
setupPostsAdapter()
bind()
setObservers()
2023-09-12 22:31:52 -06:00
setupCollapsingBar()
2023-09-10 13:17:50 -06:00
2023-12-11 23:10:57 -06:00
audioPlayerConnector = AudioPlayerConnector(requireContext())
viewLifecycleOwner.lifecycle.addObserver(audioPlayerConnector)
2023-09-10 13:17:50 -06:00
viewLifecycleOwner.lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
errorViewModel.retry.collect {
viewModel.retry()
errorViewModel.handleRetry()
}
}
}
2023-07-31 00:25:25 -06:00
}
2023-09-12 22:31:52 -06:00
companion object {
const val CALLER_ID = 30
}
2023-02-11 23:41:23 -06:00
}