This commit is contained in:
Erik Cavazos 2023-09-12 22:31:52 -06:00
parent a763926c18
commit 0a8946167f
29 changed files with 240 additions and 150 deletions

View File

@ -1,4 +1,4 @@
package com.isolaatti.posting.common.domain package com.isolaatti.common
interface OnUserInteractedCallback { interface OnUserInteractedCallback {
fun onOptions(postId: Ownable) fun onOptions(postId: Ownable)

View File

@ -1,4 +1,4 @@
package com.isolaatti.posting.common.domain package com.isolaatti.common
interface OnUserInteractedWithPostCallback : OnUserInteractedCallback { interface OnUserInteractedWithPostCallback : OnUserInteractedCallback {
fun onLiked(postId: Long) fun onLiked(postId: Long)

View File

@ -0,0 +1,5 @@
package com.isolaatti.common
interface Ownable {
val userId: Int
}

View File

@ -0,0 +1,9 @@
package com.isolaatti.common.options_bottom_sheet.domain
/**
* @param optionsId Identify what dialog it is
* @param optionId Identify action
* @param callerId Identify who started dialog
* @param payload Data to identify on what item perform action
*/
data class OptionClicked(val optionsId: Int, val optionId: Int, val callerId: Int, val payload: Any? = null)

View File

@ -1,4 +1,4 @@
package com.isolaatti.posting.common.options_bottom_sheet.domain package com.isolaatti.common.options_bottom_sheet.domain
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
@ -6,6 +6,7 @@ import com.isolaatti.R
data class Options( data class Options(
@StringRes val title: Int, @StringRes val title: Int,
val id: Int,
val items: List<Option> val items: List<Option>
) { ) {
data class Option( data class Option(
@ -19,14 +20,18 @@ data class Options(
const val OPTION_REPORT = 3 const val OPTION_REPORT = 3
const val OPTION_SAVE = 4 const val OPTION_SAVE = 4
const val OPTION_SNAPSHOT = 5 const val OPTION_SNAPSHOT = 5
const val OPTION_PROFILE_PHOTO_VIEW_PHOTO = 6
const val OPTION_PROFILE_PHOTO_CHANGE_PHOTO = 7
const val OPTION_PROFILE_PHOTO_REMOVE_PHOTO = 8
} }
} }
companion object { companion object {
const val POST_OPTIONS = 1 const val POST_OPTIONS = 1
const val COMMENT_OPTIONS = 2 const val COMMENT_OPTIONS = 2
const val PROFILE_PHOTO_OPTIONS = 3
val noOptions = Options(0, listOf()) val noOptions = Options(0, 0, listOf())
fun getPostsOptions(userOwned: Boolean, savable: Boolean, snapshotAble: Boolean): Options { fun getPostsOptions(userOwned: Boolean, savable: Boolean, snapshotAble: Boolean): Options {
val list = mutableListOf( val list = mutableListOf(
@ -36,7 +41,8 @@ data class Options(
if(userOwned) { if(userOwned) {
list.addAll(listOf( list.addAll(listOf(
Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE), Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE),
Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT)) Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT)
)
) )
} }
@ -48,7 +54,7 @@ data class Options(
list.add(Option(R.string.save_snapshot, R.drawable.baseline_download_24, Option.OPTION_SNAPSHOT)) list.add(Option(R.string.save_snapshot, R.drawable.baseline_download_24, Option.OPTION_SNAPSHOT))
} }
return Options(R.string.post_options_title, list) return Options(R.string.post_options_title, POST_OPTIONS, list)
} }
fun getCommentOptions(userOwned: Boolean, savable: Boolean, snapshotAble: Boolean): Options { fun getCommentOptions(userOwned: Boolean, savable: Boolean, snapshotAble: Boolean): Options {
@ -59,7 +65,8 @@ data class Options(
if(userOwned) { if(userOwned) {
list.addAll(listOf( list.addAll(listOf(
Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE), Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE),
Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT)) Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT)
)
) )
} }
@ -71,7 +78,20 @@ data class Options(
list.add(Option(R.string.save_snapshot, R.drawable.baseline_download_24, Option.OPTION_SNAPSHOT)) list.add(Option(R.string.save_snapshot, R.drawable.baseline_download_24, Option.OPTION_SNAPSHOT))
} }
return Options(R.string.comment_options, list) return Options(R.string.comment_options, COMMENT_OPTIONS, list)
}
fun getProfilePhotoOptions(userOwned: Boolean): Options {
val list = mutableListOf(Option(R.string.view_photo, R.drawable.baseline_image_24, Option.OPTION_PROFILE_PHOTO_VIEW_PHOTO))
if(userOwned) {
list.addAll(listOf(
Option(R.string.change_profile_photo, R.drawable.baseline_edit_24, Option.OPTION_PROFILE_PHOTO_CHANGE_PHOTO),
Option(R.string.remove_photo, R.drawable.baseline_remove_circle_24, Option.OPTION_PROFILE_PHOTO_REMOVE_PHOTO)
))
}
return Options(R.string.profile_photo,PROFILE_PHOTO_OPTIONS, list)
} }
} }
} }

