WIP perfil

This commit is contained in:
Erik Cavazos 2023-08-06 22:11:28 -06:00
parent 0ab027b635
commit 4a8d05c87b
19 changed files with 351 additions and 284 deletions

View File

@ -24,7 +24,6 @@ import com.isolaatti.databinding.FragmentFeedBinding
import com.isolaatti.drafts.ui.DraftsActivity
import com.isolaatti.home.presentation.FeedViewModel
import com.isolaatti.posting.PostViewerActivity
import com.isolaatti.posting.posts.presentation.PostsViewModel
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked
@ -32,6 +31,7 @@ import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.posting.posts.ui.CreatePostActivity
import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.settings.ui.SettingsActivity
@ -53,9 +53,8 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
const val CALLER_ID = 20
}
private val viewModel: PostsViewModel by activityViewModels()
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
private val screenViewModel: FeedViewModel by viewModels()
private val viewModel: FeedViewModel by activityViewModels()
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
private lateinit var viewBinding: FragmentFeedBinding
@ -158,7 +157,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
}
}
screenViewModel.userProfile.observe(viewLifecycleOwner) {
viewModel.userProfile.observe(viewLifecycleOwner) {
val header = viewBinding.homeDrawer.getHeaderView(0) as? ConstraintLayout
val image: ImageView? = header?.findViewById(R.id.profileImageView)
@ -175,34 +174,23 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
viewModel.posts.observe(viewLifecycleOwner){
if (it != null) {
adapter.updateList(it,null)
if (it.first != null) {
adapter.updateList(it.first!!, it.second)
}
}
// viewModel.loadingPosts.observe(viewLifecycleOwner) {
// viewBinding.progressBarLoading.visibility = if(it) View.VISIBLE else View.GONE
// viewBinding.loadMoreButton.visibility = if(it) View.GONE else View.VISIBLE
// }
//
viewModel.loadingPosts.observe(viewLifecycleOwner) {
viewBinding.swipeToRefresh.isRefreshing = it
}
// viewModel.noMoreContent.observe(viewLifecycleOwner) {
// val visibility = if(it) View.VISIBLE else View.GONE
// viewBinding.noMoreContentToShowTextView.visibility = visibility
// viewBinding.refreshButton.visibility = visibility
// viewBinding.loadMoreButton.visibility = if(it) View.GONE else View.VISIBLE
//
// }
viewModel.errorLoading.observe(viewLifecycleOwner) {
errorViewModel.error.postValue(it)
}
viewModel.postLiked.observe(viewLifecycleOwner) {
viewModel.posts.value?.let { feed ->
adapter.updateList(
feed, PostsRecyclerViewAdapter.UpdateEvent(
PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId))
}
}
screenViewModel.getProfile()
viewModel.getProfile()
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
}
@ -210,8 +198,6 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
fun onNewMenuItemClicked(v: View) {
}
override fun onLiked(postId: Long) = viewModel.likePost(postId)
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
@ -234,5 +220,4 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
override fun onProfileClick(userId: Int) {
ProfileActivity.startActivity(requireContext(), userId)
}
}

View File

@ -8,16 +8,13 @@ import androidx.lifecycle.Observer
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.isolaatti.R
import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityHomeBinding
import com.isolaatti.posting.posts.presentation.PostsViewModel
import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
import com.isolaatti.home.presentation.FeedViewModel
class HomeActivity : IsolaattiBaseActivity() {
private lateinit var viewBinding: ActivityHomeBinding
private val postsViewModel: PostsViewModel by viewModels()
private val feedViewModel: FeedViewModel by viewModels()
override fun onRetry() {
}
@ -32,7 +29,7 @@ class HomeActivity : IsolaattiBaseActivity() {
viewBinding.navigationRail?.setupWithNavController(navHostFragment.navController)
if(savedInstanceState == null) {
postsViewModel.getFeed(false)
feedViewModel.getFeed(false)
}
}

View File

@ -5,6 +5,9 @@ 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.data.remote.UserProfileDto
import com.isolaatti.profile.domain.use_case.GetProfile
import com.isolaatti.utils.Resource
@ -17,7 +20,36 @@ import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class FeedViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val authRepository: AuthRepository) : ViewModel() {
class FeedViewModel @Inject constructor(
private val getProfileUseCase: GetProfile,
private val authRepository: AuthRepository,
private val postsRepository: PostsRepository
) : PostListingViewModelBase() {
override fun getFeed(refresh: Boolean) {
viewModelScope.launch {
if (refresh) {
posts.value = null
}
postsRepository.getFeed(getLastId()).onEach { feedDtoResource ->
when (feedDtoResource) {
is Resource.Success -> {
loadingPosts.postValue(false)
posts.postValue(Pair(posts.value?.first?.concatFeed(feedDtoResource.data) ?: feedDtoResource.data, UpdateEvent(UpdateEvent.UpdateType.PAGE_ADDED, null)))
noMoreContent.postValue(feedDtoResource.data?.moreContent == false)
}
is Resource.Loading -> {
loadingPosts.postValue(true)
}
is Resource.Error -> {
errorLoading.postValue(feedDtoResource.errorType)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
// User profile
private val _userProfile: MutableLiveData<UserProfileDto> = MutableLiveData()
@ -25,10 +57,10 @@ class FeedViewModel @Inject constructor(private val getProfileUseCase: GetProfil
fun getProfile() {
viewModelScope.launch {
authRepository.getUserId().onEach {userId ->
authRepository.getUserId().onEach { userId ->
userId?.let {
getProfileUseCase(userId).onEach {profile ->
if(profile is Resource.Success) {
getProfileUseCase(userId).onEach { profile ->
if (profile is Resource.Success) {
profile.data?.let { _userProfile.postValue(it) }
}
}.flowOn(Dispatchers.IO).launchIn(this)

View File

@ -10,14 +10,8 @@ import com.isolaatti.posting.posts.data.remote.PostApi
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.FlowCollector
import kotlinx.coroutines.flow.flow
import retrofit2.await
import java.io.IOException
import java.lang.RuntimeException
import javax.inject.Inject
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
override fun getFeed(lastId: Long): Flow<Resource<FeedDto>> = flow {
@ -39,7 +33,7 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
}
}
override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>> = flow {
override fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<FeedDto>> = flow {
emit(Resource.Loading())
try {
val gson = Gson()

View File

@ -11,7 +11,7 @@ interface PostsRepository {
fun getFeed(lastId: Long): Flow<Resource<FeedDto>>
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>>
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<FeedDto>>
fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>>
fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>>

View File

@ -0,0 +1,22 @@
package com.isolaatti.posting.posts.presentation
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
abstract class PostListingRecyclerViewAdapterWiring(private val postsViewModelBase: PostListingViewModelBase) : OnUserInteractedWithPostCallback {
override fun onLiked(postId: Long) {
postsViewModelBase.likePost(postId)
}
override fun onUnLiked(postId: Long) {
postsViewModelBase.unLikePost(postId)
}
abstract override fun onComment(postId: Long)
abstract override fun onOpenPost(postId: Long)
abstract override fun onOptions(postId: Long)
abstract override fun onProfileClick(userId: Int)
}

View File

@ -0,0 +1,72 @@
package com.isolaatti.posting.posts.presentation
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.likes.data.remote.LikeDto
import com.isolaatti.posting.likes.domain.repository.LikesRepository
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.profile.domain.use_case.GetProfilePosts
import com.isolaatti.utils.Resource
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
abstract class PostListingViewModelBase : ViewModel() {
@Inject
lateinit var likesRepository: LikesRepository
@Inject
lateinit var getProfilePosts: GetProfilePosts
val posts: MutableLiveData<Pair<FeedDto?, UpdateEvent>> = MutableLiveData()
val loadingPosts = MutableLiveData(false)
val noMoreContent = MutableLiveData(false)
val errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
fun getLastId(): Long = try { posts.value?.first?.data?.last()?.post?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
abstract fun getFeed(refresh: Boolean)
fun likePost(postId: Long) {
viewModelScope.launch {
likesRepository.likePost(postId).onEach {likeDto ->
val likedPost = posts.value?.first?.data?.find { post -> post.post.id == likeDto.postId }
val index = posts.value?.first?.data?.indexOf(likedPost)
if(index != null){
val temp = posts.value?.first
Log.d("***", temp.toString())
temp?.data?.set(index, likedPost!!.apply {
liked = true
numberOfLikes = likeDto.likesCount
})
}
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, likedPost?.post?.id)))
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun unLikePost(postId: Long) {
viewModelScope.launch {
likesRepository.unLikePost(postId).onEach {likeDto ->
val likedPost = posts.value?.first?.data?.find { post -> post.post.id == likeDto.postId }
val index = posts.value?.first?.data?.indexOf(likedPost)
if(index != null){
posts.value?.first?.data?.set(index, likedPost!!.apply {
liked = false
numberOfLikes = likeDto.likesCount
})
}
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, likedPost?.post?.id)))
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

@ -114,15 +114,6 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
data class LikeCountUpdatePayload(val likeCount: Int)
data class CommentsCountUpdatePayload(val commentsCount: Int)
data class UpdateEvent(val updateType: UpdateType, val affectedId: Long) {
enum class UpdateType {
POST_LIKED,
POST_COMMENTED,
POST_REMOVED,
POST_ADDED
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.post_layout, parent, false)
@ -143,24 +134,34 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
notifyDataSetChanged()
return
}
val postUpdated = feedDto?.data?.find { p -> p.post.id == updateEvent.affectedId } ?: return
val position = feedDto?.data?.indexOf(postUpdated) ?: return
val postUpdated = feedDto?.data?.find { p -> p.post.id == updateEvent.affectedId }
val position = feedDto?.data?.indexOf(postUpdated)
feedDto = 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(postUpdated != null && position != null)
notifyItemRemoved(position)
}
UpdateEvent.UpdateType.POST_ADDED -> {
notifyItemInserted(0)
}
UpdateEvent.UpdateType.PAGE_ADDED -> {
notifyItemInserted(itemCount - 1)
}
UpdateEvent.UpdateType.REFRESH -> {
notifyDataSetChanged()
}
}
}

View File

@ -1,105 +0,0 @@
package com.isolaatti.posting.posts.presentation
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.likes.data.remote.LikeDto
import com.isolaatti.posting.likes.domain.repository.LikesRepository
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
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 PostsViewModel @Inject constructor(private val postsRepository: PostsRepository, private val likesRepository: LikesRepository) : ViewModel() {
private val _posts: MutableLiveData<FeedDto?> = MutableLiveData()
val posts: LiveData<FeedDto?> get() = _posts
private val _loadingPosts = MutableLiveData(false)
val loadingPosts: LiveData<Boolean> get() = _loadingPosts
private val _noMoreContent = MutableLiveData(false)
val noMoreContent: LiveData<Boolean> get() = _noMoreContent
private val _errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
val errorLoading: LiveData<Resource.Error.ErrorType?> get() = _errorLoading
private val _comments: MutableLiveData<FeedCommentsDto> = MutableLiveData()
val comments: LiveData<FeedCommentsDto> get() = _comments
private fun getLastId(): Long = try {_posts.value?.data?.last()?.post?.id ?: 0} catch (e: NoSuchElementException) { 0 }
private val _postLiked: MutableLiveData<LikeDto> = MutableLiveData()
val postLiked: LiveData<LikeDto> get() = _postLiked
fun getFeed(refresh: Boolean) {
viewModelScope.launch {
if(refresh) {
_posts.value = null
}
postsRepository.getFeed(getLastId()).onEach {
when(it) {
is Resource.Success -> {
_loadingPosts.postValue(false)
_posts.postValue(posts.value?.concatFeed(it.data) ?: it.data)
_noMoreContent.postValue(it.data?.moreContent == false)
}
is Resource.Loading -> {
_loadingPosts.postValue(true)
}
is Resource.Error -> {
_errorLoading.postValue(it.errorType)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun likePost(postId: Long) {
viewModelScope.launch {
likesRepository.likePost(postId).onEach {likeDto ->
val likedPost = _posts.value?.data?.find { post -> post.post.id == likeDto.postId }
val index = _posts.value?.data?.indexOf(likedPost)
Log.d("***", index.toString())
if(index != null){
val temp = _posts.value
Log.d("***", temp.toString())
temp?.data?.set(index, likedPost!!.apply {
liked = true
numberOfLikes = likeDto.likesCount
})
}
_postLiked.postValue(likeDto)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun unLikePost(postId: Long) {
viewModelScope.launch {
likesRepository.unLikePost(postId).onEach {likeDto ->
val likedPost = _posts.value?.data?.find { post -> post.post.id == likeDto.postId }
val index = _posts.value?.data?.indexOf(likedPost)
if(index != null){
_posts.value?.data?.set(index, likedPost!!.apply {
liked = false
numberOfLikes = likeDto.likesCount
})
}
_postLiked.postValue(likeDto)
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
}

View File

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

View File

@ -8,6 +8,6 @@ import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class GetProfilePosts @Inject constructor(private val postsRepository: PostsRepository) {
operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>> =
operator fun invoke(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<FeedDto>> =
postsRepository.getProfilePosts(userId, lastId, olderFirst, filter)
}

View File

@ -6,6 +6,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.domain.ProfileRepository
import com.isolaatti.profile.domain.use_case.GetProfile
@ -24,14 +26,13 @@ import java.time.Month
import javax.inject.Inject
@HiltViewModel
class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val getProfilePosts: GetProfilePosts) : ViewModel() {
class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val getProfilePostsUseCase: GetProfilePosts) : PostListingViewModelBase() {
private val _profile = MutableLiveData<UserProfileDto>()
val profile: LiveData<UserProfileDto> get() = _profile
private val _posts = MutableLiveData<FeedDto>()
val posts: LiveData<FeedDto> get() = _posts
var profileId: Int = 0
fun getProfile(profileId: Int) {
fun getProfile() {
viewModelScope.launch {
getProfileUseCase(profileId).onEach {
if(it is Resource.Success) {
@ -41,24 +42,23 @@ class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetPro
}
}
fun getPosts(profileId: Int, refresh: Boolean) {
override fun getFeed(refresh: Boolean) {
viewModelScope.launch {
getProfilePosts(
profileId,
-1,
false,
FeedFilterDto(
"both",
"both",
FeedFilterDto.DataRange(
false,
LocalDate.of(2020, 1, 1),
LocalDate.now()
)
)
).onEach {
if(it is Resource.Success) {
_posts.postValue(it.data!!)
getProfilePostsUseCase(profileId, getLastId(), false, null).onEach { feedDtoResource ->
when (feedDtoResource) {
is Resource.Success -> {
loadingPosts.postValue(false)
posts.postValue(Pair(posts.value?.first?.concatFeed(feedDtoResource.data) ?: feedDtoResource.data, UpdateEvent(UpdateEvent.UpdateType.PAGE_ADDED, null)))
noMoreContent.postValue(feedDtoResource.data?.moreContent == false)
}
is Resource.Loading -> {
loadingPosts.postValue(true)
}
is Resource.Error -> {
errorLoading.postValue(feedDtoResource.errorType)
}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}

View File

@ -1,29 +1,36 @@
package com.isolaatti.profile.ui
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.appbar.AppBarLayout
import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.databinding.FragmentDiscussionsBinding
import com.isolaatti.home.FeedFragment
import com.isolaatti.posting.PostViewerActivity
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWiring
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.presentation.ProfileViewModel
import com.isolaatti.utils.PicassoImagesPluginDef
import com.isolaatti.utils.UrlGen
import com.squareup.picasso.Picasso
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.lifecycle.HiltViewModel
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
@ -31,14 +38,19 @@ import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAb
import io.noties.markwon.linkify.LinkifyPlugin
@AndroidEntryPoint
class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback {
class DiscussionsFragment : Fragment() {
lateinit var viewBinding: FragmentDiscussionsBinding
private val viewModel: ProfileViewModel by viewModels()
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
private var userId: Int? = null
lateinit var postsAdapter: PostsRecyclerViewAdapter
// collapsing bar
private var title = ""
lateinit var feedAdapter: PostsRecyclerViewAdapter
private var scrollRange = -1
private var isShow = false
private val profileObserver = Observer<UserProfileDto> { profile ->
Picasso.get()
@ -50,30 +62,18 @@ class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback {
viewBinding.textViewDescription.text = profile.descriptionText
}
private val postsObserver: Observer<FeedDto> = Observer {
feedAdapter.updateList(it, null)
private val postsObserver: Observer<Pair<FeedDto?, UpdateEvent>> = Observer {
if(it.first != null) {
postsAdapter.updateList(it.first!!, it.second)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userId = (requireActivity()).intent.extras?.getInt(ProfileActivity.EXTRA_USER_ID)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
viewBinding = FragmentDiscussionsBinding.inflate(inflater)
private lateinit var postListingRecyclerViewAdapterWiring: PostListingRecyclerViewAdapterWiring
return viewBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
var scrollRange = -1
var isShow = false
private fun setupCollapsingBar() {
viewBinding.topAppBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
if (scrollRange == -1) scrollRange = appBarLayout.totalScrollRange
if (scrollRange + verticalOffset == 0) {
@ -83,38 +83,16 @@ class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback {
viewBinding.collapsingToolbarLayout.title = " "
}
}
}
private fun bind() {
viewBinding.topAppBar.setNavigationOnClickListener {
findNavController().popBackStack()
}
viewModel.profile.observe(viewLifecycleOwner, profileObserver)
viewModel.posts.observe(viewLifecycleOwner, postsObserver)
userId?.let {
viewModel.getProfile(it)
viewModel.getPosts(it, true)
}
val markwon = Markwon.builder(requireContext())
.usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder
.imageDestinationProcessor(
ImageDestinationProcessorRelativeToAbsolute
.create(BuildConfig.backend))
}
})
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
.usePlugin(LinkifyPlugin.create())
.build()
feedAdapter = PostsRecyclerViewAdapter(markwon, this, null)
viewBinding.feedRecyclerView.adapter = feedAdapter
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
viewBinding.bottomAppBar.setOnMenuItemClickListener {
when(it.itemId) {
R.id.audios_menu_item -> {
@ -129,29 +107,88 @@ class DiscussionsFragment : Fragment(), OnUserInteractedWithPostCallback {
}
}
viewBinding.feedRecyclerView.adapter = postsAdapter
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
}
override fun onLiked(postId: Long) {
TODO("Not yet implemented")
private fun setObservers() {
viewModel.profile.observe(viewLifecycleOwner, profileObserver)
viewModel.posts.observe(viewLifecycleOwner, postsObserver)
}
override fun onUnLiked(postId: Long) {
TODO("Not yet implemented")
private fun getData() {
userId?.let { profileId ->
viewModel.profileId = profileId
viewModel.getProfile()
viewModel.getFeed(true)
}
}
private fun setupPostsAdapter() {
val markwon = Markwon.builder(requireContext())
.usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder
.imageDestinationProcessor(
ImageDestinationProcessorRelativeToAbsolute
.create(BuildConfig.backend))
}
})
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
.usePlugin(LinkifyPlugin.create())
.build()
postsAdapter = PostsRecyclerViewAdapter(markwon,postListingRecyclerViewAdapterWiring, null )
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userId = (requireActivity()).intent.extras?.getInt(ProfileActivity.EXTRA_USER_ID)
}
override fun onAttach(context: Context) {
super.onAttach(context)
postListingRecyclerViewAdapterWiring = object: PostListingRecyclerViewAdapterWiring(viewModel) {
override fun onComment(postId: Long) {
TODO("Not yet implemented")
val modalBottomSheet = BottomSheetPostComments.getInstance(postId)
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostComments.TAG)
}
override fun onOpenPost(postId: Long) {
TODO("Not yet implemented")
PostViewerActivity.startActivity(requireContext(), postId)
}
override fun onOptions(postId: Long) {
TODO("Not yet implemented")
optionsViewModel.setOptions(Options.postOptions, FeedFragment.CALLER_ID)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
override fun onProfileClick(userId: Int) {
TODO("Not yet implemented")
//ProfileActivity.startActivity(requireContext(), userId)
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
viewBinding = FragmentDiscussionsBinding.inflate(inflater)
return viewBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupCollapsingBar()
setupPostsAdapter()
bind()
setObservers()
getData()
}
}

View File

@ -10,6 +10,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContentProviderCompat.requireContext
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.findNavController
@ -45,7 +46,6 @@ class ProfileActivity : FragmentActivity() {
lateinit var viewBinding: ActivityProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityProfileBinding.inflate(layoutInflater)

View File

@ -1,8 +1,24 @@
package com.isolaatti.profile.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.isolaatti.databinding.FragmentUserlinkBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class UserLinkFragment : Fragment() {
lateinit var binding: FragmentUserlinkBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentUserlinkBinding.inflate(inflater)
return binding.root
}
}

View File

@ -1,17 +1,18 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
<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:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/audios"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
app:title="@string/audios"
app:navigationIcon="@drawable/baseline_arrow_back_24"
app:navigationIconTint="@color/on_surface"
app:titleCentered="true"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -44,9 +44,10 @@
android:id="@+id/feed_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.navigation.NavigationView

View File

@ -1,22 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
<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:layout_width="match_parent"
android:layout_height="wrap_content">
<ScrollView
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/topAppBar"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="2000dp"
android:text="@string/images"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</ScrollView>
</FrameLayout>
android:layout_height="wrap_content"
app:title="@string/images"
app:navigationIcon="@drawable/baseline_arrow_back_24"
app:navigationIconTint="@color/on_surface"
app:titleCentered="true"/>
</com.google.android.material.appbar.AppBarLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,6 @@
<?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>