diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReport.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReport.kt new file mode 100644 index 000000000..03abc3d82 --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReport.kt @@ -0,0 +1,17 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CommentReport( + @SerialName("id") val id: CommentReportId, + @SerialName("creator_id") val creatorId: PersonId, + @SerialName("comment_id") val commentId: CommentId, + @SerialName("original_comment_text") val originalCommentText: String, + @SerialName("reason") val reason: String, + @SerialName("resolved") val resolved: Boolean, + @SerialName("resolver_id") val resolverId: PersonId? = null, + @SerialName("published") val published: String, + @SerialName("updated") val updated: String? = null, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReportResponse.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReportResponse.kt new file mode 100644 index 000000000..196fa3ffd --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReportResponse.kt @@ -0,0 +1,9 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CommentReportResponse( + @SerialName("comment_report_view") val commentReportView: CommentReportView, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReportView.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReportView.kt new file mode 100644 index 000000000..7c9086bea --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CommentReportView.kt @@ -0,0 +1,18 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CommentReportView( + @SerialName("comment_report") val commentReport: CommentReport, + @SerialName("comment") val comment: Comment, + @SerialName("post") val post: Post, + @SerialName("community") val community: Community, + @SerialName("creator") val creator: Person, + @SerialName("comment_creator") val commentCreator: Person, + @SerialName("counts") val counts: CommentAggregates, + @SerialName("creator_banned_from_community") val creatorBannedFromCommunity: Boolean, + @SerialName("my_vote") val myVote: Int? = null, + @SerialName("resolver") val resolver: Person? = null, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/Constants.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/Constants.kt index 7f39a8ff6..13a6a88be 100644 --- a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/Constants.kt +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/Constants.kt @@ -12,4 +12,6 @@ typealias LocalUserId = Int typealias CustomEmojiId = Int typealias PersonMentionId = Int typealias CommentReplyId = Int +typealias CommentReportId = Int +typealias PostReportId = Int typealias PrivateMessageId = Int diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CreateCommentReportForm.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CreateCommentReportForm.kt new file mode 100644 index 000000000..4197bd8b1 --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CreateCommentReportForm.kt @@ -0,0 +1,11 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CreateCommentReportForm( + @SerialName("comment_id") val commentId: CommentId, + @SerialName("reason") val reason: String, + @SerialName("auto") val auth: String, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CreatePostReportForm.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CreatePostReportForm.kt new file mode 100644 index 000000000..ea14846b6 --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/CreatePostReportForm.kt @@ -0,0 +1,11 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CreatePostReportForm( + @SerialName("post_id") val postId: PostId, + @SerialName("reason") val reason: String, + @SerialName("auto") val auth: String, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReport.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReport.kt new file mode 100644 index 000000000..64bd97e40 --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReport.kt @@ -0,0 +1,19 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostReport( + @SerialName("id") val id: PostReportId, + @SerialName("creator_id") val creatorId: PersonId, + @SerialName("post_id") val postId: PostId, + @SerialName("original_post_name") val originalPostName: String, + @SerialName("original_post_url") val originalPostUrl: String? = null, + @SerialName("original_post_body") val originalPostBody: String? = null, + @SerialName("reason") val reason: String, + @SerialName("resolved") val resolved: Boolean, + @SerialName("resolver_id") val resolverId: PersonId? = null, + @SerialName("published") val published: String, + @SerialName("updated") val updated: String? = null, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReportResponse.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReportResponse.kt new file mode 100644 index 000000000..59d3a6914 --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReportResponse.kt @@ -0,0 +1,9 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostReportResponse( + @SerialName("post_report_view") val postReportView: PostReportView, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReportView.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReportView.kt new file mode 100644 index 000000000..e2683fa0a --- /dev/null +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/dto/PostReportView.kt @@ -0,0 +1,17 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.api.dto + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class PostReportView( + @SerialName("post_report") val postReport: PostReport, + @SerialName("post") val post: Post, + @SerialName("community") val community: Community, + @SerialName("creator") val creator: Person, + @SerialName("post_creator") val postCreator: Person, + @SerialName("creator_banned_from_community") val creatorBannedFromCommunity: Boolean, + @SerialName("my_vote") val myVote: Int? = null, + @SerialName("counts") val counts: PostAggregates, + @SerialName("resolver") val resolver: Person? = null, +) diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/CommentService.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/CommentService.kt index 21f45393d..6afd214ba 100644 --- a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/CommentService.kt +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/CommentService.kt @@ -1,10 +1,12 @@ package com.github.diegoberaldin.raccoonforlemmy.core.api.service import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommentReplyResponse +import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommentReportResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommentResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommentSortType import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreateCommentForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreateCommentLikeForm +import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreateCommentReportForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.DeleteCommentForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.EditCommentForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.GetCommentResponse @@ -86,4 +88,11 @@ interface CommentService { @Header("Authorization") authHeader: String? = null, @Body form: DeleteCommentForm, ): Response + + @POST("comment/report") + @Headers("Content-Type: application/json") + suspend fun createReport( + @Body form: CreateCommentReportForm, + @Header("Authorization") authHeader: String? = null, + ): Response } diff --git a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/PostService.kt b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/PostService.kt index ddc1d5a93..de3142931 100644 --- a/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/PostService.kt +++ b/core-api/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/api/service/PostService.kt @@ -3,6 +3,7 @@ package com.github.diegoberaldin.raccoonforlemmy.core.api.service import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CommentReplyResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePostLikeForm +import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePostReportForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.DeletePostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.EditPostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.GetPostResponse @@ -10,6 +11,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.GetPostsResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.ListingType import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.MarkPostAsReadForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PictrsImages +import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PostReportResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.PostResponse import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SavePostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SortType @@ -97,4 +99,11 @@ interface PostService { @Header("Authorization") authHeader: String? = null, @Body content: MultiPartFormDataContent, ): Response + + @POST("post/report") + @Headers("Content-Type: application/json") + suspend fun createReport( + @Header("Authorization") authHeader: String? = null, + @Body form: CreatePostReportForm, + ): Response } diff --git a/core-commonui/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt b/core-commonui/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt index 68162e5a2..9e51ed0ad 100644 --- a/core-commonui/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt +++ b/core-commonui/src/androidMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt @@ -11,6 +11,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImag import com.github.diegoberaldin.raccoonforlemmy.core.commonui.instanceinfo.InstanceInfoMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel @@ -122,3 +123,13 @@ actual fun getModalDrawerViewModel(): ModalDrawerMviModel { val res: ModalDrawerMviModel by inject(ModalDrawerMviModel::class.java) return res } + +actual fun getCreateReportViewModel( + postId: Int?, + commentId: Int?, +): CreateReportMviModel { + val res: CreateReportMviModel by inject(CreateReportMviModel::class.java, parameters = { + parametersOf(postId, commentId) + }) + return res +} diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt index bb4412113..d111ff901 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/communitydetail/CommunityDetailScreen.kt @@ -84,6 +84,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImag import com.github.diegoberaldin.raccoonforlemmy.core.commonui.instanceinfo.InstanceInfoScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter @@ -427,6 +428,7 @@ class CommunityDetailScreen( options = buildList { add(stringResource(MR.strings.post_action_share)) add(stringResource(MR.strings.post_action_hide)) + add(stringResource(MR.strings.post_action_report)) if (post.creator?.id == uiState.currentUserId && !isOnOtherInstance) { add(stringResource(MR.strings.post_action_edit)) add(stringResource(MR.strings.comment_action_delete)) @@ -493,13 +495,13 @@ class CommunityDetailScreen( }, onOptionSelected = { optionIdx -> when (optionIdx) { - 3 -> model.reduce( + 4 -> model.reduce( CommunityDetailMviModel.Intent.DeletePost( post.id ) ) - 2 -> { + 3 -> { notificationCenter.addObserver( { model.reduce(CommunityDetailMviModel.Intent.Refresh) @@ -514,6 +516,14 @@ class CommunityDetailScreen( ) } + 2 -> { + bottomSheetNavigator.show( + CreateReportScreen( + postId = post.id + ) + ) + } + 1 -> model.reduce( CommunityDetailMviModel.Intent.Hide( idx diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/CommonUiModule.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/CommonUiModule.kt index c607b11e8..4c02aa867 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/CommonUiModule.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/CommonUiModule.kt @@ -23,6 +23,8 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.Default import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailViewModel +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportViewModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsViewModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel @@ -172,4 +174,14 @@ val commonUiModule = module { settingsRepository = get(), ) } + factory { + CreateReportViewModel( + postId = it[0], + commentId = it[1], + mvi = DefaultMviModel(CreateReportMviModel.UiState()), + identityRepository = get(), + postRepository = get(), + commentRepository = get(), + ) + } } diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt index 978812d7a..e972b9e06 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt @@ -11,6 +11,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImag import com.github.diegoberaldin.raccoonforlemmy.core.commonui.instanceinfo.InstanceInfoMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel @@ -62,4 +63,9 @@ expect fun getInboxChatViewModel(otherUserId: Int): InboxChatMviModel expect fun getSavedItemsViewModel(): SavedItemsMviModel -expect fun getModalDrawerViewModel(): ModalDrawerMviModel \ No newline at end of file +expect fun getModalDrawerViewModel(): ModalDrawerMviModel + +expect fun getCreateReportViewModel( + postId: Int? = null, + commentId: Int? = null, +): CreateReportMviModel \ No newline at end of file diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt index 1ed1f1acb..e0d05b05f 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/postdetail/PostDetailScreen.kt @@ -87,6 +87,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCo import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getPostDetailViewModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter @@ -302,6 +303,7 @@ class PostDetailScreen( }, options = buildList { add(stringResource(MR.strings.post_action_share)) + add(stringResource(MR.strings.post_action_report)) if (statePost.creator?.id == uiState.currentUserId && !isOnOtherInstance) { add(stringResource(MR.strings.post_action_edit)) add(stringResource(MR.strings.comment_action_delete)) @@ -347,7 +349,9 @@ class PostDetailScreen( }, onOptionSelected = { idx -> when (idx) { - 1 -> { + 3 -> model.reduce(PostDetailMviModel.Intent.DeletePost) + + 2 -> { notificationCenter.addObserver( { model.reduce(PostDetailMviModel.Intent.RefreshPost) @@ -360,7 +364,9 @@ class PostDetailScreen( ) } - 2 -> model.reduce(PostDetailMviModel.Intent.DeletePost) + 1 -> { + bottomSheetNavigator.show(CreateReportScreen(postId = statePost.id)) + } else -> model.reduce(PostDetailMviModel.Intent.SharePost) } @@ -501,6 +507,7 @@ class PostDetailScreen( separateUpAndDownVotes = uiState.separateUpAndDownVotes, autoLoadImages = uiState.autoLoadImages, options = buildList { + add(stringResource(MR.strings.post_action_report)) if (comment.creator?.id == uiState.currentUserId) { add(stringResource(MR.strings.post_action_edit)) add(stringResource(MR.strings.comment_action_delete)) @@ -575,15 +582,15 @@ class PostDetailScreen( ) } }, - onOptionSelected = { idx -> - when (idx) { - 1 -> model.reduce( + onOptionSelected = { optionId -> + when (optionId) { + 2 -> model.reduce( PostDetailMviModel.Intent.DeleteComment( comment.id ) ) - else -> { + 1 -> { notificationCenter.addObserver( { model.reduce(PostDetailMviModel.Intent.Refresh) @@ -598,6 +605,14 @@ class PostDetailScreen( ) ) } + + else -> { + bottomSheetNavigator.show( + CreateReportScreen( + commentId = comment.id + ) + ) + } } }) }, diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportMviModel.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportMviModel.kt new file mode 100644 index 000000000..d33052637 --- /dev/null +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportMviModel.kt @@ -0,0 +1,27 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.commonui.report + +import cafe.adriel.voyager.core.model.ScreenModel +import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel +import dev.icerock.moko.resources.desc.StringDesc + +interface CreateReportMviModel : + MviModel, + ScreenModel { + + sealed interface Intent { + data class SetText(val value: String) : Intent + data object Send : Intent + } + + data class UiState( + val text: String = "", + val textError: StringDesc? = null, + val loading: Boolean = false, + ) + + sealed interface Effect { + data object Success : Effect + + data class Failure(val message: String?) : Effect + } +} diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportScreen.kt new file mode 100644 index 000000000..62978baa7 --- /dev/null +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportScreen.kt @@ -0,0 +1,166 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.commonui.report + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Send +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import cafe.adriel.voyager.core.model.rememberScreenModel +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.navigator.bottomSheet.LocalBottomSheetNavigator +import com.github.diegoberaldin.raccoonforlemmy.core.appearance.theme.Spacing +import com.github.diegoberaldin.raccoonforlemmy.core.architecture.bindToLifecycle +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.BottomSheetHandle +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.components.ProgressHud +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getCreateReportViewModel +import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter +import com.github.diegoberaldin.raccoonforlemmy.resources.MR +import dev.icerock.moko.resources.compose.localized +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach + +class CreateReportScreen( + private val postId: Int? = null, + private val commentId: Int? = null, +) : Screen { + @OptIn(ExperimentalMaterial3Api::class) + @Composable + override fun Content() { + val model = rememberScreenModel { + getCreateReportViewModel( + postId = postId, + commentId = commentId, + ) + } + model.bindToLifecycle(key) + val uiState by model.uiState.collectAsState() + val snackbarHostState = remember { SnackbarHostState() } + val genericError = stringResource(MR.strings.message_generic_error) + val bottomSheetNavigator = LocalBottomSheetNavigator.current + val notificationCenter = remember { getNotificationCenter() } + + LaunchedEffect(model) { + model.effects.onEach { + when (it) { + is CreateReportMviModel.Effect.Failure -> { + snackbarHostState.showSnackbar(it.message ?: genericError) + } + + CreateReportMviModel.Effect.Success -> { + bottomSheetNavigator.hide() + } + } + }.launchIn(this) + } + + Box( + contentAlignment = Alignment.BottomCenter, + ) { + Column( + verticalArrangement = Arrangement.spacedBy(Spacing.s), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Column( + modifier = Modifier.fillMaxWidth().padding(top = Spacing.s), + verticalArrangement = Arrangement.spacedBy(Spacing.s), + horizontalAlignment = Alignment.CenterHorizontally + ) { + BottomSheetHandle() + val title = when { + commentId != null -> stringResource(MR.strings.create_report_title_comment) + else -> stringResource(MR.strings.create_report_title_post) + } + Text( + text = title, + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onBackground, + ) + } + + val commentFocusRequester = remember { FocusRequester() } + TextField( + modifier = Modifier + .focusRequester(commentFocusRequester) + .heightIn(min = 300.dp, max = 500.dp) + .fillMaxWidth(), + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, + ), + label = { + Text(text = stringResource(MR.strings.create_report_placeholder)) + }, + textStyle = MaterialTheme.typography.bodyMedium, + value = uiState.text, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + autoCorrect = true, + ), + onValueChange = { value -> + model.reduce(CreateReportMviModel.Intent.SetText(value)) + }, + isError = uiState.textError != null, + supportingText = { + if (uiState.textError != null) { + Text( + text = uiState.textError?.localized().orEmpty(), + color = MaterialTheme.colorScheme.error, + ) + } + }, + trailingIcon = { + IconButton( + content = { + Icon( + imageVector = Icons.Default.Send, + contentDescription = null, + ) + }, + onClick = { + model.reduce(CreateReportMviModel.Intent.Send) + }, + ) + }) + Spacer(Modifier.height(Spacing.xxl)) + } + + if (uiState.loading) { + ProgressHud() + } + + SnackbarHost( + modifier = Modifier.padding(bottom = Spacing.xxxl), + hostState = snackbarHostState + ) + } + } +} \ No newline at end of file diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportViewModel.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportViewModel.kt new file mode 100644 index 000000000..5b994e315 --- /dev/null +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/report/CreateReportViewModel.kt @@ -0,0 +1,66 @@ +package com.github.diegoberaldin.raccoonforlemmy.core.commonui.report + +import com.github.diegoberaldin.raccoonforlemmy.core.architecture.DefaultMviModel +import com.github.diegoberaldin.raccoonforlemmy.core.architecture.MviModel +import com.github.diegoberaldin.raccoonforlemmy.domain.identity.repository.IdentityRepository +import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.CommentRepository +import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository.PostRepository +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.launch + +class CreateReportViewModel( + private val postId: Int?, + private val commentId: Int?, + private val mvi: DefaultMviModel, + private val identityRepository: IdentityRepository, + private val postRepository: PostRepository, + private val commentRepository: CommentRepository, +) : CreateReportMviModel, + MviModel by mvi { + + override fun reduce(intent: CreateReportMviModel.Intent) { + when (intent) { + is CreateReportMviModel.Intent.SetText -> { + mvi.updateState { + it.copy(text = intent.value) + } + } + + CreateReportMviModel.Intent.Send -> submit() + } + } + + private fun submit() { + if (mvi.uiState.value.loading) { + return + } + val text = uiState.value.text + + mvi.updateState { it.copy(loading = true) } + mvi.scope?.launch(Dispatchers.IO) { + try { + val auth = identityRepository.authToken.value.orEmpty() + if (postId != null) { + postRepository.report( + postId = postId, + reason = text, + auth = auth, + ) + } else if (commentId != null) { + commentRepository.report( + commentId = commentId, + reason = text, + auth = auth, + ) + } + mvi.emitEffect(CreateReportMviModel.Effect.Success) + } catch (e: Throwable) { + val message = e.message + mvi.emitEffect(CreateReportMviModel.Effect.Failure(message)) + } finally { + mvi.updateState { it.copy(loading = false) } + } + } + } +} \ No newline at end of file diff --git a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt index c05e1dcb5..d1d7df835 100644 --- a/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt +++ b/core-commonui/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/userdetail/UserDetailScreen.kt @@ -84,6 +84,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getUserDetailVi import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter import com.github.diegoberaldin.raccoonforlemmy.core.utils.onClick @@ -390,6 +391,7 @@ class UserDetailScreen( autoLoadImages = uiState.autoLoadImages, options = buildList { add(stringResource(MR.strings.post_action_share)) + add(stringResource(MR.strings.post_action_report)) }, onUpVote = if (isOnOtherInstance) { null @@ -454,6 +456,14 @@ class UserDetailScreen( }, onOptionSelected = { optionIdx -> when (optionIdx) { + 1 -> { + bottomSheetNavigator.show( + CreateReportScreen( + postId = post.id + ) + ) + } + else -> model.reduce( UserDetailMviModel.Intent.SharePost(idx) ) @@ -599,6 +609,20 @@ class UserDetailScreen( onOpenCommunity = { community -> navigator?.push(CommunityDetailScreen(community)) }, + options = buildList { + add(stringResource(MR.strings.post_action_report)) + }, + onOptionSelected = { optionId -> + when (optionId) { + else -> { + bottomSheetNavigator.show( + CreateReportScreen( + commentId = comment.id + ) + ) + } + } + }, ) Divider( modifier = Modifier.padding(vertical = Spacing.xxxs), diff --git a/core-commonui/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt b/core-commonui/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt index 11cbbc1c8..c23c7a6f4 100644 --- a/core-commonui/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt +++ b/core-commonui/src/iosMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/core/commonui/di/Utils.kt @@ -11,6 +11,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImag import com.github.diegoberaldin.raccoonforlemmy.core.commonui.instanceinfo.InstanceInfoMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.navigation.NavigationCoordinator import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailMviModel +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.saveditems.SavedItemsMviModel import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailMviModel import com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.data.CommunityModel @@ -71,6 +72,11 @@ actual fun getSavedItemsViewModel(): SavedItemsMviModel = actual fun getModalDrawerViewModel(): ModalDrawerMviModel = CommonUiViewModelHelper.modalDrawerViewModel +actual fun getCreateReportViewModel( + postId: Int?, + commentId: Int?, +): CreateReportMviModel = CommonUiViewModelHelper.getCreateReportModel(postId, commentId) + object CommonUiViewModelHelper : KoinComponent { val navigationCoordinator: NavigationCoordinator by inject() @@ -145,4 +151,14 @@ object CommonUiViewModelHelper : KoinComponent { ) return model } + + fun getCreateReportModel( + postId: Int?, + commentId: Int?, + ): CreateReportMviModel { + val model: CreateReportMviModel by inject( + parameters = { parametersOf(postId, commentId) } + ) + return model + } } diff --git a/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/CommentRepository.kt b/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/CommentRepository.kt index c8145b33a..060644bd7 100644 --- a/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/CommentRepository.kt +++ b/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/CommentRepository.kt @@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreateCommentForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreateCommentLikeForm +import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreateCommentReportForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.DeleteCommentForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.EditCommentForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.SaveCommentForm @@ -247,4 +248,16 @@ class CommentRepository( ) services.comment.delete(authHeader = auth.toAuthHeader(), form = data) } + + suspend fun report(commentId: Int, reason: String, auth: String) { + val data = CreateCommentReportForm( + commentId = commentId, + reason = reason, + auth = auth, + ) + services.comment.createReport( + form = data, + authHeader = auth.toAuthHeader(), + ) + } } diff --git a/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/PostRepository.kt b/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/PostRepository.kt index cb473fdc0..7e338853e 100644 --- a/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/PostRepository.kt +++ b/domain-lemmy/repository/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/domain/lemmy/repository/PostRepository.kt @@ -2,6 +2,7 @@ package com.github.diegoberaldin.raccoonforlemmy.domain.lemmy.repository import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePostLikeForm +import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.CreatePostReportForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.DeletePostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.EditPostForm import com.github.diegoberaldin.raccoonforlemmy.core.api.dto.MarkPostAsReadForm @@ -246,4 +247,16 @@ class PostRepository( e.printStackTrace() null } + + suspend fun report(postId: Int, reason: String, auth: String) { + val data = CreatePostReportForm( + postId = postId, + reason = reason, + auth = auth, + ) + services.post.createReport( + form = data, + authHeader = auth.toAuthHeader(), + ) + } } diff --git a/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt b/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt index 1da126be5..014e18f12 100644 --- a/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt +++ b/feature-home/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/home/postlist/PostListScreen.kt @@ -71,6 +71,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImag import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.ListingTypeBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter @@ -306,6 +307,7 @@ class PostListScreen : Screen { options = buildList { add(stringResource(MR.strings.post_action_share)) add(stringResource(MR.strings.post_action_hide)) + add(stringResource(MR.strings.post_action_report)) if (post.creator?.id == uiState.currentUserId) { add(stringResource(MR.strings.post_action_edit)) add(stringResource(MR.strings.comment_action_delete)) @@ -367,13 +369,13 @@ class PostListScreen : Screen { }, onOptionSelected = { optionIdx -> when (optionIdx) { - 3 -> model.reduce( + 4 -> model.reduce( PostListMviModel.Intent.DeletePost( post.id ) ) - 2 -> { + 3 -> { notificationCenter.addObserver( { model.reduce(PostListMviModel.Intent.Refresh) @@ -388,6 +390,14 @@ class PostListScreen : Screen { ) } + 2 -> { + bottomSheetNavigator.show( + CreateReportScreen( + postId = post.id + ) + ) + } + 1 -> model.reduce( PostListMviModel.Intent.Hide(idx) ) diff --git a/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/login/LoginBottomSheet.kt b/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/login/LoginBottomSheet.kt index 13529f5cd..2c9800b0c 100644 --- a/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/login/LoginBottomSheet.kt +++ b/feature-profile/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/profile/login/LoginBottomSheet.kt @@ -18,10 +18,10 @@ import androidx.compose.material.icons.filled.Visibility import androidx.compose.material.icons.filled.VisibilityOff import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TextField @@ -65,7 +65,6 @@ class LoginBottomSheet : Screen { private const val HELP_URL = "https://join-lemmy.org/docs/users/01-getting-started.html" } - @OptIn(ExperimentalMaterial3Api::class) @Composable override fun Content() { val model = rememberScreenModel { getLoginBottomSheetViewModel() } @@ -75,7 +74,6 @@ class LoginBottomSheet : Screen { val snackbarHostState = remember { SnackbarHostState() } val genericError = stringResource(MR.strings.message_generic_error) val bottomSheetNavigator = LocalBottomSheetNavigator.current - val successfulLoginMessage = stringResource(MR.strings.message_login_successful) LaunchedEffect(model) { model.effects.onEach { @@ -87,7 +85,6 @@ class LoginBottomSheet : Screen { } LoginBottomSheetMviModel.Effect.LoginSuccess -> { - snackbarHostState.showSnackbar(message = successfulLoginMessage) bottomSheetNavigator.hide() } } @@ -98,214 +95,223 @@ class LoginBottomSheet : Screen { val navigator = remember { getNavigationCoordinator().getRootNavigator() } val settingsRepository = remember { getSettingsRepository() } - Column( - modifier = Modifier.padding( - top = Spacing.s, - start = Spacing.s, - end = Spacing.s, - bottom = Spacing.m, - ), - verticalArrangement = Arrangement.spacedBy(Spacing.s), - horizontalAlignment = Alignment.CenterHorizontally, + Box( + contentAlignment = Alignment.BottomCenter, ) { - Box { - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - BottomSheetHandle() - Text( - modifier = Modifier.padding(start = Spacing.s, top = Spacing.s), - text = stringResource(MR.strings.profile_button_login), - style = MaterialTheme.typography.titleLarge, - color = MaterialTheme.colorScheme.onBackground, - ) - } - IconButton( - modifier = Modifier.align(Alignment.TopEnd), - onClick = { - bottomSheetNavigator.hide() - handleUrl( - url = HELP_URL, - openExternal = settingsRepository.currentSettings.value.openUrlsInExternalBrowser, - uriHandler = uriHandler, - navigator = navigator - ) - }, - ) { - Icon( - imageVector = Icons.Default.HelpOutline, - contentDescription = null, - tint = MaterialTheme.colorScheme.onBackground, - ) - } - } - - val instanceFocusRequester = remember { FocusRequester() } - val usernameFocusRequester = remember { FocusRequester() } - val passwordFocusRequester = remember { FocusRequester() } - val tokenFocusRequester = remember { FocusRequester() } - - TextField( - modifier = Modifier.focusRequester(instanceFocusRequester), - label = { - Text(text = stringResource(MR.strings.login_field_instance_name)) - }, - singleLine = true, - value = uiState.instanceName, - isError = uiState.instanceNameError != null, - keyboardActions = KeyboardActions( - onNext = { - usernameFocusRequester.requestFocus() - }, + Column( + modifier = Modifier.padding( + top = Spacing.s, + start = Spacing.s, + end = Spacing.s, + bottom = Spacing.m, ), - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Email, - autoCorrect = false, - imeAction = ImeAction.Next, - ), - onValueChange = { value -> - model.reduce(LoginBottomSheetMviModel.Intent.SetInstanceName(value)) - }, - supportingText = { - if (uiState.instanceNameError != null) { + verticalArrangement = Arrangement.spacedBy(Spacing.s), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + BottomSheetHandle() Text( - text = uiState.instanceNameError?.localized().orEmpty(), - color = MaterialTheme.colorScheme.error, + modifier = Modifier.padding(start = Spacing.s, top = Spacing.s), + text = stringResource(MR.strings.profile_button_login), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onBackground, ) } - }, - ) - - TextField( - modifier = Modifier.focusRequester(usernameFocusRequester), - label = { - Text(text = stringResource(MR.strings.login_field_user_name)) - }, - singleLine = true, - value = uiState.username, - isError = uiState.usernameError != null, - keyboardActions = KeyboardActions( - onNext = { - passwordFocusRequester.requestFocus() - }, - ), - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Email, - autoCorrect = false, - imeAction = ImeAction.Next, - ), - onValueChange = { value -> - model.reduce(LoginBottomSheetMviModel.Intent.SetUsername(value)) - }, - supportingText = { - if (uiState.usernameError != null) { - Text( - text = uiState.usernameError?.localized().orEmpty(), - color = MaterialTheme.colorScheme.error, + IconButton( + modifier = Modifier.align(Alignment.TopEnd), + onClick = { + bottomSheetNavigator.hide() + handleUrl( + url = HELP_URL, + openExternal = settingsRepository.currentSettings.value.openUrlsInExternalBrowser, + uriHandler = uriHandler, + navigator = navigator + ) + }, + ) { + Icon( + imageVector = Icons.Default.HelpOutline, + contentDescription = null, + tint = MaterialTheme.colorScheme.onBackground, ) } - }, - ) + } - var transformation: VisualTransformation by remember { - mutableStateOf(PasswordVisualTransformation()) - } - TextField( - modifier = Modifier.focusRequester(passwordFocusRequester), - label = { - Text(text = stringResource(MR.strings.login_field_password)) - }, - singleLine = true, - value = uiState.password, - isError = uiState.passwordError != null, - keyboardActions = KeyboardActions( - onNext = { - tokenFocusRequester.requestFocus() + val instanceFocusRequester = remember { FocusRequester() } + val usernameFocusRequester = remember { FocusRequester() } + val passwordFocusRequester = remember { FocusRequester() } + val tokenFocusRequester = remember { FocusRequester() } + + TextField( + modifier = Modifier.focusRequester(instanceFocusRequester), + label = { + Text(text = stringResource(MR.strings.login_field_instance_name)) }, - ), - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Next, - ), - onValueChange = { value -> - model.reduce(LoginBottomSheetMviModel.Intent.SetPassword(value)) - }, - visualTransformation = transformation, - trailingIcon = { - Image( - modifier = Modifier.onClick { - transformation = if (transformation == VisualTransformation.None) { - PasswordVisualTransformation() + singleLine = true, + value = uiState.instanceName, + isError = uiState.instanceNameError != null, + keyboardActions = KeyboardActions( + onNext = { + usernameFocusRequester.requestFocus() + }, + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + autoCorrect = false, + imeAction = ImeAction.Next, + ), + onValueChange = { value -> + model.reduce(LoginBottomSheetMviModel.Intent.SetInstanceName(value)) + }, + supportingText = { + if (uiState.instanceNameError != null) { + Text( + text = uiState.instanceNameError?.localized().orEmpty(), + color = MaterialTheme.colorScheme.error, + ) + } + }, + ) + + TextField( + modifier = Modifier.focusRequester(usernameFocusRequester), + label = { + Text(text = stringResource(MR.strings.login_field_user_name)) + }, + singleLine = true, + value = uiState.username, + isError = uiState.usernameError != null, + keyboardActions = KeyboardActions( + onNext = { + passwordFocusRequester.requestFocus() + }, + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + autoCorrect = false, + imeAction = ImeAction.Next, + ), + onValueChange = { value -> + model.reduce(LoginBottomSheetMviModel.Intent.SetUsername(value)) + }, + supportingText = { + if (uiState.usernameError != null) { + Text( + text = uiState.usernameError?.localized().orEmpty(), + color = MaterialTheme.colorScheme.error, + ) + } + }, + ) + + var transformation: VisualTransformation by remember { + mutableStateOf(PasswordVisualTransformation()) + } + TextField( + modifier = Modifier.focusRequester(passwordFocusRequester), + label = { + Text(text = stringResource(MR.strings.login_field_password)) + }, + singleLine = true, + value = uiState.password, + isError = uiState.passwordError != null, + keyboardActions = KeyboardActions( + onNext = { + tokenFocusRequester.requestFocus() + }, + ), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Next, + ), + onValueChange = { value -> + model.reduce(LoginBottomSheetMviModel.Intent.SetPassword(value)) + }, + visualTransformation = transformation, + trailingIcon = { + Image( + modifier = Modifier.onClick { + transformation = if (transformation == VisualTransformation.None) { + PasswordVisualTransformation() + } else { + VisualTransformation.None + } + }, + imageVector = if (transformation == VisualTransformation.None) { + Icons.Default.VisibilityOff } else { - VisualTransformation.None - } - }, - imageVector = if (transformation == VisualTransformation.None) { - Icons.Default.VisibilityOff - } else { - Icons.Default.Visibility - }, - contentDescription = null, - colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onBackground), - ) - }, - supportingText = { - if (uiState.passwordError != null) { - Text( - text = uiState.passwordError?.localized().orEmpty(), - color = MaterialTheme.colorScheme.error, + Icons.Default.Visibility + }, + contentDescription = null, + colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onBackground), ) - } - }, - ) + }, + supportingText = { + if (uiState.passwordError != null) { + Text( + text = uiState.passwordError?.localized().orEmpty(), + color = MaterialTheme.colorScheme.error, + ) + } + }, + ) - TextField( - modifier = Modifier.focusRequester(tokenFocusRequester), - label = { + TextField( + modifier = Modifier.focusRequester(tokenFocusRequester), + label = { + Row( + horizontalArrangement = Arrangement.spacedBy(Spacing.s), + verticalAlignment = Alignment.Bottom, + ) { + Text(text = stringResource(MR.strings.login_field_token)) + Text( + text = stringResource(MR.strings.login_field_label_optional), + style = MaterialTheme.typography.labelSmall, + ) + } + }, + singleLine = true, + value = uiState.totp2faToken, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + onValueChange = { value -> + model.reduce(LoginBottomSheetMviModel.Intent.SetTotp2faToken(value)) + }, + visualTransformation = PasswordVisualTransformation(), + ) + Spacer(modifier = Modifier.height(Spacing.m)) + Button( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = { + model.reduce(LoginBottomSheetMviModel.Intent.Confirm) + }, + ) { Row( horizontalArrangement = Arrangement.spacedBy(Spacing.s), - verticalAlignment = Alignment.Bottom, + verticalAlignment = Alignment.CenterVertically, ) { - Text(text = stringResource(MR.strings.login_field_token)) - Text( - text = stringResource(MR.strings.login_field_label_optional), - style = MaterialTheme.typography.labelSmall, - ) + if (uiState.loading) { + CircularProgressIndicator( + modifier = Modifier.size(20.dp), + color = MaterialTheme.colorScheme.onPrimary, + ) + } + Text(stringResource(MR.strings.button_confirm)) } - }, - singleLine = true, - value = uiState.totp2faToken, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - onValueChange = { value -> - model.reduce(LoginBottomSheetMviModel.Intent.SetTotp2faToken(value)) - }, - visualTransformation = PasswordVisualTransformation(), - ) - Spacer(modifier = Modifier.height(Spacing.m)) - Button( - modifier = Modifier.align(Alignment.CenterHorizontally), - onClick = { - model.reduce(LoginBottomSheetMviModel.Intent.Confirm) - }, - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(Spacing.s), - verticalAlignment = Alignment.CenterVertically, - ) { - if (uiState.loading) { - CircularProgressIndicator( - modifier = Modifier.size(20.dp), - color = MaterialTheme.colorScheme.onPrimary, - ) - } - Text(stringResource(MR.strings.button_confirm)) } + Spacer(modifier = Modifier.height(Spacing.m)) } - Spacer(modifier = Modifier.height(Spacing.m)) + + SnackbarHost( + modifier = Modifier.padding(bottom = Spacing.xxxl), + hostState = snackbarHostState + ) } } } diff --git a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt index fb1a933df..230641761 100644 --- a/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt +++ b/feature-search/src/commonMain/kotlin/com/github/diegoberaldin/raccoonforlemmy/feature/search/multicommunity/detail/MultiCommunityScreen.kt @@ -73,6 +73,7 @@ import com.github.diegoberaldin.raccoonforlemmy.core.commonui.di.getNavigationCo import com.github.diegoberaldin.raccoonforlemmy.core.commonui.image.ZoomableImageScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.modals.SortBottomSheet import com.github.diegoberaldin.raccoonforlemmy.core.commonui.postdetail.PostDetailScreen +import com.github.diegoberaldin.raccoonforlemmy.core.commonui.report.CreateReportScreen import com.github.diegoberaldin.raccoonforlemmy.core.commonui.userdetail.UserDetailScreen import com.github.diegoberaldin.raccoonforlemmy.core.notifications.NotificationCenterContractKeys import com.github.diegoberaldin.raccoonforlemmy.core.notifications.di.getNotificationCenter @@ -317,6 +318,7 @@ class MultiCommunityScreen( options = buildList { add(stringResource(MR.strings.post_action_share)) add(stringResource(MR.strings.post_action_hide)) + add(stringResource(MR.strings.post_action_report)) }, blurNsfw = uiState.blurNsfw, onOpenCommunity = { community -> @@ -374,6 +376,14 @@ class MultiCommunityScreen( }, onOptionSelected = { optionIdx -> when (optionIdx) { + 2 -> { + bottomSheetNavigator.show( + CreateReportScreen( + postId = post.id + ) + ) + } + 1 -> model.reduce(MultiCommunityMviModel.Intent.Hide(idx)) else -> model.reduce( MultiCommunityMviModel.Intent.SharePost(idx) diff --git a/resources/src/commonMain/resources/MR/base/strings.xml b/resources/src/commonMain/resources/MR/base/strings.xml index 10d1af309..33f872207 100644 --- a/resources/src/commonMain/resources/MR/base/strings.xml +++ b/resources/src/commonMain/resources/MR/base/strings.xml @@ -20,7 +20,6 @@ Missing field Invalid field Image loading error - 🎉 Login successful! 🎉 No items to display Operation completed succcessfully @@ -63,11 +62,16 @@ Load more comments… Delete - Share + Share… Edit Hide + Report… also posted to: + Report post + Report comment + Report text (optional) + All Posts Comments diff --git a/resources/src/commonMain/resources/MR/es/strings.xml b/resources/src/commonMain/resources/MR/es/strings.xml index 67d91904e..1658762ff 100644 --- a/resources/src/commonMain/resources/MR/es/strings.xml +++ b/resources/src/commonMain/resources/MR/es/strings.xml @@ -16,7 +16,6 @@ Campo obligatorio Campo no válido No ha sido posible descargar la imagen - 🎉 ¡Acceso completado con éxito! 🎉 Ningún elemento para mostrar Operación completada con éxito @@ -59,11 +58,16 @@ Descarga más comentarios… Eliminar - Compartir + Compartir… Modificar Esconder + Crear informe… también publicado en: + Crear informe sobre publicación + Crear informe sobre comentario + Texto del informe (opcional) + Todos Publicaciones Comentarios diff --git a/resources/src/commonMain/resources/MR/it/strings.xml b/resources/src/commonMain/resources/MR/it/strings.xml index 306c716a0..18acc05ef 100644 --- a/resources/src/commonMain/resources/MR/it/strings.xml +++ b/resources/src/commonMain/resources/MR/it/strings.xml @@ -16,7 +16,6 @@ Campo obbligatorio Campo non valido Errore caricamento immagine - 🎉 Login effettuato con successo! 🎉 Nessun elemento da visualizzare Operazione completata con successo @@ -59,11 +58,16 @@ Carica altri commenti… Elimina - Condividi + Condividi… Modifica Nascondi + Segnala… postato anche in: + Segnala post + Segnala commento + Testo segnalazione (opzionale) + Tutti Post Commenti