View File

@ -1,15 +1,15 @@
package com.isolaatti.posting.common.options_bottom_sheet.presentation package com.isolaatti.common.options_bottom_sheet.presentation
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options.Companion.COMMENT_OPTIONS import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.COMMENT_OPTIONS
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options.Companion.POST_OPTIONS import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.POST_OPTIONS
import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.PROFILE_PHOTO_OPTIONS
import com.isolaatti.settings.domain.UserIdSetting import com.isolaatti.settings.domain.UserIdSetting
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -39,7 +39,8 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
when(options) { when(options) {
POST_OPTIONS -> { POST_OPTIONS -> {
userIdSetting.getUserId()?.let { userId -> userIdSetting.getUserId()?.let { userId ->
_options.postValue(Options.getPostsOptions( _options.postValue(
Options.getPostsOptions(
userOwned = userId == payload?.userId, userOwned = userId == payload?.userId,
savable = false, savable = false,
snapshotAble = false) snapshotAble = false)
@ -50,7 +51,8 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
} }
COMMENT_OPTIONS -> { COMMENT_OPTIONS -> {
userIdSetting.getUserId()?.let { userId -> userIdSetting.getUserId()?.let { userId ->
_options.postValue(Options.getCommentOptions( _options.postValue(
Options.getCommentOptions(
userOwned = userId == payload?.userId, userOwned = userId == payload?.userId,
savable = false, savable = false,
snapshotAble = false) snapshotAble = false)
@ -59,13 +61,20 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
_payload = payload _payload = payload
} }
} }
PROFILE_PHOTO_OPTIONS -> {
userIdSetting.getUserId()?.let { userId ->
_options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,))
_callerId = callerId
_payload = payload
}
}
} }
} }
} }
} }
fun optionClicked(optionId: Int) { fun optionClicked(optionsId: Int, optionId: Int) {
_optionClicked.postValue(OptionClicked(optionId, _callerId, _payload)) _optionClicked.postValue(OptionClicked(optionsId, optionId, _callerId, _payload))
} }
} }

View File

