This commit is contained in:
erike 2023-08-24 20:56:08 -06:00
parent 7f042917e2
commit 1e02f0bc6f
25 changed files with 167 additions and 38 deletions

View File

@ -1,8 +1,7 @@
package com.isolaatti.auth
import android.content.Context
import com.isolaatti.auth.data.AuthRepositoryImpl
import com.isolaatti.auth.data.local.KeyValueDao
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.auth.data.local.TokenStorage
import com.isolaatti.auth.data.remote.AuthApi
import com.isolaatti.auth.domain.AuthRepository
@ -11,7 +10,6 @@ import com.isolaatti.database.AppDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
@Module
@ -21,10 +19,6 @@ class Module {
fun provideAuthApi(retrofitClient: RetrofitClient): AuthApi {
return retrofitClient.client.create(AuthApi::class.java)
}
@Provides
fun provideKeyValueDao(database: AppDatabase): KeyValueDao {
return database.keyValueDao()
}
@Provides
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, keyValueDao: KeyValueDao): AuthRepository {

View File

@ -1,7 +1,7 @@
package com.isolaatti.auth.data
import com.isolaatti.auth.data.local.KeyValueDao
import com.isolaatti.auth.data.local.KeyValueEntity
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.settings.data.KeyValueEntity
import com.isolaatti.auth.data.remote.AuthTokenDto
import com.isolaatti.auth.data.local.TokenStorage
import com.isolaatti.auth.data.remote.AuthApi

View File

@ -0,0 +1,15 @@
package com.isolaatti.common
import android.content.Context
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.R
object Dialogs {
fun buildDeletePostDialog(context: Context, onContinue: (delete: Boolean) -> Unit, ): MaterialAlertDialogBuilder {
return MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete)
.setMessage(R.string.post_will_dropped)
.setPositiveButton(R.string.yes_continue) {_, _ -> onContinue(true)}
.setNegativeButton(R.string.cancel) { _, _ -> onContinue(false)}
}
}

View File

