From 73cb05f218cdeb204f9c570296e204bbe9786cee Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Fri, 6 Sep 2019 22:58:33 +0200 Subject: [PATCH] display dashboard in PR and reviews & their comment --- .../github/ui/adapter/IssueTimelineAdapter.kt | 11 + .../viewholder/CommitThreadViewHolder.kt | 111 ++++++ .../viewholder/IssueContentViewHolder.kt | 60 ++- .../ui/adapter/viewholder/ReviewViewHolder.kt | 112 ++++++ .../pr/fragment/PullRequestFragment.kt | 17 +- .../usecase/issuesprs/BaseTimelineUseCase.kt | 3 +- .../issuesprs/CreateIssueCommentUseCase.kt | 8 +- .../GetPullRequestTimelineUseCase.kt | 75 ++-- .../issuesprs/GetPullRequestUseCase.kt | 46 ++- .../layout/pr_fragment_layout.xml | 15 + .../main_layouts/layout/pr_view_layout.xml | 53 +++ .../layout/comment_small_row_item.xml | 94 +++++ .../layout/commit_with_comment_row_item.xml | 52 +++ .../layout/issue_header_row_item.xml | 2 +- .../row_layouts/layout/pr_header_row_item.xml | 366 ++++++++++++++++++ .../layout/review_with_comment_row_item.xml | 50 +++ .../src/main/graphql/github/fragments.graphql | 21 +- .../main/graphql/github/issues_prs.graphql | 54 +-- .../data/model/PullRequestDashboard.kt | 11 + .../fastaccess/data/model/TimelineModel.kt | 20 +- .../data/persistence/db/FastHubDatabase.kt | 2 +- .../persistence/models/PullRequestModel.kt | 8 +- .../fastaccess/markdown/spans/DiffLineSpan.kt | 93 +++++ resources/src/main/res/drawable/ic_repo.xml | 34 +- .../src/main/res/drawable/ic_repo_small.xml | 35 +- resources/src/main/res/values/dimens.xml | 1 + resources/src/main/res/values/strings.xml | 10 + 27 files changed, 1205 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/CommitThreadViewHolder.kt create mode 100644 app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt create mode 100644 app/src/main/res/layouts/main_layouts/layout/pr_fragment_layout.xml create mode 100644 app/src/main/res/layouts/main_layouts/layout/pr_view_layout.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/comment_small_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/commit_with_comment_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/pr_header_row_item.xml create mode 100644 app/src/main/res/layouts/row_layouts/layout/review_with_comment_row_item.xml create mode 100644 data/src/main/java/com/fastaccess/data/model/PullRequestDashboard.kt create mode 100644 markdown/src/main/java/com/fastaccess/markdown/spans/DiffLineSpan.kt diff --git a/app/src/main/java/com/fastaccess/github/ui/adapter/IssueTimelineAdapter.kt b/app/src/main/java/com/fastaccess/github/ui/adapter/IssueTimelineAdapter.kt index b1f31c84..3221b682 100644 --- a/app/src/main/java/com/fastaccess/github/ui/adapter/IssueTimelineAdapter.kt +++ b/app/src/main/java/com/fastaccess/github/ui/adapter/IssueTimelineAdapter.kt @@ -11,6 +11,7 @@ import com.fastaccess.github.ui.adapter.base.BaseViewHolder import com.fastaccess.github.ui.adapter.viewholder.CommentViewHolder import com.fastaccess.github.ui.adapter.viewholder.IssueContentViewHolder import com.fastaccess.github.ui.adapter.viewholder.LoadingViewHolder +import com.fastaccess.github.ui.adapter.viewholder.ReviewViewHolder import io.noties.markwon.Markwon /** @@ -34,6 +35,7 @@ class IssueTimelineAdapter( return getItem(position)?.let { when { it.comment != null -> COMMENT + it.review?.comment != null -> REVIEW_THREAD else -> CONTENT } } ?: super.getItemViewType(position) @@ -51,6 +53,13 @@ class IssueTimelineAdapter( getItemByPosition(position)?.comment?.let { commentClickListener.invoke(position, it) } } } + REVIEW_THREAD -> ReviewViewHolder(parent, markwon, theme, notifyCallback, deleteCommentListener, editCommentListener).apply { + itemView.setOnClickListener { + val position = adapterPosition + if (position == RecyclerView.NO_POSITION) return@setOnClickListener + getItemByPosition(position)?.comment?.let { commentClickListener.invoke(position, it) } + } + } CONTENT -> IssueContentViewHolder(parent) else -> LoadingViewHolder(parent).apply { itemView.isVisible = false } } @@ -62,6 +71,7 @@ class IssueTimelineAdapter( ) { when (holder) { is CommentViewHolder -> holder.bind(getItem(position).comment) + is ReviewViewHolder -> holder.bind(getItem(position).review) is IssueContentViewHolder -> holder.bind(getItem(position)) } } @@ -79,6 +89,7 @@ class IssueTimelineAdapter( private const val HEADER = 1 private const val COMMENT = 2 private const val CONTENT = 3 + private const val REVIEW_THREAD = 4 private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { override fun areItemsTheSame( diff --git a/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/CommitThreadViewHolder.kt b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/CommitThreadViewHolder.kt new file mode 100644 index 00000000..730f8ef5 --- /dev/null +++ b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/CommitThreadViewHolder.kt @@ -0,0 +1,111 @@ +package com.fastaccess.github.ui.adapter.viewholder + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.fastaccess.data.model.CommentAuthorAssociation +import com.fastaccess.data.model.CommentModel +import com.fastaccess.data.model.ReviewModel +import com.fastaccess.github.R +import com.fastaccess.github.extensions.getColorAttr +import com.fastaccess.github.extensions.isTrue +import com.fastaccess.github.extensions.showYesNoDialog +import com.fastaccess.github.extensions.timeAgo +import com.fastaccess.github.ui.adapter.base.BaseViewHolder +import com.fastaccess.github.utils.extensions.popMenu +import com.fastaccess.markdown.spans.DiffLineSpan +import io.noties.markwon.Markwon +import io.noties.markwon.utils.NoCopySpannableFactory +import kotlinx.android.synthetic.main.comment_small_row_item.view.* +import kotlinx.android.synthetic.main.commit_with_comment_row_item.view.* + + +/** + * Created by Kosh on 12.10.18. + */ + +class CommitThreadViewHolder( + parent: ViewGroup, + private val markwon: Markwon, + private val theme: Int, + private val callback: (position: Int) -> Unit, + private val deleteCommentListener: (position: Int, comment: CommentModel) -> Unit, + private val editCommentListener: (position: Int, comment: CommentModel) -> Unit +) : BaseViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.commit_with_comment_row_item, parent, false) +) { + + @SuppressLint("SetTextI18n", "DefaultLocale") + override fun bind(item: ReviewModel?) { + val review = item ?: run { + itemView.isVisible = false + return + } + itemView.apply { + itemView.commentLayout.isVisible = review.comment != null + review.comment?.let { model -> + fileName.text = model.path + if (model.diffHunk.isNullOrEmpty()) { + diffHunk.text = DiffLineSpan.getSpannable( + model.diffHunk, + context.getColorAttr(R.attr.patch_addition), context.getColorAttr(R.attr.patch_deletion), + context.getColorAttr(R.attr.patch_ref), + truncate = true + ) + diffHunk.isVisible = true + } else { + diffHunk.isVisible = false + } + userIcon.loadAvatar(model.author?.avatarUrl, model.author?.url ?: "") + author.text = model.author?.login ?: "" + association.text = if (CommentAuthorAssociation.NONE == model.authorAssociation) { + model.updatedAt?.timeAgo() + } else { + com.fastaccess.markdown.widget.SpannableBuilder.builder() + .bold(model.authorAssociation?.value?.toLowerCase()?.replace("_", "") ?: "") + .space() + .append(model.updatedAt?.timeAgo()) + } + + description.post { + description.setSpannableFactory(NoCopySpannableFactory.getInstance()) + val bodyMd = model.body + markwon.setMarkdown(description, if (!bodyMd.isNullOrEmpty()) bodyMd else resources.getString(R.string.no_description_provided)) + } + + description.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_UP && !description.hasSelection()) { + itemView.callOnClick() + } + return@setOnTouchListener false + } + + val canAlter = model.viewerCanUpdate == true || model.viewerCanDelete == true + menu.isVisible = canAlter + if (canAlter) { + menu.popMenu(R.menu.comment_menu, { menu -> + menu.findItem(R.id.edit)?.isVisible = model.viewerCanUpdate == true + menu.findItem(R.id.delete)?.isVisible = model.viewerCanDelete == true + }) { itemId -> + if (itemId == R.id.delete) { + context.showYesNoDialog(R.string.delete) { + it.isTrue { + deleteCommentListener.invoke(adapterPosition, model) + } + } + } else if (itemId == R.id.edit) { + editCommentListener.invoke(adapterPosition, model) + } + } + } + + adaptiveEmoticon.init(requireNotNull(model.id), model.reactionGroups) { + callback.invoke(adapterPosition) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/IssueContentViewHolder.kt b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/IssueContentViewHolder.kt index 93c8db8d..2256c440 100644 --- a/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/IssueContentViewHolder.kt +++ b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/IssueContentViewHolder.kt @@ -5,6 +5,7 @@ import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import com.fastaccess.data.model.* import com.fastaccess.github.R import com.fastaccess.github.extensions.route @@ -12,6 +13,7 @@ import com.fastaccess.github.extensions.timeAgo import com.fastaccess.github.ui.adapter.base.BaseViewHolder import com.fastaccess.markdown.spans.LabelSpan import com.fastaccess.markdown.widget.SpannableBuilder +import github.type.PullRequestReviewState import github.type.PullRequestState import github.type.StatusState import kotlinx.android.synthetic.main.issue_content_row_item.view.* @@ -48,18 +50,57 @@ class IssueContentViewHolder(parent: ViewGroup) : BaseViewHolder( item.reviewDismissed?.let(this::presenetReviewDismissed) item.reviewRequestRemoved?.let(this::presenetReviewRequestRemoved) item.pullRequestCommit?.let(this::presentPrCommit) + item.review?.let(this::presentReview) + } + + @SuppressLint("DefaultLocale") + private fun presentReview(model: ReviewModel) { + itemView.apply { + val icon = when (model.state) { + PullRequestReviewState.APPROVED.rawValue() -> R.drawable.ic_done + PullRequestReviewState.CHANGES_REQUESTED.rawValue() -> R.drawable.ic_clear + PullRequestReviewState.COMMENTED.rawValue() -> R.drawable.ic_comment + else -> 0 + } + if (icon != 0) { + stateIcon.setImageResource(icon) + stateIcon.isVisible = true + } else { + stateIcon.isVisible = false + } + userIcon.loadAvatar(model.author?.avatarUrl, model.author?.url) + text.text = SpannableBuilder.builder() + .bold(model.author?.login) + .space() + .apply { + append(model.state?.replace("_", " ")?.toLowerCase() ?: "") + val body = model.body + if (!body.isNullOrEmpty()) { + newline() + append(body) + space() + } else { + space() + } + } + .append(model.createdAt?.timeAgo()) + } } private fun presentPrCommit(model: PullRequestCommitModel) { itemView.apply { - stateIcon.setImageResource( - when (model.commit?.state) { - StatusState.ERROR.rawValue(), StatusState.FAILURE.rawValue() -> R.drawable.ic_state_error - StatusState.SUCCESS.rawValue() -> R.drawable.ic_state_success - StatusState.PENDING.rawValue() -> R.drawable.ic_state_pending - else -> 0 - } - ) + val icon = when (model.commit?.state) { + StatusState.ERROR.rawValue(), StatusState.FAILURE.rawValue() -> R.drawable.ic_state_error + StatusState.SUCCESS.rawValue() -> R.drawable.ic_state_success + StatusState.PENDING.rawValue() -> R.drawable.ic_state_pending + else -> 0 + } + if (icon == 0) { + stateIcon.isVisible = false + } else { + stateIcon.setImageResource(icon) + stateIcon.isVisible = true + } userIcon.loadAvatar(model.commit?.author?.avatarUrl, model.commit?.author?.url) text.text = SpannableBuilder.builder() .bold(model.commit?.author?.login) @@ -70,6 +111,7 @@ class IssueContentViewHolder(parent: ViewGroup) : BaseViewHolder( model.commit?.commitUrl?.let { view.context.route(it) } }) .space() + .append("${model.commit?.message} " ?: "") .append(model.commit?.authoredDate?.timeAgo()) } } @@ -95,7 +137,7 @@ class IssueContentViewHolder(parent: ViewGroup) : BaseViewHolder( text.text = SpannableBuilder.builder() .bold(model.actor?.login) .space() - .append("dismissed their review (${model.previousReviewState?.toLowerCase()})") + .append("dismissed ${model.previousReviewState?.replace("_", " ")?.toLowerCase()} review") .newline() .append(model.dismissalMessage ?: "") .space() diff --git a/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt new file mode 100644 index 00000000..b0443958 --- /dev/null +++ b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt @@ -0,0 +1,112 @@ +package com.fastaccess.github.ui.adapter.viewholder + +import android.annotation.SuppressLint +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.ViewGroup +import androidx.core.view.isVisible +import com.fastaccess.data.model.CommentAuthorAssociation +import com.fastaccess.data.model.CommentModel +import com.fastaccess.data.model.ReviewModel +import com.fastaccess.github.R +import com.fastaccess.github.extensions.getColorAttr +import com.fastaccess.github.extensions.isTrue +import com.fastaccess.github.extensions.showYesNoDialog +import com.fastaccess.github.extensions.timeAgo +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 io.noties.markwon.Markwon +import io.noties.markwon.utils.NoCopySpannableFactory +import kotlinx.android.synthetic.main.comment_small_row_item.view.* +import kotlinx.android.synthetic.main.review_with_comment_row_item.view.* + + +/** + * Created by Kosh on 12.10.18. + */ + +class ReviewViewHolder( + parent: ViewGroup, + private val markwon: Markwon, + private val theme: Int, + private val callback: (position: Int) -> Unit, + private val deleteCommentListener: (position: Int, comment: CommentModel) -> Unit, + private val editCommentListener: (position: Int, comment: CommentModel) -> Unit +) : BaseViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.review_with_comment_row_item, parent, false) +) { + + @SuppressLint("SetTextI18n", "DefaultLocale") + override fun bind(item: ReviewModel?) { + val review = item ?: run { + itemView.isVisible = false + return + } + itemView.apply { + itemView.commentLayout.isVisible = review.comment != null + review.comment?.let { model -> + fileName.text = model.path + if (!model.diffHunk.isNullOrEmpty()) { + diffHunk.text = DiffLineSpan.getSpannable( + model.diffHunk, + context.getColorAttr(R.attr.patch_addition), context.getColorAttr(R.attr.patch_deletion), + context.getColorAttr(R.attr.patch_ref), + truncate = true + ) + diffHunk.isVisible = true + } else { + diffHunk.isVisible = false + } + userIcon.loadAvatar(model.author?.avatarUrl, model.author?.url ?: "") + author.text = model.author?.login ?: "" + association.text = if (CommentAuthorAssociation.NONE == model.authorAssociation) { + model.updatedAt?.timeAgo() + } else { + SpannableBuilder.builder() + .bold(model.authorAssociation?.value?.toLowerCase()?.replace("_", "") ?: "") + .space() + .append(model.updatedAt?.timeAgo()) + } + + description.post { + description.setSpannableFactory(NoCopySpannableFactory.getInstance()) + val bodyMd = model.body + markwon.setMarkdown(description, if (!bodyMd.isNullOrEmpty()) bodyMd else resources.getString(R.string.no_description_provided)) + } + + description.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_UP && !description.hasSelection()) { + itemView.callOnClick() + } + return@setOnTouchListener false + } + + val canAlter = model.viewerCanUpdate == true || model.viewerCanDelete == true + menu.isVisible = canAlter + if (canAlter) { + menu.popMenu(R.menu.comment_menu, { menu -> + menu.findItem(R.id.edit)?.isVisible = model.viewerCanUpdate == true + menu.findItem(R.id.delete)?.isVisible = model.viewerCanDelete == true + }) { itemId -> + if (itemId == R.id.delete) { + context.showYesNoDialog(R.string.delete) { + it.isTrue { + deleteCommentListener.invoke(adapterPosition, model) + } + } + } else if (itemId == R.id.edit) { + editCommentListener.invoke(adapterPosition, model) + } + } + } + + adaptiveEmoticon.init(requireNotNull(model.id), model.reactionGroups) { + callback.invoke(adapterPosition) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt b/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt index 87912db0..d00fcd32 100644 --- a/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt +++ b/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt @@ -34,8 +34,8 @@ import github.type.PullRequestState import io.noties.markwon.Markwon import io.noties.markwon.utils.NoCopySpannableFactory import kotlinx.android.synthetic.main.comment_box_layout.* -import kotlinx.android.synthetic.main.issue_header_row_item.* -import kotlinx.android.synthetic.main.issue_pr_view_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 @@ -55,7 +55,7 @@ class PullRequestFragment : BaseIssuePrTimelineFragment() { IssueTimelineAdapter(markwon, preference.theme, onCommentClicked(), onDeleteCommentClicked(), onEditCommentClicked()) } - override fun layoutRes(): Int = R.layout.issue_pr_fragment_layout + override fun layoutRes(): Int = R.layout.pr_fragment_layout override fun viewModel(): BaseViewModel? = viewModel override fun isPr(): Boolean = true override fun lockIssuePr(lockReason: LockReason?) = viewModel.lockUnlockIssue(login, repo, number, lockReason, true) @@ -186,9 +186,20 @@ class PullRequestFragment : BaseIssuePrTimelineFragment() { initAssignees(model.assignees) initMilestone(model.milestone) initToolbarMenu(isAuthor, model.viewerCanUpdate == true, model.viewerDidAuthor, model.locked, state = model.state) + initDashboard(model) recyclerView.removeEmptyView() } + private fun initDashboard(model: PullRequestModel) { + commits.text = getString(R.string.commits_with_count, model.dashboard?.commits ?: 0) + files.text = getString(R.string.files_with_count, model.dashboard?.changedFiles ?: 0) + addition.text = getString(R.string.addition_with_count, model.dashboard?.additions ?: 0) + deletion.text = getString(R.string.deletion_with_count, model.dashboard?.deletions ?: 0) + approved.text = getString(R.string.approved_with_count, model.dashboard?.approvedReviews ?: 0) + commented.text = getString(R.string.commented_with_count, model.dashboard?.commentedReviews ?: 0) + changes.text = getString(R.string.changes_with_count, model.dashboard?.changeRequestedReviews ?: 0) + } + override fun onEditCommentClicked(): (position: Int, comment: CommentModel) -> Unit = { position, comment -> routeForResult( EDITOR_DEEPLINK, EDIT_COMMENT_REQUEST_CODE, bundleOf( diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt index ced19feb..1a55313b 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt @@ -205,8 +205,7 @@ abstract class BaseTimelineUseCase : BaseObservableUseCase() { comment = CommentModel( node.id, node.databaseId, ShortUserModel(node.author?.login, node.author?.login, node.author?.url?.toString(), avatarUrl = node.author?.avatarUrl.toString()), - node.bodyHTML.toString(), node.body, CommentAuthorAssociation.fromName(node.authorAssociation.rawValue()), - node.viewerCannotUpdateReasons.map { reason -> CommentCannotUpdateReason.fromName(reason.rawValue()) }.toList(), + node.body, CommentAuthorAssociation.fromName(node.authorAssociation.rawValue()), node.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, node.createdAt, node.updatedAt, node.isViewerCanReact, node.isViewerCanDelete, node.isViewerCanUpdate, node.isViewerDidAuthor, node.isViewerCanMinimize ) diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt index a8e37736..c2abfec7 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt @@ -2,7 +2,10 @@ package com.fastaccess.github.usecase.issuesprs import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.rx2.Rx2Apollo -import com.fastaccess.data.model.* +import com.fastaccess.data.model.CommentAuthorAssociation +import com.fastaccess.data.model.CommentModel +import com.fastaccess.data.model.ShortUserModel +import com.fastaccess.data.model.TimelineModel import com.fastaccess.data.repository.SchedulerProvider import com.fastaccess.domain.repository.services.IssuePrService import com.fastaccess.domain.response.body.CommentRequestModel @@ -39,8 +42,7 @@ class CreateIssueCommentUseCase @Inject constructor( node.id, node.databaseId, ShortUserModel(node.author?.login, node.author?.login, avatarUrl = node.author?.avatarUrl.toString()), - node.bodyHTML.toString(), node.body, CommentAuthorAssociation.fromName(node.authorAssociation.rawValue()), - node.viewerCannotUpdateReasons.map { reason -> CommentCannotUpdateReason.fromName(reason.rawValue()) }.toList(), + node.body, CommentAuthorAssociation.fromName(node.authorAssociation.rawValue()), node.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, node.createdAt, node.updatedAt, node.isViewerCanReact, node.isViewerCanDelete, node.isViewerCanUpdate, node.isViewerDidAuthor, node.isViewerCanMinimize ) diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestTimelineUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestTimelineUseCase.kt index bd82088e..8b80351e 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestTimelineUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestTimelineUseCase.kt @@ -6,6 +6,7 @@ import com.apollographql.apollo.rx2.Rx2Apollo import com.fastaccess.data.model.* import com.fastaccess.data.persistence.models.MyIssuesPullsModel import com.fastaccess.data.repository.SchedulerProvider +import com.fastaccess.extension.toReactionGroup import com.fastaccess.extension.toUser import com.fastaccess.github.extensions.addIfNotNull import github.GetPullRequestTimelineQuery @@ -45,11 +46,6 @@ class GetPullRequestTimelineUseCase @Inject constructor( timeline.pageInfo.startCursor, timeline.pageInfo.endCursor, timeline.pageInfo.isHasNextPage, timeline.pageInfo.isHasPreviousPage ) - /** - * - PULL_REQUEST_COMMIT, PULL_REQUEST_COMMIT_COMMENT_THREAD, - PULL_REQUEST_REVIEW, PULL_REQUEST_REVIEW_THREAD,] - */ timeline.nodes?.forEach { node -> when (node) { is AsIssueComment -> node.fragments.comment?.let { list.add(getComment(it)) } @@ -88,7 +84,6 @@ class GetPullRequestTimelineUseCase @Inject constructor( is AsReviewDismissedEvent -> list.add(getDismissedReview(node)) is AsReviewRequestRemovedEvent -> list.add(getReviewRemoved(node)) is AsPullRequestReview -> list.add(getPullRequestReview(node)) - is AsPullRequestCommit -> list.add(getPullRequestCommit(node)) } } return@map Pair(pageInfo, list) @@ -108,49 +103,41 @@ class GetPullRequestTimelineUseCase @Inject constructor( node.state.rawValue(), node.createdAt, node.comments.nodes?.map { - ReviewComment( - ShortUserModel( - it.author?.login, - it.author?.login, - it.author?.url?.toString(), - avatarUrl = it.author?.avatarUrl.toString() - ), - ShortUserModel( - it.replyTo?.author?.login, - it.replyTo?.author?.login, - it.replyTo?.author?.url?.toString(), - avatarUrl = it.replyTo?.author?.avatarUrl.toString() - ), - it.body, - it.path, - it.originalPosition, - it.isOutdated + CommentModel( + it.id, + it.databaseId, + ShortUserModel(it.author?.login, it.author?.login, it.author?.url?.toString(), avatarUrl = it.author?.avatarUrl?.toString()), + it.body, CommentAuthorAssociation.fromName(it.authorAssociation.rawValue()), + it.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, + it.updatedAt, it.updatedAt, it.isViewerCanReact, it.isViewerCanDelete, + it.isViewerCanUpdate, it.isViewerDidAuthor, false, + it.path, it.originalPosition, it.isOutdated, it.diffHunk ) }?.firstOrNull() ) ) - private fun getPullRequestCommit(node: AsPullRequestCommit) = TimelineModel( - pullRequestCommit = PullRequestCommitModel( - node.id, - node.url.toString(), - CommitModel( - node.prCommit.oid.toString(), - ShortUserModel( - node.prCommit.author?.name, - node.prCommit.author?.name, - node.prCommit.author?.user?.url.toString(), - avatarUrl = node.prCommit.author?.avatarUrl?.toString() ?: node.prCommit.author?.user?.avatarUrl?.toString() - ), - node.prCommit.message, - node.prCommit.abbreviatedOid, - node.prCommit.commitUrl.toString(), - node.prCommit.authoredDate, - node.prCommit.isCommittedViaWeb, - node.prCommit.history.nodes?.lastOrNull()?.status?.state?.rawValue() - ) - ) - ) +// private fun getPullRequestCommit(node: AsPullRequestCommit) = TimelineModel( +// pullRequestCommit = PullRequestCommitModel( +// node.id, +// node.url.toString(), +// CommitModel( +// node.prCommit.oid.toString(), +// ShortUserModel( +// node.prCommit.author?.name, +// node.prCommit.author?.name, +// node.prCommit.author?.user?.url.toString(), +// avatarUrl = node.prCommit.author?.avatarUrl?.toString() ?: node.prCommit.author?.user?.avatarUrl?.toString() +// ), +// node.prCommit.message, +// node.prCommit.abbreviatedOid, +// node.prCommit.commitUrl.toString(), +// node.prCommit.authoredDate, +// node.prCommit.isCommittedViaWeb, +// node.prCommit.history.nodes?.lastOrNull()?.status?.state?.rawValue() +// ) +// ) +// ) private fun getReviewRemoved(node: AsReviewRequestRemovedEvent) = TimelineModel( reviewRequestRemoved = ReviewRequestRemovedModel( diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt index d689ddc7..2e62c1be 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt @@ -4,6 +4,7 @@ import com.apollographql.apollo.ApolloClient import com.apollographql.apollo.rx2.Rx2Apollo import com.fastaccess.data.model.CountModel import com.fastaccess.data.model.EmbeddedRepoModel +import com.fastaccess.data.model.PullRequestDashboard import com.fastaccess.data.model.ShortUserModel import com.fastaccess.data.persistence.models.PullRequestModel import com.fastaccess.data.repository.PullRequestRepository @@ -44,24 +45,33 @@ class GetPullRequestUseCase @Inject constructor( .observeOn(schedulerProvider.uiThread()) .map { it.data()?.repositoryOwner?.repository?.pullRequest?.fragments?.fullPullRequest } .map { issue -> - repoProvider.upsert(PullRequestModel(issue.id, issue.databaseId, issue.number, issue.activeLockReason?.rawValue(), - issue.body, issue.bodyHTML.toString(), issue.closedAt, issue.createdAt, issue.updatedAt, issue.state.rawValue(), - issue.title, issue.viewerSubscription?.rawValue(), ShortUserModel( - issue.author?.login, issue.author?.login, - issue.author?.url?.toString(), avatarUrl = issue.author?.avatarUrl?.toString() - ), - EmbeddedRepoModel(issue.repository.nameWithOwner), ShortUserModel( - issue.mergedBy?.login, issue.mergedBy?.login, - issue.mergedBy?.url?.toString(), avatarUrl = issue.mergedBy?.avatarUrl?.toString() - ), - CountModel(issue.userContentEdits?.totalCount), - issue.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, - issue.viewerCannotUpdateReasons.map { it.rawValue() }, issue.isClosed, issue.isCreatedViaEmail, issue.isLocked, - issue.isViewerCanReact, issue.isViewerCanSubscribe, issue.isViewerCanUpdate, issue.isViewerDidAuthor, - issue.mergeable.rawValue(), issue.isMerged, issue.mergedAt, - issue.authorAssociation.rawValue(), issue.url.toString(), issue.labels?.nodes?.map { it.fragments.labels.toLabels() }, - issue.milestone?.toMilestone(), issue.assignees.nodes?.map { it.fragments }?.map { it.shortUserRowItem.toUser() }, - issue.headRefName, issue.baseRefName) + repoProvider.upsert( + PullRequestModel( + issue.id, issue.databaseId, issue.number, issue.activeLockReason?.rawValue(), + issue.body, issue.bodyHTML.toString(), issue.closedAt, issue.createdAt, issue.updatedAt, issue.state.rawValue(), + issue.title, issue.viewerSubscription?.rawValue(), ShortUserModel( + issue.author?.login, issue.author?.login, + issue.author?.url?.toString(), avatarUrl = issue.author?.avatarUrl?.toString() + ), + EmbeddedRepoModel(issue.repository.nameWithOwner), ShortUserModel( + issue.mergedBy?.login, issue.mergedBy?.login, + issue.mergedBy?.url?.toString(), avatarUrl = issue.mergedBy?.avatarUrl?.toString() + ), + CountModel(issue.userContentEdits?.totalCount), + issue.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, + issue.viewerCannotUpdateReasons.map { it.rawValue() }, issue.isClosed, issue.isCreatedViaEmail, issue.isLocked, + issue.isViewerCanReact, issue.isViewerCanSubscribe, issue.isViewerCanUpdate, issue.isViewerDidAuthor, + issue.mergeable.rawValue(), issue.isMerged, issue.mergedAt, + issue.authorAssociation.rawValue(), issue.url.toString(), issue.labels?.nodes?.map { it.fragments.labels.toLabels() }, + issue.milestone?.toMilestone(), issue.assignees.nodes?.map { it.fragments }?.map { it.shortUserRowItem.toUser() }, + issue.headRefName, issue.baseRefName, + PullRequestDashboard( + issue.changedFiles, issue.additions, issue.deletions, + issue.commits.totalCount, issue.commentedReviews?.totalCount ?: 0, + issue.approvedReviews?.totalCount ?: 0, + issue.changeRequestedReviews?.totalCount ?: 0 + ) + ) ) } } diff --git a/app/src/main/res/layouts/main_layouts/layout/pr_fragment_layout.xml b/app/src/main/res/layouts/main_layouts/layout/pr_fragment_layout.xml new file mode 100644 index 00000000..c12b1237 --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/pr_fragment_layout.xml @@ -0,0 +1,15 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/main_layouts/layout/pr_view_layout.xml b/app/src/main/res/layouts/main_layouts/layout/pr_view_layout.xml new file mode 100644 index 00000000..865cb25c --- /dev/null +++ b/app/src/main/res/layouts/main_layouts/layout/pr_view_layout.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/comment_small_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/comment_small_row_item.xml new file mode 100644 index 00000000..d9adfea4 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/comment_small_row_item.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/commit_with_comment_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/commit_with_comment_row_item.xml new file mode 100644 index 00000000..b62ef999 --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/commit_with_comment_row_item.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/issue_header_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/issue_header_row_item.xml index 1f820d08..aa13ce64 100644 --- a/app/src/main/res/layouts/row_layouts/layout/issue_header_row_item.xml +++ b/app/src/main/res/layouts/row_layouts/layout/issue_header_row_item.xml @@ -108,10 +108,10 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layouts/row_layouts/layout/review_with_comment_row_item.xml b/app/src/main/res/layouts/row_layouts/layout/review_with_comment_row_item.xml new file mode 100644 index 00000000..d229770d --- /dev/null +++ b/app/src/main/res/layouts/row_layouts/layout/review_with_comment_row_item.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/data/src/main/graphql/github/fragments.graphql b/data/src/main/graphql/github/fragments.graphql index 6478f121..1177b8ad 100644 --- a/data/src/main/graphql/github/fragments.graphql +++ b/data/src/main/graphql/github/fragments.graphql @@ -171,12 +171,29 @@ fragment FullPullRequest on PullRequest { mergedAt headRefName baseRefName + changedFiles + additions + deletions + url + createdViaEmail + authorAssociation + commits(first: 0) { + totalCount + } + commentedReviews: reviews(first: 0, states: COMMENTED) { + totalCount + } + approvedReviews: reviews(first: 0, states: APPROVED) { + totalCount + } + changeRequestedReviews: reviews(first: 0, states: CHANGES_REQUESTED) { + totalCount + } mergedBy { avatarUrl login url } - url userContentEdits(first: 0) { totalCount } @@ -188,11 +205,9 @@ fragment FullPullRequest on PullRequest { reactionGroups { ...Reactions } - createdViaEmail repository { nameWithOwner } - authorAssociation labels(first: 30) { nodes { ...Labels diff --git a/data/src/main/graphql/github/issues_prs.graphql b/data/src/main/graphql/github/issues_prs.graphql index 78f785be..c90d77ce 100644 --- a/data/src/main/graphql/github/issues_prs.graphql +++ b/data/src/main/graphql/github/issues_prs.graphql @@ -161,7 +161,7 @@ query getPullRequestTimeline($login: String!, $repo: String!, $number: Int!, $pa state timelineItems(first: 30, after: $page, itemTypes: [ISSUE_COMMENT, CLOSED_EVENT, REOPENED_EVENT, REFERENCED_EVENT, ASSIGNED_EVENT, UNASSIGNED_EVENT, LABELED_EVENT, UNLABELED_EVENT, MILESTONED_EVENT, DEMILESTONED_EVENT, RENAMED_TITLE_EVENT, - LOCKED_EVENT, UNLOCKED_EVENT, TRANSFERRED_EVENT, PULL_REQUEST_COMMIT, PULL_REQUEST_COMMIT_COMMENT_THREAD, + LOCKED_EVENT, UNLOCKED_EVENT, TRANSFERRED_EVENT, PULL_REQUEST_COMMIT_COMMENT_THREAD, PULL_REQUEST_REVIEW, PULL_REQUEST_REVIEW_THREAD, HEAD_REF_DELETED_EVENT, HEAD_REF_FORCE_PUSHED_EVENT, MERGED_EVENT, MERGED_EVENT, REVIEW_DISMISSED_EVENT, REVIEW_REQUESTED_EVENT, REVIEW_REQUEST_REMOVED_EVENT, READY_FOR_REVIEW_EVENT]) { @@ -225,41 +225,10 @@ query getPullRequestTimeline($login: String!, $repo: String!, $number: Int!, $pa ... on TransferredEvent { ... Transferred } - ... on PullRequestCommit { - id - url - prCommit: commit { - oid - abbreviatedOid - committedViaWeb - authoredDate - message - commitUrl - status { - state - } - history { - nodes { - status { - state - } - } - } - author { - avatarUrl - name - user { - login - avatarUrl - url - } - } - } - } ... on PullRequestCommitCommentThread { path position - tComment: comments(first: 5) { + tComment: comments(first: 1) { nodes { author { avatarUrl @@ -433,8 +402,10 @@ query getPullRequestTimeline($login: String!, $repo: String!, $number: Int!, $pa createdAt state resourcePath - comments(first: 5) { + comments(first: 1) { nodes { + id + databaseId author { login avatarUrl @@ -444,12 +415,15 @@ query getPullRequestTimeline($login: String!, $repo: String!, $number: Int!, $pa originalPosition path outdated - replyTo { - author { - login - avatarUrl - url - } + authorAssociation + updatedAt + viewerCanReact + viewerCanDelete + viewerCanUpdate + viewerDidAuthor + diffHunk + reactionGroups { + ...Reactions } } } diff --git a/data/src/main/java/com/fastaccess/data/model/PullRequestDashboard.kt b/data/src/main/java/com/fastaccess/data/model/PullRequestDashboard.kt new file mode 100644 index 00000000..89e3dccc --- /dev/null +++ b/data/src/main/java/com/fastaccess/data/model/PullRequestDashboard.kt @@ -0,0 +1,11 @@ +package com.fastaccess.data.model + +data class PullRequestDashboard( + var changedFiles: Int = 0, + var additions: Int = 0, + var deletions: Int = 0, + var commits: Int = 0, + var commentedReviews: Int = 0, + var approvedReviews: Int = 0, + var changeRequestedReviews: Int = 0 +) \ No newline at end of file diff --git a/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt b/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt index 63ea0a6e..e82a8f6f 100644 --- a/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt +++ b/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt @@ -51,10 +51,8 @@ data class CommentModel( @SerializedName("id") var id: String? = null, @SerializedName("databaseId") var databaseId: Int? = null, @SerializedName("author") var author: ShortUserModel? = null, - @SerializedName("bodyHTML") var bodyHTML: String? = null, @SerializedName("body") var body: String? = null, @SerializedName("authorAssociation") var authorAssociation: CommentAuthorAssociation? = null, - @SerializedName("viewerCannotUpdateReasons") var viewerCannotUpdateReasons: List? = null, @SerializedName("reactionGroups") var reactionGroups: List? = null, @SerializedName("createdAt") var createdAt: Date? = null, @SerializedName("updatedAt") var updatedAt: Date? = null, @@ -62,7 +60,11 @@ data class CommentModel( @SerializedName("viewerCanDelete") var viewerCanDelete: Boolean? = null, @SerializedName("viewerCanUpdate") var viewerCanUpdate: Boolean? = null, @SerializedName("viewerDidAuthor") var viewerDidAuthor: Boolean? = null, - @SerializedName("viewerCanMinimize") var viewerCanMinimize: Boolean? = null + @SerializedName("viewerCanMinimize") var viewerCanMinimize: Boolean? = null, + @SerializedName("path") var path: String? = null, + @SerializedName("originalPosition") var originalPosition: Int? = null, + @SerializedName("outdated") var outdated: Boolean? = null, + @SerializedName("diffHunk") var diffHunk: String? = null ) data class CrossReferencedEventModel( @@ -208,19 +210,9 @@ data class ReviewModel( @SerializedName("authorAssociation") var authorAssociation: String? = null, @SerializedName("state") var state: String? = null, @SerializedName("createdAt") var createdAt: Date? = null, - @SerializedName("comment") var comment: ReviewComment? = null + @SerializedName("comment") var comment: CommentModel? = null ) -data class ReviewComment( - @SerializedName("author") var author: ShortUserModel? = null, - @SerializedName("replyTo") var replyTo: ShortUserModel? = null, - @SerializedName("body") var body: String? = null, - @SerializedName("path") var path: String? = null, - @SerializedName("originalPosition") var originalPosition: Int? = null, - @SerializedName("outdated") var outdated: Boolean? = null -) - - enum class CommentAuthorAssociation(val value: String) { MEMBER("MEMBER"), OWNER("OWNER"), diff --git a/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt b/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt index 6105efc6..183058b9 100644 --- a/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt +++ b/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt @@ -10,7 +10,7 @@ import com.fastaccess.data.persistence.models.* * Created by Kosh on 11.05.18. */ -const val VERSION = 30 +const val VERSION = 31 const val DATABASE_NAME = "FastHub-Room-DB" @Database( diff --git a/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt b/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt index 37754a7f..241d28a9 100644 --- a/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt +++ b/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt @@ -3,10 +3,7 @@ package com.fastaccess.data.persistence.models import androidx.room.Embedded import androidx.room.Entity import androidx.room.PrimaryKey -import com.fastaccess.data.model.CountModel -import com.fastaccess.data.model.EmbeddedRepoModel -import com.fastaccess.data.model.ReactionGroupModel -import com.fastaccess.data.model.ShortUserModel +import com.fastaccess.data.model.* import com.fastaccess.data.model.parcelable.LabelModel import com.fastaccess.data.model.parcelable.MilestoneModel import com.google.gson.annotations.SerializedName @@ -52,7 +49,8 @@ data class PullRequestModel( @SerializedName("milestone") @Embedded(prefix = "milestone_") var milestone: MilestoneModel? = null, @SerializedName("assignees") var assignees: List? = null, @SerializedName("headRefName") var headRefName: String? = null, - @SerializedName("baseRefName") var baseRefName: String? = null + @SerializedName("baseRefName") var baseRefName: String? = null, + @SerializedName("reviewDashboard") @Embedded(prefix = "dashboard_") var dashboard: PullRequestDashboard? = null ) { companion object { const val TABLE_NAME = "pullrequest_table" diff --git a/markdown/src/main/java/com/fastaccess/markdown/spans/DiffLineSpan.kt b/markdown/src/main/java/com/fastaccess/markdown/spans/DiffLineSpan.kt new file mode 100644 index 00000000..416951ca --- /dev/null +++ b/markdown/src/main/java/com/fastaccess/markdown/spans/DiffLineSpan.kt @@ -0,0 +1,93 @@ +package com.fastaccess.markdown.spans + +import android.graphics.* +import android.graphics.drawable.Drawable +import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.TextPaint +import android.text.style.LineBackgroundSpan +import android.text.style.MetricAffectingSpan +import android.text.style.TypefaceSpan +import androidx.annotation.ColorInt +import com.fastaccess.markdown.widget.SpannableBuilder + +class DiffLineSpan private constructor(private val color: Int) : MetricAffectingSpan(), LineBackgroundSpan { + private val rect = Rect() + + override fun updateMeasureState(paint: TextPaint) { + apply(paint) + } + + override fun updateDrawState(paint: TextPaint) { + apply(paint) + } + + private fun apply(paint: TextPaint) { + paint.typeface = Typeface.MONOSPACE + } + + override fun drawBackground( + c: Canvas, p: Paint, left: Int, right: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence, start: Int, + end: Int, lnum: Int + ) { + val style = p.style + val color = p.color + p.style = Paint.Style.FILL + p.color = this.color + rect.set(left, top, right, bottom) + c.drawRect(rect, p) + p.color = color + p.style = style + } + + companion object { + fun getSpannable( + text: String?, + @ColorInt patchAdditionColor: Int, + @ColorInt patchDeletionColor: Int, + @ColorInt patchRefColor: Int, + newLine: Drawable? = null, + truncate: Boolean = false + ): SpannableStringBuilder { + val builder = SpannableStringBuilder() + if (!text.isNullOrEmpty()) { + val split = text.split("\\r?\\n|\\r".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (split.isNotEmpty()) { + val lines = split.size + var index = -1 + for (i in 0 until lines) { + if (truncate && lines - i > 2) continue + var token = split[i] + if (i < lines - 1) { + token += "\n" + } + val firstChar = token[0] + var color = Color.TRANSPARENT + when { + token.startsWith("@@") -> color = patchRefColor + firstChar == '+' -> color = patchAdditionColor + firstChar == '-' -> color = patchDeletionColor + } + index = token.indexOf("\\ No newline at end of file") + if (index != -1) { + token = token.replace("\\ No newline at end of file", "") + } + val spannableDiff = SpannableString(token) + if (color != Color.TRANSPARENT) { + val span = DiffLineSpan(color) + spannableDiff.setSpan(span, 0, token.length, SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE) + } + builder.append(spannableDiff) + } + if (index != -1) { + builder.insert(builder.length - 1, SpannableBuilder.builder().append(newLine)) + } + } + } + builder.setSpan(TypefaceSpan("monospace"), 0, builder.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + return builder + } + } + +} \ No newline at end of file diff --git a/resources/src/main/res/drawable/ic_repo.xml b/resources/src/main/res/drawable/ic_repo.xml index d50cd550..df50d02f 100644 --- a/resources/src/main/res/drawable/ic_repo.xml +++ b/resources/src/main/res/drawable/ic_repo.xml @@ -1,12 +1,30 @@ - + android:height="26dp" + android:viewportWidth="12" + android:viewportHeight="14"> - \ No newline at end of file + android:fillType="evenOdd" + android:pathData="M6.455,1v4.364L4.818,4.273l-1.636,1.09V1" + android:strokeWidth="1" + android:strokeColor="#000" + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + + + diff --git a/resources/src/main/res/drawable/ic_repo_small.xml b/resources/src/main/res/drawable/ic_repo_small.xml index 2461fe97..7ca45823 100644 --- a/resources/src/main/res/drawable/ic_repo_small.xml +++ b/resources/src/main/res/drawable/ic_repo_small.xml @@ -1,11 +1,30 @@ - - + android:height="18dp" + android:viewportWidth="12" + android:viewportHeight="14"> - \ No newline at end of file + android:fillType="evenOdd" + android:pathData="M6.455,1v4.364L4.818,4.273l-1.636,1.09V1" + android:strokeWidth="1" + android:strokeColor="#000" + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + + + diff --git a/resources/src/main/res/values/dimens.xml b/resources/src/main/res/values/dimens.xml index 1ee32c5a..508c4418 100644 --- a/resources/src/main/res/values/dimens.xml +++ b/resources/src/main/res/values/dimens.xml @@ -15,6 +15,7 @@ 16dp 0dp 48dp + 36dp 64dp 24dp 1px diff --git a/resources/src/main/res/values/strings.xml b/resources/src/main/res/values/strings.xml index dff1aef1..ba218214 100644 --- a/resources/src/main/res/values/strings.xml +++ b/resources/src/main/res/values/strings.xml @@ -610,4 +610,14 @@ Limit by Not connected to Internet Lock Reason + Reviews + Approved + Commented + Files: %d + Addition: %d + Changes: %d + Deletion: %d + Approved: %d + Commented: %d + Commits: %d \ No newline at end of file