@ -1,4 +1,4 @@
package com.isolaatti.posting.common.options_bottom_sheet.presentation package com.isolaatti.common.options_bottom_sheet.presentation
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
@ -6,14 +6,14 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.isolaatti.databinding.OptionItemBinding import com.isolaatti.databinding.OptionItemBinding
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
class OptionsRecyclerAdapter(val options: List<Options.Option>, private val optionCallback: OptionsCallback) : RecyclerView.Adapter<OptionsRecyclerAdapter.OptionViewHolder>() { class OptionsRecyclerAdapter(val optionsId: Int, val options: List<Options.Option>, private val optionCallback: OptionsCallback) : RecyclerView.Adapter<OptionsRecyclerAdapter.OptionViewHolder>() {
inner class OptionViewHolder(val viewBinding: OptionItemBinding) : RecyclerView.ViewHolder(viewBinding.root) inner class OptionViewHolder(val viewBinding: OptionItemBinding) : RecyclerView.ViewHolder(viewBinding.root)
fun interface OptionsCallback { fun interface OptionsCallback {
fun optionClicked(optionId: Int) fun optionClicked(optionsId:Int, optionId: Int)
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder {
@ -34,7 +34,7 @@ class OptionsRecyclerAdapter(val options: List<Options.Option>, private val opti
text = context.getText(options[position].stringRes) text = context.getText(options[position].stringRes)
icon = AppCompatResources.getDrawable(context, options[position].icon) icon = AppCompatResources.getDrawable(context, options[position].icon)
setOnClickListener { setOnClickListener {
optionCallback.optionClicked(options[position].optionId) optionCallback.optionClicked(optionsId, options[position].optionId)
} }
} }
} }

View File

@ -1,4 +1,4 @@
package com.isolaatti.posting.common.options_bottom_sheet.ui package com.isolaatti.common.options_bottom_sheet.ui
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@ -15,9 +15,9 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.databinding.BottomSheetPostOptionsBinding import com.isolaatti.databinding.BottomSheetPostOptionsBinding
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.presentation.OptionsRecyclerAdapter import com.isolaatti.common.options_bottom_sheet.presentation.OptionsRecyclerAdapter
class BottomSheetPostOptionsFragment : BottomSheetDialogFragment(), OptionsRecyclerAdapter.OptionsCallback { class BottomSheetPostOptionsFragment : BottomSheetDialogFragment(), OptionsRecyclerAdapter.OptionsCallback {
private lateinit var viewBinding: BottomSheetPostOptionsBinding private lateinit var viewBinding: BottomSheetPostOptionsBinding
@ -56,7 +56,7 @@ class BottomSheetPostOptionsFragment : BottomSheetDialogFragment(), OptionsRecyc
private fun renderOptions(options: Options) { private fun renderOptions(options: Options) {
viewBinding.title.text = requireContext().getText(options.title) viewBinding.title.text = requireContext().getText(options.title)
viewBinding.recyclerOptions.adapter = OptionsRecyclerAdapter(options.items, this) viewBinding.recyclerOptions.adapter = OptionsRecyclerAdapter(options.id, options.items, this)
viewBinding.recyclerOptions.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) viewBinding.recyclerOptions.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
} }
@ -66,7 +66,7 @@ class BottomSheetPostOptionsFragment : BottomSheetDialogFragment(), OptionsRecyc
} }
override fun optionClicked(optionId: Int) { override fun optionClicked(optionsId:Int, optionId: Int) {
viewModel.optionClicked(optionId) viewModel.optionClicked(optionsId, optionId)
} }
} }

View File

@ -27,12 +27,12 @@ import com.isolaatti.home.presentation.FeedViewModel
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.posting.comments.ui.BottomSheetPostComments import com.isolaatti.posting.comments.ui.BottomSheetPostComments
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback import com.isolaatti.common.OnUserInteractedWithPostCallback
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
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
import com.isolaatti.posting.posts.presentation.EditPostContract import com.isolaatti.posting.posts.presentation.EditPostContract
@ -194,14 +194,14 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
val textViewName: TextView? = header?.findViewById(R.id.textViewName) val textViewName: TextView? = header?.findViewById(R.id.textViewName)
val textViewEmail: TextView? = header?.findViewById(R.id.textViewEmail) val textViewEmail: TextView? = header?.findViewById(R.id.textViewEmail)
Picasso.get().load(UrlGen.userProfileImage(it.id)).into(image) Picasso.get().load(UrlGen.userProfileImage(it.userId)).into(image)
image?.setOnClickListener {_ -> image?.setOnClickListener {_ ->
PictureViewerActivity.startActivityWithUrls(requireContext(), arrayOf(UrlGen.userProfileImageFullQuality(it.id))) PictureViewerActivity.startActivityWithUrls(requireContext(), arrayOf(UrlGen.userProfileImageFullQuality(it.userId)))
} }
textViewName?.text = it.name textViewName?.text = it.name
textViewEmail?.text = it.email textViewEmail?.text = it.email
currentUserId = it.id currentUserId = it.userId
} }

View File