@ -2,8 +2,8 @@ package com.isolaatti.database
import androidx.room.Database
import androidx.room.RoomDatabase
import com.isolaatti.auth.data.local.KeyValueDao
import com.isolaatti.auth.data.local.KeyValueEntity
import com.isolaatti.settings.data.KeyValueDao
import com.isolaatti.settings.data.KeyValueEntity
@Database(entities = [KeyValueEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase() {

View File

@ -15,10 +15,10 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.about.AboutActivity
import com.isolaatti.common.Dialogs
import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.databinding.FragmentFeedBinding
import com.isolaatti.drafts.ui.DraftsActivity
@ -68,14 +68,23 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
}
}
val optionsObserver: Observer<OptionClicked> = Observer {
if(it.callerId == CALLER_ID) {
when(it.optionId) {
val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
if(optionClicked?.callerId == CALLER_ID) {
// post id should come as payload
val postId = optionClicked.payload as? Long ?: return@Observer
when(optionClicked.optionId) {
Options.Option.OPTION_DELETE -> {
Dialogs.buildDeletePostDialog(requireContext()) { delete ->
optionsViewModel.handle()
if(delete) {
viewModel.deletePost(postId)
}
}.show()
}
Options.Option.OPTION_EDIT -> {
optionsViewModel.handle()
CreatePostActivity.startActivityEditMode(requireContext(), postId)
}
Options.Option.OPTION_REPORT -> {
@ -197,15 +206,12 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
}
fun onNewMenuItemClicked(v: View) {
}
override fun onLiked(postId: Long) = viewModel.likePost(postId)
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
override fun onOptions(postId: Long) {
optionsViewModel.setOptions(Options.myPostOptions, CALLER_ID)
optionsViewModel.setOptions(Options.myPostOptions, CALLER_ID, postId)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}

View File

@ -35,8 +35,8 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
val optionsObserver: Observer<OptionClicked> = Observer {
if(it.callerId == CALLER_ID) {
val optionsObserver: Observer<OptionClicked?> = Observer {
if(it?.callerId == CALLER_ID) {
optionsViewModel.optionClicked(-1)
}

View File

@ -1,3 +1,8 @@
package com.isolaatti.posting.common.options_bottom_sheet.domain
data class OptionClicked(val optionId: Int, val callerId: Int)
/**
* @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

@ -21,6 +21,7 @@ data class Options(
}
companion object {
val noOptions = Options(0, listOf())
val myPostOptions = Options(R.string.post_options_title, listOf(
Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE),
Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT),

View File

@ -12,16 +12,24 @@ class BottomSheetPostOptionsViewModel : ViewModel() {
private var _callerId: Int = 0
private val _optionClicked: MutableLiveData<OptionClicked> = MutableLiveData()
val optionClicked: LiveData<OptionClicked> get() = _optionClicked
private val _optionClicked: MutableLiveData<OptionClicked?> = MutableLiveData()
val optionClicked: LiveData<OptionClicked?> get() = _optionClicked
fun setOptions(options: Options, callerId: Int) {
private var _payload: Any? = null
fun handle() {
_optionClicked.postValue(null)
}
fun setOptions(options: Options, callerId: Int, payload: Any? = null) {
_options.postValue(options)
_callerId = callerId
_payload = payload
}
fun optionClicked(optionId: Int) {
_optionClicked.postValue(OptionClicked(optionId, _callerId))
_optionClicked.postValue(OptionClicked(optionId, _callerId, _payload))
}
}

View File

@ -1,6 +1,7 @@
package com.isolaatti.posting.common.options_bottom_sheet.ui
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -39,14 +40,16 @@ class BottomSheetPostOptionsFragment : BottomSheetDialogFragment(), OptionsRecyc
viewModel.options.observe(viewLifecycleOwner) {
Log.d("BottomSheetPostOptionsFragment", "entra")
renderOptions(it)
}
viewModel.optionClicked.observe(viewLifecycleOwner) {
if(it.optionId > -1) {
if(it != null) {
(dialog as BottomSheetDialog).dismiss()
}
}
}

View File

@ -12,6 +12,6 @@ interface PostApi {
fun editPost(@Body editedPost: EditPostDto): Call<FeedDto.PostDto>
@POST("Posting/Delete")
fun deletePost(@Body postToDelete: DeletePostDto): Call<Any>
fun deletePost(@Body postToDelete: DeletePostDto): Call<PostDeletedDto>
}

View File

@ -0,0 +1,3 @@
package com.isolaatti.posting.posts.data.remote
data class PostDeletedDto(val operationTime: String, val postId: Long, val success: Boolean)

View File

@ -2,11 +2,13 @@ package com.isolaatti.posting.posts.data.repository
import com.google.gson.Gson
import com.isolaatti.posting.posts.data.remote.CreatePostDto
import com.isolaatti.posting.posts.data.remote.DeletePostDto
import com.isolaatti.posting.posts.data.remote.EditPostDto
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
import com.isolaatti.posting.posts.data.remote.FeedsApi
import com.isolaatti.posting.posts.data.remote.PostApi
import com.isolaatti.posting.posts.data.remote.PostDeletedDto
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
@ -90,4 +92,23 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
override fun deletePost(postId: Long): Flow<Resource<PostDeletedDto>> = flow {
emit(Resource.Loading())
try {
val result = postApi.deletePost(DeletePostDto(postId)).execute()
if(result.isSuccessful) {
emit(Resource.Success(result.body()))
return@flow
}
when(result.code()) {
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
}
} catch(_: Exception) {
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
}
}
}

View File

@ -4,6 +4,7 @@ import com.isolaatti.posting.posts.data.remote.CreatePostDto
import com.isolaatti.posting.posts.data.remote.EditPostDto
import com.isolaatti.posting.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
import com.isolaatti.posting.posts.data.remote.PostDeletedDto
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
@ -15,4 +16,5 @@ interface PostsRepository {
fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>>
fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>>
fun deletePost(postId: Long): Flow<Resource<PostDeletedDto>>
}

View File

@ -0,0 +1,13 @@
package com.isolaatti.posting.posts.domain.use_case
import com.isolaatti.posting.posts.data.remote.PostDeletedDto
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.utils.Resource
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
class DeletePost @Inject constructor(private val postsRepository: PostsRepository) {
operator fun invoke(postId: Long): Flow<Resource<PostDeletedDto>> {
return postsRepository.deletePost(postId)
}
}

View File

@ -4,9 +4,9 @@ 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.posting.posts.domain.use_case.DeletePost
import com.isolaatti.profile.domain.use_case.GetProfilePosts
import com.isolaatti.utils.Resource
import kotlinx.coroutines.Dispatchers
@ -21,9 +21,12 @@ abstract class PostListingViewModelBase : ViewModel() {
lateinit var likesRepository: LikesRepository
@Inject
lateinit var getProfilePosts: GetProfilePosts
@Inject
lateinit var deletePostUseCase: DeletePost
val posts: MutableLiveData<Pair<FeedDto?, UpdateEvent>?> = MutableLiveData()
val loadingPosts = MutableLiveData(false)
val noMoreContent = MutableLiveData(false)
@ -50,7 +53,7 @@ abstract class PostListingViewModelBase : ViewModel() {
})
}
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, likedPost?.post?.id)))
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, index)))
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
@ -66,7 +69,27 @@ abstract class PostListingViewModelBase : ViewModel() {
numberOfLikes = likeDto.likesCount
})
}
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, likedPost?.post?.id)))
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_LIKED, index)))
}.flowOn(Dispatchers.IO).launchIn(this)
}
}
fun deletePost(postId: Long) {
viewModelScope.launch {
deletePostUseCase(postId).onEach { res ->
when(res) {
is Resource.Success -> {
val postDeleted = posts.value?.first?.data?.find { postDto -> postDto.post.id == postId }
?: return@onEach
val index = posts.value?.first?.data?.indexOf(postDeleted)
posts.value?.first?.data?.removeAt(index!!)
posts.postValue(posts.value?.copy(second = UpdateEvent(UpdateEvent.UpdateType.POST_REMOVED, index)))
}
is Resource.Loading -> {}
is Resource.Error -> {}
}
}.flowOn(Dispatchers.IO).launchIn(this)
}
}

View File

@ -136,8 +136,8 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
notifyDataSetChanged()
return
}
val postUpdated = feedDto?.data?.find { p -> p.post.id == updateEvent.affectedId }
val position = feedDto?.data?.indexOf(postUpdated)
val postUpdated = updateEvent.affectedPosition?.let { feedDto?.data?.get(it) }
val position = updateEvent.affectedPosition
previousSize = itemCount
feedDto = updatedFeed

View File

@ -1,6 +1,6 @@
package com.isolaatti.posting.posts.presentation
data class UpdateEvent(val updateType: UpdateType, val affectedId: Long?) {
data class UpdateEvent(val updateType: UpdateType, val affectedPosition: Int?) {
enum class UpdateType {
POST_LIKED,
POST_COMMENTED,

View File

@ -1,6 +1,7 @@
package com.isolaatti.posting.posts.ui
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.PersistableBundle
@ -31,6 +32,14 @@ class CreatePostActivity : IsolaattiBaseActivity() {
const val EXTRA_KEY_POST_ID = "postId"
const val EXTRA_KEY_POST_POSTED = "post"
fun startActivityEditMode(context: Context, postId: Long) {
val intent = Intent(context, CreatePostActivity::class.java).apply {
putExtra(EXTRA_KEY_MODE, EXTRA_MODE_EDIT)
putExtra(EXTRA_KEY_POST_ID, postId)
}
context.startActivity(intent)
}
}
lateinit var binding: ActivityCreatePostBinding

View File

@ -211,7 +211,7 @@ class ProfileMainFragment : Fragment() {
}
override fun onOptions(postId: Long) {
optionsViewModel.setOptions(Options.myPostOptions, FeedFragment.CALLER_ID)
optionsViewModel.setOptions(Options.myPostOptions, FeedFragment.CALLER_ID, postId)
val modalBottomSheet = BottomSheetPostOptionsFragment()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}

View File

@ -0,0 +1,6 @@
package com.isolaatti.reports.ui
import androidx.appcompat.app.AppCompatActivity
class ReportsActivity : AppCompatActivity() {
}

View File

@ -0,0 +1,17 @@
package com.isolaatti.settings
import com.isolaatti.database.AppDatabase
import com.isolaatti.settings.data.KeyValueDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
class Module {
@Provides
fun provideKeyValueDao(database: AppDatabase): KeyValueDao {
return database.keyValueDao()
}
}

View File

@ -1,4 +1,4 @@
package com.isolaatti.auth.data.local
package com.isolaatti.settings.data
import androidx.room.Dao
import androidx.room.Insert

View File

@ -1,4 +1,4 @@
package com.isolaatti.auth.data.local
package com.isolaatti.settings.data
import androidx.room.Entity
import androidx.room.PrimaryKey

View File

@ -65,4 +65,7 @@
<string name="million_suffix">M</string>
<string name="people">People</string>
<string name="about">About</string>
<string name="post_will_dropped">This discussion and all related content will be dropped. Continue?</string>
<string name="yes_continue">Yes, delete</string>
<string name="cancel">Cancel</string>
</resources>