dismiss reviews and displaying github error msgs

This commit is contained in:
k0shk0sh 2019-09-08 12:30:21 +02:00
parent d14aa3412b
commit 16348c3d3e
23 changed files with 402 additions and 136 deletions

View File

@ -43,6 +43,7 @@ abstract class BaseBottomSheetDialogFragment : BottomSheetDialogFragment(), HasS
abstract fun onFragmentCreatedWithUser(view: View, savedInstanceState: Bundle?)
abstract fun viewModel(): BaseViewModel?
protected open fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {}
protected open fun isFullScreen(): Boolean = false
override fun onAttach(context: Context) {
AndroidSupportInjection.inject(this)
@ -54,9 +55,6 @@ abstract class BaseBottomSheetDialogFragment : BottomSheetDialogFragment(), HasS
}
}
override fun onCreate(savedInstanceState: Bundle?) { // expose so its easier to find
super.onCreate(savedInstanceState)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
@ -120,11 +118,14 @@ abstract class BaseBottomSheetDialogFragment : BottomSheetDialogFragment(), HasS
}
private fun onGlobalLayoutChanged(view: View) {
val parent = dialog?.findViewById<ViewGroup>(R.id.design_bottom_sheet);
val parent = dialog?.findViewById<ViewGroup>(R.id.design_bottom_sheet)
if (parent != null) {
val toggleArrow = view.findViewById<ImageView?>(R.id.toggleArrow)
parent.setBackgroundColor(Color.TRANSPARENT)
bottomSheetBehavior = BottomSheetBehavior.from(parent)
if (isFullScreen()) {
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_EXPANDED
}
bottomSheetBehavior?.setBottomSheetCallback({ newState ->
if (newState == BottomSheetBehavior.STATE_HIDDEN) dialog?.cancel()
})

View File

@ -3,14 +3,15 @@ package com.fastaccess.github.base
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fastaccess.data.model.FastHubErrors
import com.fastaccess.domain.response.GithubErrorResponse
import com.fastaccess.github.R
import com.google.gson.Gson
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import org.json.JSONObject
import retrofit2.HttpException
import timber.log.Timber
import java.io.IOException
import java.util.concurrent.TimeoutException
import javax.inject.Inject
@ -34,7 +35,12 @@ abstract class BaseViewModel : ViewModel() {
hideProgress()
if (throwable is HttpException) {
val response = throwable.response()
val message: String? = JSONObject(response?.errorBody()?.string() ?: "").getString("message")
val errorBody: GithubErrorResponse? = gson.fromJson(response?.errorBody()?.string(), GithubErrorResponse::class.java)
if (errorBody?.message?.equals("Validation Failed", true) == true) {
errorBody.message = errorBody.errors?.firstOrNull()
}
val message = errorBody?.message ?: response?.message()
Timber.e("$errorBody")
val code = response?.code()
if (code == 401) { // OTP
val twoFactor = response.headers()["X-GitHub-OTP"]
@ -42,14 +48,21 @@ abstract class BaseViewModel : ViewModel() {
error.postValue(
FastHubErrors(
FastHubErrors.ErrorType.TWO_FACTOR, message = message
?: response.message()
)
)
} else {
error.postValue(FastHubErrors(FastHubErrors.ErrorType.OTHER, resId = R.string.failed_login, message = message))
}
} else {
error.postValue(FastHubErrors(FastHubErrors.ErrorType.OTHER, resId = R.string.network_error, message = message))
error.postValue(
FastHubErrors(
FastHubErrors.ErrorType.OTHER, resId = if (message.isNullOrEmpty()) {
R.string.network_error
} else {
null
}, message = message
)
)
}
return
}

View File

@ -6,6 +6,7 @@ import com.fastaccess.github.ui.modules.issuesprs.edit.labels.create.CreateLabel
import com.fastaccess.github.ui.modules.issuesprs.edit.milestone.CreateMilestoneDialogFragment
import com.fastaccess.github.ui.modules.issuesprs.filter.FilterIssuesPrsBottomSheet
import com.fastaccess.github.ui.modules.multipurpose.MultiPurposeBottomSheetDialog
import com.fastaccess.github.ui.modules.quickmsg.QuickMessageBottomSheetDialog
import com.fastaccess.github.ui.modules.search.filter.FilterSearchBottomSheet
import com.fastaccess.github.ui.modules.trending.filter.FilterTrendingBottomSheet
import com.fastaccess.github.ui.widget.dialog.IconDialogFragment
@ -26,4 +27,5 @@ abstract class DialogFragmentBindingModule {
@PerFragment @ContributesAndroidInjector abstract fun provideFilterSearchBottomSheet(): FilterSearchBottomSheet
@PerFragment @ContributesAndroidInjector abstract fun provideFilterTrendingBottomSheet(): FilterTrendingBottomSheet
@PerFragment @ContributesAndroidInjector abstract fun provideCreateLinkDialogFragment(): CreateLinkDialogFragment
@PerFragment @ContributesAndroidInjector abstract fun provideQuickMessageBottomSheetDialog(): QuickMessageBottomSheetDialog
}

View File

@ -55,10 +55,16 @@ class IssueTimelineAdapter(
getItemByPosition(position)?.comment?.let { commentClickListener.invoke(position, it) }
}
}
REVIEW_THREAD -> ReviewViewHolder(parent, markwon, theme, notifyCallback, { position ->
getItemByPosition(position)?.let { deleteCommentListener.invoke(position, it) }
}, { position ->
getItemByPosition(position)?.let { editCommentListener.invoke(position, it) }
REVIEW_THREAD -> ReviewViewHolder(parent, markwon, theme, notifyCallback, { position, isReviewBody ->
getItemByPosition(position)?.let {
it.review?.isReviewBody = isReviewBody
deleteCommentListener.invoke(position, it)
}
}, { position, isReviewBody ->
getItemByPosition(position)?.let {
it.review?.isReviewBody = isReviewBody
editCommentListener.invoke(position, it)
}
}).apply {
itemView.setOnClickListener {
val position = adapterPosition

View File

@ -44,6 +44,15 @@ class MainScreenAdapter(
}
}
override fun getItemCount(): Int {
val count = super.getItemCount()
if (count > 24) {
return 24
}
return count
}
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int

View File

@ -16,6 +16,7 @@ import com.fastaccess.github.ui.adapter.base.BaseViewHolder
import com.fastaccess.github.utils.extensions.popMenu
import com.fastaccess.markdown.spans.DiffLineSpan
import com.fastaccess.markdown.widget.SpannableBuilder
import github.type.PullRequestReviewState
import io.noties.markwon.Markwon
import io.noties.markwon.utils.NoCopySpannableFactory
import kotlinx.android.synthetic.main.comment_small_row_item.view.*
@ -31,8 +32,8 @@ class ReviewViewHolder(
private val markwon: Markwon,
private val theme: Int,
private val callback: (position: Int) -> Unit,
private val deleteCommentListener: (position: Int) -> Unit,
private val editCommentListener: (position: Int) -> Unit
private val deleteCommentListener: (position: Int, isReviewBody: Boolean) -> Unit,
private val editCommentListener: (position: Int, isReviewBody: Boolean) -> Unit
) : BaseViewHolder<ReviewModel?>(
LayoutInflater.from(parent.context)
.inflate(R.layout.review_with_comment_row_item, parent, false)
@ -46,14 +47,13 @@ class ReviewViewHolder(
}
itemView.apply {
itemView.commentLayout.isVisible = _review.comment != null
val showReview = !_review.body.isNullOrBlank()
if (showReview) {
initReview(_review)
}
reviewLayout.isVisible = showReview
_review.comment?.let { model ->
fileName.text = model.path
val showReview = !_review.body.isNullOrBlank()
reviewLayout.isVisible = showReview
if (showReview) {
initReview(_review)
}
if (!model.diffHunk.isNullOrEmpty()) {
diffHunk.text = DiffLineSpan.getSpannable(
model.diffHunk,
@ -68,11 +68,18 @@ class ReviewViewHolder(
userIcon.loadAvatar(model.author?.avatarUrl, model.author?.url ?: "")
author.text = model.author?.login ?: ""
association.text = if (CommentAuthorAssociation.NONE == model.authorAssociation) {
model.updatedAt?.timeAgo()
if (!showReview) {
SpannableBuilder.builder()
.bold(_review.state?.replace("_", "")?.toLowerCase())
.append(", ")
.append(model.createdAt?.timeAgo())
} else {
model.createdAt?.timeAgo()
}
} else {
SpannableBuilder.builder()
.bold(model.authorAssociation?.value?.toLowerCase()?.replace("_", "") ?: "")
.space()
.append(", ")
.append(model.updatedAt?.timeAgo())
}
@ -99,11 +106,11 @@ class ReviewViewHolder(
if (itemId == R.id.delete) {
context.showYesNoDialog(R.string.delete) {
it.isTrue {
deleteCommentListener.invoke(adapterPosition)
deleteCommentListener.invoke(adapterPosition, false)
}
}
} else if (itemId == R.id.edit) {
editCommentListener.invoke(adapterPosition)
editCommentListener.invoke(adapterPosition, false)
}
}
}
@ -112,6 +119,8 @@ class ReviewViewHolder(
callback.invoke(adapterPosition)
}
}
divider.isVisible = _review.comment != null
reviewCommentLayout.isVisible = _review.comment != null
}
}
@ -121,11 +130,16 @@ class ReviewViewHolder(
reviewUserIcon.loadAvatar(model.author?.avatarUrl, model.author?.url ?: "")
reviewAuthor.text = model.author?.login ?: ""
reviewAssociation.text = if (CommentAuthorAssociation.NONE.value == model.authorAssociation) {
model.createdAt?.timeAgo()
SpannableBuilder.builder()
.bold(model.state?.replace("_", "")?.toLowerCase())
.append(", ")
.append(model.createdAt?.timeAgo())
} else {
SpannableBuilder.builder()
.bold(model.authorAssociation?.toLowerCase()?.replace("_", "") ?: "")
.space()
.bold(model.state?.replace("_", "")?.toLowerCase())
.append(", ")
.append(model.authorAssociation?.toLowerCase()?.replace("_", "") ?: "")
.append(", ")
.append(model.createdAt?.timeAgo())
}
@ -147,16 +161,16 @@ class ReviewViewHolder(
if (canAlter) {
reviewMenu.popMenu(R.menu.comment_menu, { menu ->
menu.findItem(R.id.edit)?.isVisible = model.viewerCanUpdate == true
menu.findItem(R.id.delete)?.isVisible = model.viewerCanDelete == true
menu.findItem(R.id.delete)?.let {
it.isVisible = model.viewerCanDelete == true &&
model.state != PullRequestReviewState.COMMENTED.rawValue()
it.title = context.getString(R.string.dismiss_review)
}
}) { itemId ->
if (itemId == R.id.delete) {
context.showYesNoDialog(R.string.delete) {
it.isTrue {
deleteCommentListener.invoke(adapterPosition)
}
}
deleteCommentListener.invoke(adapterPosition, true)
} else if (itemId == R.id.edit) {
editCommentListener.invoke(adapterPosition)
editCommentListener.invoke(adapterPosition, true)
}
}
}

View File

@ -13,6 +13,7 @@ import com.fastaccess.github.R
import com.fastaccess.github.base.BaseFragment
import com.fastaccess.github.base.BaseViewModel
import com.fastaccess.github.extensions.getDrawableCompat
import com.fastaccess.github.extensions.hideKeyboard
import com.fastaccess.github.extensions.isTrue
import com.fastaccess.github.extensions.show
import com.fastaccess.github.platform.mentions.MentionsPresenter
@ -57,6 +58,7 @@ class EditorFragment : BaseFragment(), IconDialogFragment.IconDialogClickListene
editText.showKeyboard()
editText.setSelection(editText.asString().length)
setupToolbar(R.string.markdown, R.menu.submit_menu) { item ->
activity?.hideKeyboard()
val intent = Intent().apply {
val bundle = arguments ?: bundleOf()
bundle.putString(EXTRA, editText.asString())

View File

@ -84,9 +84,16 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
) {
if (isPr()) {
setupToolbar("", R.menu.issue_menu)// todo
toolbar.title = SpannableBuilder.builder()
.append(getString(R.string.pull_request))
.bold("#$number")
} else {
setupToolbar("", R.menu.issue_menu)
toolbar.title = SpannableBuilder.builder()
.append(getString(R.string.issue))
.bold("#$number")
}
toolbar.subtitle = "$login/$repo"
swipeRefresh.appBarLayout = appBar
appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, p1 ->
val scrollTop = toolbar.menu?.findItem(R.id.scrollTop)
@ -116,6 +123,11 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
setupEditText()
}
override fun onResume() {
activity?.hideKeyboard()
super.onResume()
}
override fun onDestroyView() {
mentionsPresenter.onDispose()
super.onDestroyView()
@ -137,13 +149,15 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
val model = data?.getParcelableExtra<EditIssuePrBundleModel>(EXTRA) ?: return
editIssuerPr(model.title, model.description)
}
EDIT_COMMENT_REQUEST_CODE, EDIT_REVIEW_COMMENT_REQUEST_CODE, EDIT_COMMIT_COMMENT_REQUEST_CODE -> {
EDIT_COMMENT_REQUEST_CODE, EDIT_REVIEW_COMMENT_REQUEST_CODE,
EDIT_REVIEW_BODY_REQUEST_CODE, EDIT_COMMIT_COMMENT_REQUEST_CODE -> {
val comment = data?.getStringExtra(EXTRA)
val commentId = data?.getIntExtra(EXTRA_TWO, 0)
onEditComment(
comment, commentId, when (requestCode) {
EDIT_COMMENT_REQUEST_CODE -> TimelineType.ISSUE
EDIT_REVIEW_COMMENT_REQUEST_CODE -> TimelineType.REVIEW
EDIT_REVIEW_BODY_REQUEST_CODE -> TimelineType.REVIEW_BODY
EDIT_COMMIT_COMMENT_REQUEST_CODE -> TimelineType.COMMIT
else -> TimelineType.ISSUE
}
@ -194,31 +208,6 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
}
}
protected fun setupEditText() {
Autocomplete.on<String>(commentText)
.with(CharPolicy('@'))
.with(mentionsPresenter)
.with(requireContext().getDrawableCompat(R.drawable.popup_window_background))
.with(object : AutocompleteCallback<String?> {
override fun onPopupItemClicked(
editable: Editable?,
item: String?
): Boolean = MarkdownProvider.replaceMention(editable, item)
override fun onPopupVisibilityChanged(shown: Boolean) {}
})
.build()
sendComment.setOnClickListener {
val comment = commentText.text?.toString()
if (!comment.isNullOrEmpty()) {
sendComment(comment)
}
}
toggleFullScreen.setOnClickListener {
routeForResult(EDITOR_DEEPLINK, COMMENT_REQUEST_CODE, bundleOf(EXTRA to commentText.text?.toString()))
}
}
protected fun menuClick(
url: String?,
labels: List<LabelModel>?,
@ -263,21 +252,6 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
}
}
private fun startEditingIssue(
title: String?,
body: String?,
isOwner: Boolean
) {
EditIssuePrActivity.startForResult(
this, EditIssuePrBundleModel(
login, repo, number, title, body, false, isOwner = isOwner
), EDIT_ISSUE_REQUEST_CODE
)
}
protected open fun lockUnlockIssuePr() = Unit
protected open fun closeOpenIssuePr() = Unit
protected fun initAssignees(assigneesList: List<ShortUserModel>?) {
assigneesLayout.isVisible = !assigneesList.isNullOrEmpty()
val builder = SpannableBuilder.builder()
@ -345,19 +319,31 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
val databaseId = when {
timeline.comment != null -> timeline.comment?.databaseId
timeline.commitThread != null -> timeline.commitThread?.comment?.databaseId
timeline.review != null -> timeline.review?.comment?.databaseId
timeline.review != null -> if (timeline.review?.isReviewBody == true) {
timeline.review?.databaseId
} else {
timeline.review?.comment?.databaseId
}
else -> 0
}
val body = when {
timeline.comment != null -> timeline.comment?.body
timeline.commitThread != null -> timeline.commitThread?.comment?.body
timeline.review != null -> timeline.review?.comment?.body
timeline.review != null -> if (timeline.review?.isReviewBody == true) {
timeline.review?.body
} else {
timeline.review?.comment?.body
}
else -> null
}
val requestCode = when {
timeline.comment != null -> EDIT_COMMENT_REQUEST_CODE
timeline.commitThread != null -> EDIT_COMMIT_COMMENT_REQUEST_CODE
timeline.review != null -> EDIT_REVIEW_COMMENT_REQUEST_CODE
timeline.review != null -> if (timeline.review?.isReviewBody == true) {
EDIT_REVIEW_BODY_REQUEST_CODE
} else {
EDIT_REVIEW_COMMENT_REQUEST_CODE
}
else -> 0
}
if (databaseId != 0) {
@ -374,13 +360,21 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
val databaseId = when {
timeline.comment != null -> timeline.comment?.databaseId
timeline.commitThread != null -> timeline.commitThread?.comment?.databaseId
timeline.review != null -> timeline.review?.comment?.databaseId
timeline.review != null -> if (timeline.review?.isReviewBody == true) {
timeline.review?.databaseId
} else {
timeline.review?.comment?.databaseId
}
else -> 0
}
val type = when {
timeline.comment != null -> TimelineType.ISSUE
timeline.commitThread != null -> TimelineType.COMMIT
timeline.review != null -> TimelineType.REVIEW
timeline.review != null -> if (timeline.review?.isReviewBody == true) {
TimelineType.REVIEW_BODY
} else {
TimelineType.REVIEW
}
else -> TimelineType.ISSUE
}
if (databaseId != null && databaseId != 0) {
@ -392,12 +386,52 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(),
protected open fun deleteComment(login: String, repo: String, commentId: Long, type: TimelineType) = Unit
protected open fun onEditComment(comment: String?, commentId: Int?, type: TimelineType = TimelineType.ISSUE) = Unit
protected open fun lockUnlockIssuePr() = Unit
protected open fun closeOpenIssuePr() = Unit
private fun setupEditText() {
Autocomplete.on<String>(commentText)
.with(CharPolicy('@'))
.with(mentionsPresenter)
.with(requireContext().getDrawableCompat(R.drawable.popup_window_background))
.with(object : AutocompleteCallback<String?> {
override fun onPopupItemClicked(
editable: Editable?,
item: String?
): Boolean = MarkdownProvider.replaceMention(editable, item)
override fun onPopupVisibilityChanged(shown: Boolean) {}
})
.build()
sendComment.setOnClickListener {
val comment = commentText.text?.toString()
if (!comment.isNullOrEmpty()) {
sendComment(comment)
}
}
toggleFullScreen.setOnClickListener {
routeForResult(EDITOR_DEEPLINK, COMMENT_REQUEST_CODE, bundleOf(EXTRA to commentText.text?.toString()))
}
}
private fun startEditingIssue(
title: String?,
body: String?,
isOwner: Boolean
) {
EditIssuePrActivity.startForResult(
this, EditIssuePrBundleModel(
login, repo, number, title, body, false, isOwner = isOwner
), EDIT_ISSUE_REQUEST_CODE
)
}
companion object {
const val COMMENT_REQUEST_CODE = 1001
const val EDIT_ISSUE_REQUEST_CODE = 1002
const val EDIT_COMMENT_REQUEST_CODE = 1003
const val EDIT_REVIEW_COMMENT_REQUEST_CODE = 1004
const val EDIT_COMMIT_COMMENT_REQUEST_CODE = 1005
const val EDIT_REVIEW_BODY_REQUEST_CODE = 1005
const val EDIT_COMMIT_COMMENT_REQUEST_CODE = 1006
}
}

View File

@ -19,6 +19,8 @@ import com.fastaccess.github.extensions.timeAgo
import com.fastaccess.github.ui.adapter.IssueTimelineAdapter
import com.fastaccess.github.ui.modules.issuesprs.BaseIssuePrTimelineFragment
import com.fastaccess.github.ui.modules.pr.fragment.viewmodel.PullRequestTimelineViewModel
import com.fastaccess.github.ui.modules.quickmsg.QuickMessageBottomSheetDialog
import com.fastaccess.github.ui.modules.quickmsg.QuickMessageBottomSheetDialog.QuickMessageCallback
import com.fastaccess.github.usecase.issuesprs.TimelineType
import com.fastaccess.github.utils.EXTRA
import com.fastaccess.github.utils.EXTRA_THREE
@ -35,13 +37,12 @@ import kotlinx.android.synthetic.main.comment_box_layout.*
import kotlinx.android.synthetic.main.pr_header_row_item.*
import kotlinx.android.synthetic.main.pr_view_layout.*
import kotlinx.android.synthetic.main.recyclerview_fastscroll_empty_state_layout.*
import kotlinx.android.synthetic.main.title_toolbar_layout.*
import javax.inject.Inject
/**
* Created by Kosh on 28.01.19.
*/
class PullRequestFragment : BaseIssuePrTimelineFragment() {
class PullRequestFragment : BaseIssuePrTimelineFragment(), QuickMessageCallback {
@Inject lateinit var viewModelFactory: ViewModelProvider.Factory
@Inject lateinit var markwon: Markwon
@ -82,6 +83,11 @@ class PullRequestFragment : BaseIssuePrTimelineFragment() {
override fun lockUnlockIssuePr() = viewModel.lockUnlockIssue(login, repo, number)
override fun closeOpenIssuePr() = viewModel.closeOpenIssue(login, repo, number)
override fun onMessageEntered(msg: String, bundle: Bundle?) {
val commentId = bundle?.getLong(EXTRA_TWO) ?: return
viewModel.deleteComment(login, repo, commentId, TimelineType.REVIEW_BODY, number, msg)
}
private fun observeChanges() {
viewModel.getPullRequest(login, repo, number).observeNotNull(this) {
initIssue(it.first, it.second)
@ -114,9 +120,6 @@ class PullRequestFragment : BaseIssuePrTimelineFragment() {
issueHeaderWrapper.isVisible = true
val theme = preference.theme
title.text = model.title
toolbar.title = SpannableBuilder.builder()
.append(getString(R.string.pull_request))
.bold("#${model.number}")
opener.text = if (model.merged == true) {
SpannableBuilder.builder()
@ -199,11 +202,20 @@ class PullRequestFragment : BaseIssuePrTimelineFragment() {
}
override fun deleteComment(login: String, repo: String, commentId: Long, type: TimelineType) {
viewModel.deleteComment(login, repo, commentId, type)
if (type == TimelineType.REVIEW_BODY) {
QuickMessageBottomSheetDialog.show(
childFragmentManager, bundleOf(
EXTRA to getString(R.string.dismiss_review),
EXTRA_TWO to commentId
)
)
} else {
viewModel.deleteComment(login, repo, commentId, type, number)
}
}
override fun onEditComment(comment: String?, commentId: Int?, type: TimelineType) {
viewModel.editComment(login, repo, comment, commentId?.toLong(), type)
viewModel.editComment(login, repo, comment, commentId?.toLong(), type, number)
}
companion object {

View File

@ -176,17 +176,27 @@ class PullRequestTimelineViewModel @Inject constructor(
login: String,
repo: String,
commentId: Long,
type: TimelineType = TimelineType.ISSUE
type: TimelineType = TimelineType.ISSUE,
number: Int,
msg: String? = null
) {
deleteCommentUseCase.commentId = commentId
deleteCommentUseCase.login = login
deleteCommentUseCase.repo = repo
deleteCommentUseCase.type = type
deleteCommentUseCase.number = number
deleteCommentUseCase.msg = msg
justSubscribe(deleteCommentUseCase.buildObservable()
.map {
val index = getIndexOfComment(type, commentId)
if (index != -1) {
list.removeAt(index)
if (type == TimelineType.REVIEW) {
val item = list.getOrNull(index) ?: return@map list
item.review?.comment = null
list[index] = item
} else {
list.removeAt(index)
}
}
return@map list
}
@ -200,7 +210,8 @@ class PullRequestTimelineViewModel @Inject constructor(
repo: String,
comment: String?,
commentId: Long?,
type: TimelineType = TimelineType.ISSUE
type: TimelineType = TimelineType.ISSUE,
number: Int
) {
if (!comment.isNullOrBlank() && commentId != null) {
editCommentUseCase.comment = comment
@ -208,6 +219,7 @@ class PullRequestTimelineViewModel @Inject constructor(
editCommentUseCase.repo = repo
editCommentUseCase.commentId = commentId
editCommentUseCase.type = type
editCommentUseCase.number = number
justSubscribe(editCommentUseCase.buildObservable()
.map {
val index = getIndexOfComment(type, commentId)
@ -215,6 +227,7 @@ class PullRequestTimelineViewModel @Inject constructor(
when (type) {
TimelineType.ISSUE -> item.comment?.body = comment
TimelineType.REVIEW -> item.review?.comment?.body = comment
TimelineType.REVIEW_BODY -> item.review?.body = comment
TimelineType.COMMIT -> item.commitThread?.comment?.body = comment
else -> {
}
@ -230,8 +243,9 @@ class PullRequestTimelineViewModel @Inject constructor(
}
private fun getIndexOfComment(type: TimelineType, commentId: Long?): Int = list.indexOfFirst {
when (type) {
return@indexOfFirst when (type) {
TimelineType.ISSUE -> it.comment?.databaseId?.toLong() == commentId
TimelineType.REVIEW_BODY -> it.review?.databaseId?.toLong() == commentId
TimelineType.REVIEW -> it.review?.comment?.databaseId?.toLong() == commentId
TimelineType.COMMIT -> it.commitThread?.comment?.databaseId?.toLong() == commentId
else -> false

View File

@ -0,0 +1,60 @@
package com.fastaccess.github.ui.modules.quickmsg
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.FragmentManager
import com.fastaccess.github.R
import com.fastaccess.github.base.BaseBottomSheetDialogFragment
import com.fastaccess.github.base.BaseViewModel
import com.fastaccess.github.utils.EXTRA
import com.fastaccess.github.utils.extensions.asString
import kotlinx.android.synthetic.main.appbar_center_title_round_background_layout.*
import kotlinx.android.synthetic.main.quick_input_layout.*
class QuickMessageBottomSheetDialog : BaseBottomSheetDialogFragment() {
private var callback: QuickMessageCallback? = null
override fun viewModel(): BaseViewModel? = null
override fun layoutRes(): Int = R.layout.quick_input_layout
override fun isFullScreen(): Boolean = true
override fun onAttach(context: Context) {
super.onAttach(context)
callback = when {
parentFragment is QuickMessageCallback -> parentFragment as QuickMessageCallback
context is QuickMessageCallback -> context
else -> throw IllegalAccessException("your $parentFragment or $context must implement QuickMessageCallback")
}
}
override fun onDetach() {
callback = null
super.onDetach()
}
override fun onFragmentCreatedWithUser(view: View, savedInstanceState: Bundle?) {
toolbarTitle.text = arguments?.getString(EXTRA) ?: getString(R.string.message)
submit.setOnClickListener {
val text = editText.asString()
if (text.isNotEmpty()) {
callback?.onMessageEntered(text, arguments)
dismiss()
}
}
}
companion object {
fun show(fragmentManager: FragmentManager, bundle: Bundle) {
QuickMessageBottomSheetDialog().apply {
arguments = bundle
show(fragmentManager, "QuickMessageBottomSheetDialog")
}
}
}
interface QuickMessageCallback {
fun onMessageEntered(msg: String, bundle: Bundle?)
}
}

View File

@ -4,8 +4,10 @@ import com.fastaccess.data.repository.SchedulerProvider
import com.fastaccess.domain.repository.services.CommitService
import com.fastaccess.domain.repository.services.IssuePrService
import com.fastaccess.domain.repository.services.ReviewService
import com.fastaccess.domain.response.body.DismissReviewRequestModel
import com.fastaccess.domain.usecase.base.BaseObservableUseCase
import io.reactivex.Observable
import retrofit2.HttpException
import javax.inject.Inject
class DeleteCommentUseCase @Inject constructor(
@ -14,6 +16,8 @@ class DeleteCommentUseCase @Inject constructor(
private val commitService: CommitService,
private val schedulerProvider: SchedulerProvider
) : BaseObservableUseCase() {
var msg: String? = null
var number: Int = 0
var repo: String = ""
var login: String = ""
var commentId: Long = 0
@ -22,6 +26,14 @@ class DeleteCommentUseCase @Inject constructor(
TimelineType.ISSUE -> issueService.deleteIssueComment(login, repo, commentId)
TimelineType.REVIEW -> reviewService.deleteComment(login, repo, commentId)
TimelineType.COMMIT -> commitService.deleteComment(login, repo, commentId)
TimelineType.REVIEW_BODY -> reviewService.dismissReview(login, repo, number, commentId, DismissReviewRequestModel(msg ?: ""))
.flatMap {
return@flatMap if (it.code() != 200) {
Observable.error(HttpException(it))
} else {
Observable.just(it)
}
}
TimelineType.GIST -> TODO()
}
.subscribeOn(schedulerProvider.ioThread())

View File

@ -19,11 +19,13 @@ class EditCommentUseCase @Inject constructor(
var login: String = ""
var commentId: Long = 0
var comment: String = ""
var number: Int = 0
var type: TimelineType = TimelineType.ISSUE
override fun buildObservable(): Observable<*> = when (type) {
TimelineType.ISSUE -> issueService.editIssueComment(login, repo, commentId, CommentRequestModel(comment))
TimelineType.REVIEW -> reviewService.editComment(login, repo, commentId, CommentRequestModel(comment))
TimelineType.REVIEW_BODY -> reviewService.editReview(login, repo, number, commentId, CommentRequestModel(comment))
TimelineType.COMMIT -> commitService.editCommitComment(login, repo, commentId, CommentRequestModel(comment))
TimelineType.GIST -> TODO()
}

View File

@ -1,5 +1,5 @@
package com.fastaccess.github.usecase.issuesprs
enum class TimelineType {
ISSUE, REVIEW, GIST, COMMIT
ISSUE, REVIEW, REVIEW_BODY, GIST, COMMIT
}

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:orientation="vertical">
<include layout="@layout/appbar_center_title_round_background_layout" />
<LinearLayout
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
android:orientation="vertical"
android:paddingBottom="@dimen/spacing_large">
<com.google.android.material.textfield.TextInputLayout
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_xs_large">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/message" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/submit"
style="@style/Widget.MaterialComponents.Button.OutlinedButton.Icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_margin="@dimen/spacing_xs_large"
android:text="@string/submit"
app:icon="@drawable/ic_done" />
</LinearLayout>
</LinearLayout>

View File

@ -1,13 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/transparent"
android:orientation="vertical">
<include layout="@layout/appbar_center_title_round_background_layout"/>
<include layout="@layout/appbar_center_title_round_background_layout" />
<androidx.core.widget.NestedScrollView
android:id="@+id/nestedScrollView"
@ -23,20 +22,20 @@
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_xs_large"
android:text="@string/by"/>
android:text="@string/by"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/filter"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:layout_marginTop="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_normal"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
app:checkedChip="@+id/created"
app:singleSelection="true">
@ -45,21 +44,21 @@
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/created"/>
android:text="@string/created" />
<com.google.android.material.chip.Chip
android:id="@+id/assigned"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/assigned"/>
android:text="@string/assigned" />
<com.google.android.material.chip.Chip
android:id="@+id/mentioned"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/mentioned"/>
android:text="@string/mentioned" />
<com.google.android.material.chip.Chip
android:id="@+id/reviewRequest"
@ -67,25 +66,25 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/review_requests"
android:visibility="gone"/>
android:visibility="gone" />
</com.google.android.material.chip.ChipGroup>
<androidx.appcompat.widget.AppCompatTextView
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_xs_large"
android:text="@string/type"/>
android:text="@string/type"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/type"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:layout_marginTop="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_normal"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
app:checkedChip="@+id/open"
app:singleSelection="true">
@ -94,33 +93,33 @@
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/open"/>
android:text="@string/open" />
<com.google.android.material.chip.Chip
android:id="@+id/closed"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/closed"/>
android:text="@string/closed" />
</com.google.android.material.chip.ChipGroup>
<androidx.appcompat.widget.AppCompatTextView
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_xs_large"
android:text="@string/visibility"/>
android:text="@string/visibility"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/visibility"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:layout_marginTop="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_normal"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
app:checkedChip="@+id/bothVisibility"
app:singleSelection="true">
@ -129,41 +128,41 @@
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/both_label"/>
android:text="@string/both_label" />
<com.google.android.material.chip.Chip
android:id="@+id/publicRepos"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/public_label"/>
android:text="@string/public_label" />
<com.google.android.material.chip.Chip
android:id="@+id/privateRepos"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/private_label"/>
android:text="@string/private_label" />
</com.google.android.material.chip.ChipGroup>
<androidx.appcompat.widget.AppCompatTextView
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_xs_large"
android:text="@string/sort"/>
android:text="@string/sort"
android:textAppearance="@style/TextAppearance.AppCompat.Title" />
<com.google.android.material.chip.ChipGroup
android:id="@+id/sort"
style="@style/ChipStyleCheckable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:layout_marginTop="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_normal"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
app:checkedChip="@+id/newest"
app:singleSelection="true">
@ -172,42 +171,42 @@
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/newest"/>
android:text="@string/newest" />
<com.google.android.material.chip.Chip
android:id="@+id/oldest"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/oldest"/>
android:text="@string/oldest" />
<com.google.android.material.chip.Chip
android:id="@+id/mostCommented"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/most_commented"/>
android:text="@string/most_commented" />
<com.google.android.material.chip.Chip
android:id="@+id/leastCommented"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/least_commented"/>
android:text="@string/least_commented" />
<com.google.android.material.chip.Chip
android:id="@+id/recentlyUpdated"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/recently_updated"/>
android:text="@string/recently_updated" />
<com.google.android.material.chip.Chip
android:id="@+id/leastRecentlyUpdated"
style="@style/ChipStyleCheckable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/least_recent_updated"/>
android:text="@string/least_recent_updated" />
</com.google.android.material.chip.ChipGroup>
@ -220,7 +219,7 @@
android:layout_gravity="end"
android:layout_margin="@dimen/spacing_xs_large"
android:text="@string/submit"
app:icon="@drawable/ic_done"/>
app:icon="@drawable/ic_done" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

View File

@ -100,6 +100,7 @@
android:layout_marginTop="@dimen/spacing_normal" />
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="@dimen/spacing_normal"
@ -109,6 +110,7 @@
</LinearLayout>
<LinearLayout
android:id="@+id/reviewCommentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_normal"

View File

@ -217,7 +217,8 @@ data class ReviewModel(
@SerializedName("viewerCanUpdate") var viewerCanUpdate: Boolean? = null,
@SerializedName("viewerDidAuthor") var viewerDidAuthor: Boolean? = null,
@SerializedName("viewerCanMinimize") var viewerCanMinimize: Boolean? = null,
@SerializedName("reactionGroups") var reactionGroups: List<ReactionGroupModel>? = null
@SerializedName("reactionGroups") var reactionGroups: List<ReactionGroupModel>? = null,
@SerializedName("isReviewBody") var isReviewBody: Boolean? = null
)
data class CommitThreadModel(

View File

@ -37,7 +37,7 @@ interface IssuePrService {
@Path("repo") repo: String,
@Path("number") number: Int,
@Body body: CommentRequestModel
): Observable<Response<Void>>
): Observable<Response<Unit>>
@PATCH("repos/{owner}/{repo}/issues/comments/{id}")
fun editIssueComment(
@ -45,14 +45,14 @@ interface IssuePrService {
@Path("repo") repo: String,
@Path("id") id: Long,
@Body body: CommentRequestModel
): Observable<Response<Void>>
): Observable<Response<Unit>>
@DELETE("repos/{owner}/{repo}/issues/comments/{id}")
fun deleteIssueComment(
@Path("owner") owner: String,
@Path("repo") repo: String,
@Path("id") id: Long
): Observable<Response<Void>>
): Observable<Response<Unit>>
@POST("repos/{owner}/{repo}/issues/{number}/labels")
fun addLabelsToIssue(

View File

@ -1,7 +1,7 @@
package com.fastaccess.domain.repository.services
import com.fastaccess.domain.response.CommentResponse
import com.fastaccess.domain.response.body.CommentRequestModel
import com.fastaccess.domain.response.body.DismissReviewRequestModel
import io.reactivex.Observable
import retrofit2.Response
import retrofit2.http.*
@ -14,7 +14,16 @@ interface ReviewService {
@Path("repo") repo: String,
@Path("number") number: Long,
@Body body: CommentRequestModel
): Observable<CommentResponse>
): Observable<Unit>
@PUT("/repos/{owner}/{repo}/pulls/{number}/reviews/{reviewId}")
fun editReview(
@Path("owner") owner: String,
@Path("repo") repo: String,
@Path("number") number: Int,
@Path("reviewId") reviewId: Long,
@Body body: CommentRequestModel
): Observable<Response<Unit>>
@PATCH("/repos/{owner}/{repo}/pulls/comments/{id}")
fun editComment(
@ -22,12 +31,21 @@ interface ReviewService {
@Path("repo") repo: String,
@Path("id") id: Long,
@Body body: CommentRequestModel
): Observable<CommentResponse>
): Observable<Unit>
@DELETE("repos/{owner}/{repo}/pulls/comments/{id}")
fun deleteComment(
@Path("owner") owner: String,
@Path("repo") repo: String,
@Path("id") id: Long
): Observable<Response<Boolean>>
): Observable<Response<Unit>>
@PUT("/repos/{owner}/{repo}/pulls/{number}/reviews/{reviewId}/dismissals")
fun dismissReview(
@Path("owner") owner: String,
@Path("repo") repo: String,
@Path("number") number: Int,
@Path("reviewId") reviewId: Long,
@Body body: DismissReviewRequestModel
): Observable<Response<Unit>>
}

View File

@ -0,0 +1,9 @@
package com.fastaccess.domain.response
import com.google.gson.annotations.SerializedName
data class GithubErrorResponse(
@SerializedName("message") var message: String? = null,
@SerializedName("documentation_url") var documentationUrl: String? = null,
@SerializedName("errors") var errors: List<String?>? = null
)

View File

@ -0,0 +1,10 @@
package com.fastaccess.domain.response.body
import com.google.gson.annotations.SerializedName
/**
* Created by Kosh on 2019-07-18.
*/
data class DismissReviewRequestModel(
@SerializedName("message") var message: String
)

View File

@ -620,4 +620,6 @@
<string name="approved_with_count">Approved: %d</string>
<string name="commented_with_count">Commented: %d</string>
<string name="commits_with_count">Commits: %d</string>
<string name="message">Message</string>
<string name="dismiss_review">Dismiss Review</string>
</resources>