@ -8,6 +8,7 @@ import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
import com.isolaatti.posting.posts.presentation.UpdateEvent import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.domain.use_case.GetProfile import com.isolaatti.profile.domain.use_case.GetProfile
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -75,8 +76,8 @@ class FeedViewModel @Inject constructor(
} }
// User profile // User profile
private val _userProfile: MutableLiveData<UserProfileDto> = MutableLiveData() private val _userProfile: MutableLiveData<UserProfile> = MutableLiveData()
val userProfile: LiveData<UserProfileDto> get() = _userProfile val userProfile: LiveData<UserProfile> get() = _userProfile
fun getProfile() { fun getProfile() {
viewModelScope.launch { viewModelScope.launch {

View File

@ -2,8 +2,7 @@ package com.isolaatti.posting.comments.domain.model
import com.isolaatti.posting.comments.data.remote.CommentDto import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.posting.posts.data.remote.FeedDto
data class Comment( data class Comment(
val id: Long, val id: Long,

View File

@ -5,10 +5,8 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.isolaatti.databinding.CommentLayoutBinding import com.isolaatti.databinding.CommentLayoutBinding
import com.isolaatti.posting.comments.data.remote.CommentDto
import com.isolaatti.posting.comments.domain.model.Comment import com.isolaatti.posting.comments.domain.model.Comment
import com.isolaatti.posting.common.domain.OnUserInteractedCallback import com.isolaatti.common.OnUserInteractedCallback
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso
import io.noties.markwon.Markwon import io.noties.markwon.Markwon

View File

@ -1,12 +1,10 @@
package com.isolaatti.posting.comments.ui package com.isolaatti.posting.comments.ui
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast import android.widget.Toast
import androidx.core.view.doOnLayout
import androidx.core.widget.doOnTextChanged import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
@ -15,9 +13,7 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HALF_EXPANDED
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.isolaatti.R import com.isolaatti.R
@ -28,12 +24,12 @@ import com.isolaatti.posting.comments.domain.model.Comment
import com.isolaatti.posting.comments.presentation.CommentsRecyclerViewAdapter import com.isolaatti.posting.comments.presentation.CommentsRecyclerViewAdapter
import com.isolaatti.posting.comments.presentation.CommentsViewModel import com.isolaatti.posting.comments.presentation.CommentsViewModel
import com.isolaatti.posting.comments.presentation.UpdateEvent import com.isolaatti.posting.comments.presentation.UpdateEvent
import com.isolaatti.posting.common.domain.OnUserInteractedCallback import com.isolaatti.common.OnUserInteractedCallback
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.utils.PicassoImagesPluginDef import com.isolaatti.utils.PicassoImagesPluginDef
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -42,7 +38,6 @@ import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
import io.noties.markwon.linkify.LinkifyPlugin import io.noties.markwon.linkify.LinkifyPlugin
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint

View File

@ -1,5 +0,0 @@
package com.isolaatti.posting.common.domain
interface Ownable {
val userId: Int
}

View File

@ -1,8 +0,0 @@
package com.isolaatti.posting.common.options_bottom_sheet.domain
/**
* @param optionId Identify action
* @param callerId Identify who started dialog
* @param payload Data to identify on what item perform action
*/
data class OptionClicked(val optionId: Int, val callerId: Int, val payload: Any? = null)

View File

@ -1,6 +1,6 @@
package com.isolaatti.posting.posts.domain.entity package com.isolaatti.posting.posts.domain.entity
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.posting.posts.data.remote.FeedDto import com.isolaatti.posting.posts.data.remote.FeedDto
data class Post( data class Post(

View File

@ -1,9 +1,10 @@
package com.isolaatti.posting.posts.presentation package com.isolaatti.posting.posts.presentation
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback import com.isolaatti.common.OnUserInteractedWithPostCallback
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
abstract class PostListingRecyclerViewAdapterWiring(private val postsViewModelBase: PostListingViewModelBase) : OnUserInteractedWithPostCallback { abstract class PostListingRecyclerViewAdapterWiring(private val postsViewModelBase: PostListingViewModelBase) :
OnUserInteractedWithPostCallback {
override fun onLiked(postId: Long) { override fun onLiked(postId: Long) {
postsViewModelBase.likePost(postId) postsViewModelBase.likePost(postId)

View File

@ -12,7 +12,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import com.google.android.material.card.MaterialCardView import com.google.android.material.card.MaterialCardView
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback import com.isolaatti.common.OnUserInteractedWithPostCallback
import com.isolaatti.posting.posts.domain.entity.Post import com.isolaatti.posting.posts.domain.entity.Post
import com.isolaatti.utils.UrlGen.userProfileImage import com.isolaatti.utils.UrlGen.userProfileImage
import com.squareup.picasso.Picasso import com.squareup.picasso.Picasso

View File

@ -102,6 +102,11 @@ class PostViewerActivity : IsolaattiBaseActivity() {
binding.commentsInfo.setOnClickListener { binding.commentsInfo.setOnClickListener {
openComments() openComments()
} }
binding.toolbar.setNavigationOnClickListener {
finish()
}
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)

View File

@ -3,7 +3,7 @@ package com.isolaatti.profile.data.remote
data class UserProfileDto( data class UserProfileDto(
val id: Int, val id: Int,
val name: String, val name: String,
val email: String, val email: String?,
val numberOfFollowers: Int, val numberOfFollowers: Int,
val numberOfFollowing: Int, val numberOfFollowing: Int,
val numberOfLikes: Int, val numberOfLikes: Int,

View File

@ -1,8 +1,9 @@
package com.isolaatti.profile.data.repository package com.isolaatti.profile.data.repository
import android.util.Log
import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.remote.ProfileApi
import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.domain.ProfileRepository import com.isolaatti.profile.domain.ProfileRepository
import com.isolaatti.profile.domain.entity.UserProfile
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
@ -10,15 +11,20 @@ import retrofit2.awaitResponse
import javax.inject.Inject import javax.inject.Inject
class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository { class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository {
override fun getProfile(userId: Int): Flow<Resource<UserProfileDto>> = flow { override fun getProfile(userId: Int): Flow<Resource<UserProfile>> = flow {
try { try {
val result = profileApi.userProfile(userId).awaitResponse() val result = profileApi.userProfile(userId).awaitResponse()
if(result.isSuccessful) { if(result.isSuccessful) {
emit(Resource.Success(result.body())) val dto = result.body()
return@flow if(dto != null) {
emit(Resource.Success(UserProfile.fromDto(dto)))
}
} else {
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
} }
emit(Resource.Error(Resource.Error.mapErrorCode(result.code())))
} catch(_: Exception) { } catch(e: Exception) {
Log.e("ProfileRepositoryImpl", e.message.toString())
emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
} }
} }

View File

@ -1,10 +1,9 @@
package com.isolaatti.profile.domain package com.isolaatti.profile.domain
import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface ProfileRepository { interface ProfileRepository {
fun getProfile(userId: Int): Flow<Resource<UserProfileDto>> fun getProfile(userId: Int): Flow<Resource<UserProfile>>
} }

View File

@ -0,0 +1,44 @@
package com.isolaatti.profile.domain.entity
import com.isolaatti.common.Ownable
import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.utils.UrlGen
data class UserProfile(
override val userId: Int,
val name: String,
val email: String?,
val numberOfFollowers: Int,
val numberOfFollowing: Int,
val numberOfLikes: Int,
val numberOfPosts: Int,
val isUserItself: Boolean,
val followingThisUser: Boolean,
val thisUserIsFollowingMe: Boolean,
val profileImageId: String?,
val descriptionText: String?,
val descriptionAudioId: String?
) : Ownable {
val profileAvatarPictureUrl: String get() = UrlGen.userProfileImage(userId)
val profilePictureUrl: String get() = UrlGen.userProfileImageFullQuality(userId)
companion object {
fun fromDto(userProfileDto: UserProfileDto): UserProfile {
return UserProfile(
userId = userProfileDto.id,
name = userProfileDto.name,
email = userProfileDto.email,
numberOfFollowers = userProfileDto.numberOfFollowers,
numberOfFollowing = userProfileDto.numberOfFollowing,
numberOfLikes = userProfileDto.numberOfLikes,
numberOfPosts = userProfileDto.numberOfPosts,
isUserItself = userProfileDto.isUserItself,
followingThisUser = userProfileDto.followingThisUser,
thisUserIsFollowingMe = userProfileDto.thisUserIsFollowingMe,
profileImageId = userProfileDto.profileImageId,
descriptionText = userProfileDto.descriptionText,
descriptionAudioId = userProfileDto.descriptionAudioId
)
}
}
}

View File

@ -2,10 +2,11 @@ package com.isolaatti.profile.domain.use_case
import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.domain.ProfileRepository import com.isolaatti.profile.domain.ProfileRepository
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import javax.inject.Inject import javax.inject.Inject
class GetProfile @Inject constructor(private val profileRepository: ProfileRepository) { class GetProfile @Inject constructor(private val profileRepository: ProfileRepository) {
operator fun invoke(userId: Int): Flow<Resource<UserProfileDto>> = profileRepository.getProfile(userId) operator fun invoke(userId: Int): Flow<Resource<UserProfile>> = profileRepository.getProfile(userId)
} }

View File

@ -11,6 +11,7 @@ import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
import com.isolaatti.posting.posts.presentation.UpdateEvent import com.isolaatti.posting.posts.presentation.UpdateEvent
import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.domain.ProfileRepository import com.isolaatti.profile.domain.ProfileRepository
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.domain.use_case.GetProfile import com.isolaatti.profile.domain.use_case.GetProfile
import com.isolaatti.profile.domain.use_case.GetProfilePosts import com.isolaatti.profile.domain.use_case.GetProfilePosts
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
@ -28,8 +29,8 @@ import javax.inject.Inject
@HiltViewModel @HiltViewModel
class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val getProfilePostsUseCase: GetProfilePosts) : PostListingViewModelBase() { class ProfileViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val getProfilePostsUseCase: GetProfilePosts) : PostListingViewModelBase() {
private val _profile = MutableLiveData<UserProfileDto>() private val _profile = MutableLiveData<UserProfile>()
val profile: LiveData<UserProfileDto> get() = _profile val profile: LiveData<UserProfile> get() = _profile
var profileId: Int = 0 var profileId: Int = 0

View File

@ -3,44 +3,9 @@ package com.isolaatti.profile.ui
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import androidx.activity.addCallback
import androidx.activity.viewModels
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
import androidx.navigation.fragment.NavHostFragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.viewpager.widget.PagerAdapter
import androidx.viewpager2.adapter.FragmentStateAdapter
import androidx.viewpager2.adapter.FragmentViewHolder
import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.OnOffsetChangedListener
import com.google.android.material.tabs.TabLayoutMediator
import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityProfileBinding import com.isolaatti.databinding.ActivityProfileBinding
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
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.AndroidEntryPoint
import io.noties.markwon.AbstractMarkwonPlugin
import io.noties.markwon.Markwon
import io.noties.markwon.MarkwonConfiguration
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
import io.noties.markwon.linkify.LinkifyPlugin
@AndroidEntryPoint @AndroidEntryPoint
class ProfileActivity : IsolaattiBaseActivity() { class ProfileActivity : IsolaattiBaseActivity() {

View File

@ -2,6 +2,7 @@ package com.isolaatti.profile.ui
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -24,11 +25,12 @@ import com.isolaatti.followers.domain.FollowingState
import com.isolaatti.home.FeedFragment import com.isolaatti.home.FeedFragment
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.posting.comments.ui.BottomSheetPostComments import com.isolaatti.posting.comments.ui.BottomSheetPostComments
import com.isolaatti.posting.common.domain.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
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
import com.isolaatti.posting.posts.presentation.EditPostContract import com.isolaatti.posting.posts.presentation.EditPostContract
@ -36,6 +38,7 @@ import com.isolaatti.posting.posts.presentation.PostListingRecyclerViewAdapterWi
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.profile.data.remote.UserProfileDto import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.profile.domain.entity.UserProfile
import com.isolaatti.profile.presentation.ProfileViewModel import com.isolaatti.profile.presentation.ProfileViewModel
import com.isolaatti.utils.PicassoImagesPluginDef import com.isolaatti.utils.PicassoImagesPluginDef
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
@ -75,9 +78,9 @@ class ProfileMainFragment : Fragment() {
} }
} }
private val profileObserver = Observer<UserProfileDto> { profile -> private val profileObserver = Observer<UserProfile> { profile ->
Picasso.get() Picasso.get()
.load(UrlGen.userProfileImage(profile.id)) .load(UrlGen.userProfileImage(profile.userId))
.into(viewBinding.profileImageView) .into(viewBinding.profileImageView)
title = profile.name title = profile.name
@ -94,6 +97,12 @@ class ProfileMainFragment : Fragment() {
) )
viewBinding.profileImageView.setOnClickListener {
optionsViewModel.setOptions(Options.PROFILE_PHOTO_OPTIONS, CALLER_ID, profile)
val fragment = BottomSheetPostOptionsFragment()
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
setupUiForUserType(profile.isUserItself) setupUiForUserType(profile.isUserItself)
} }
@ -131,27 +140,49 @@ class ProfileMainFragment : Fragment() {
} }
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked -> private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
if(optionClicked?.callerId == FeedFragment.CALLER_ID) { if(optionClicked?.callerId == CALLER_ID) {
// post id should come as payload Log.d("ProfileMainFragment", optionClicked.toString())
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()
} when(optionClicked.optionsId) {
Options.Option.OPTION_EDIT -> { 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) {
PictureViewerActivity.startActivityWithUrls(requireContext(), arrayOf(profilePictureUrl))
}
}
}
optionsViewModel.handle() optionsViewModel.handle()
editDiscussion.launch(post.id)
} }
Options.Option.OPTION_REPORT -> { Options.POST_OPTIONS -> {
optionsViewModel.handle() // 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()
}
}
} }
} }
} }
} }
@ -163,7 +194,7 @@ class ProfileMainFragment : Fragment() {
viewBinding.topAppBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset -> viewBinding.topAppBarLayout.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
if (scrollRange == -1) scrollRange = appBarLayout.totalScrollRange if (scrollRange == -1) scrollRange = appBarLayout.totalScrollRange
if (scrollRange + verticalOffset == 0) { if (scrollRange + verticalOffset == 0) {
viewBinding.collapsingToolbarLayout.title = title viewBinding.collapsingToolbarLayout.title = viewModel.profile.value?.name
isShow = true isShow = true
} else if (isShow) { } else if (isShow) {
viewBinding.collapsingToolbarLayout.title = " " viewBinding.collapsingToolbarLayout.title = " "
@ -278,7 +309,7 @@ class ProfileMainFragment : Fragment() {
} }
override fun onOptions(post: Ownable) { override fun onOptions(post: Ownable) {
optionsViewModel.setOptions(Options.POST_OPTIONS, FeedFragment.CALLER_ID, post) optionsViewModel.setOptions(Options.POST_OPTIONS, CALLER_ID, post)
val modalBottomSheet = BottomSheetPostOptionsFragment() val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG) modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
} }
@ -305,11 +336,12 @@ class ProfileMainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupCollapsingBar()
setupPostsAdapter() setupPostsAdapter()
bind() bind()
setObservers() setObservers()
getData() getData()
setupCollapsingBar()
@ -322,4 +354,8 @@ class ProfileMainFragment : Fragment() {
} }
} }
} }
companion object {
const val CALLER_ID = 30
}
} }

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#000000"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM17,13L7,13v-2h10v2z"/>
</vector>

View File

@ -84,4 +84,8 @@
<string name="comments">Comments</string> <string name="comments">Comments</string>
<string name="likes_info">Claps: %d</string> <string name="likes_info">Claps: %d</string>
<string name="comments_info">Comments: %d</string> <string name="comments_info">Comments: %d</string>
<string name="view_photo">View photo</string>
<string name="change_profile_photo">Change profile photo</string>
<string name="profile_photo">Profile photo</string>
<string name="remove_photo">Remove photo</string>
</resources> </resources>