display dashboard in PR and reviews & their comment

This commit is contained in:
k0shk0sh 2019-09-06 22:58:33 +02:00
parent f4561d84b4
commit 73cb05f218
27 changed files with 1205 additions and 159 deletions

View File

@ -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<Any>(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<TimelineModel?>() {
override fun areItemsTheSame(

View File

@ -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<ReviewModel?>(
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)
}
}
}
}
}

View File

@ -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<TimelineModel>(
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<TimelineModel>(
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<TimelineModel>(
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()

View File

@ -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<ReviewModel?>(
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)
}
}
}
}
}

View File

@ -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(

View File

@ -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
)

View File

@ -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
)

View File

@ -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(

View File

@ -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
)
)
)
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/pr_view_layout" />
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@drawable/toolbar_shadow_up" />
<include layout="@layout/comment_box_layout" />
</LinearLayout>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<com.fastaccess.github.ui.widget.ParentSwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
tools:showIn="@layout/issue_pr_fragment_layout">
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:windowBackground"
android:theme="?android:toolbarStyle"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:contentScrim="?android:windowBackground"
app:layout_scrollFlags="scroll|exitUntilCollapsed"
app:scrimAnimationDuration="0"
app:titleEnabled="false">
<LinearLayout
android:id="@+id/issueHeaderWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="?actionBarSize"
android:orientation="vertical"
android:visibility="invisible"
app:layout_collapseMode="none">
<include layout="@layout/pr_header_row_item" />
</LinearLayout>
<include layout="@layout/title_toolbar_layout" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/recyclerview_fastscroll_empty_state_layout" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</com.fastaccess.github.ui.widget.ParentSwipeRefreshLayout>

View File

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/commentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:foreground="?selectableItemBackground"
android:paddingTop="@dimen/spacing_normal"
android:paddingBottom="@dimen/spacing_normal">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:orientation="horizontal">
<com.fastaccess.github.ui.widget.AvatarImageView
android:id="@+id/userIcon"
android:layout_width="@dimen/small_icon_zie"
android:layout_height="@dimen/small_icon_zie"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:src="@drawable/ic_ring" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="10sp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
tools:text="Place the truffels in an ice blender, and toss immediately with tangy lime.immediately with tangy lime" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/association"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="10sp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
tools:text="Owner" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatImageButton
android:id="@+id/menu"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="?selectableItemBackgroundBorderless"
android:src="@drawable/ic_overflow" />
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:breakStrategy="simple"
android:hyphenationFrequency="none"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:textIsSelectable="true"
tools:text="Place the truffels in an ice blender, and toss immediately with tangy lime.immediately with tangy lime" />
<com.fastaccess.github.ui.widget.AdaptiveEmoticonLayout
android:id="@+id/adaptiveEmoticon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal" />
</LinearLayout>
</FrameLayout>

View File

@ -0,0 +1,52 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/CardViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginTop="@dimen/spacing_micro"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_micro"
app:cardBackgroundColor="?colorPrimary">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/fileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_push"
android:drawablePadding="@dimen/spacing_normal"
android:ellipsize="middle"
android:gravity="start|center"
android:maxLines="2"
android:paddingTop="@dimen/spacing_normal"
android:paddingBottom="@dimen/spacing_normal"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="/app/src/main/java/blah/blah/blah/blah/MainActivity.kt" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/diffHunk"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="5"
android:paddingTop="@dimen/spacing_normal"
android:paddingBottom="@dimen/spacing_normal"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="long text....." />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor" />
<include layout="@layout/comment_small_row_item" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -108,10 +108,10 @@
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/association"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textStyle="bold"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="10sp"

View File

@ -0,0 +1,366 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingTop="@dimen/spacing_normal"
android:paddingEnd="@dimen/spacing_xs_large"
android:paddingBottom="@dimen/spacing_normal">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="4"
android:textAppearance="@style/TextAppearance.AppCompat.Title"
android:textIsSelectable="true"
app:autoSizeMaxTextSize="20sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
tools:text="Place the truffels in an ice blender, and toss immediately with tangy lime.immediately with tangy lime" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_micro"
android:layout_marginBottom="@dimen/spacing_micro"
android:orientation="horizontal">
<com.google.android.material.chip.Chip
android:id="@+id/state"
style="@style/ChipStyleNoElevation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/spacing_micro"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_marginBottom="@dimen/spacing_micro"
android:textAllCaps="false"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textColor="@color/white"
tools:text="@string/open" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/opener"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="3"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="12sp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
tools:text="Place the truffels in an ice blender, and toss immediately with tangy lime.immediately with tangy lime" />
</LinearLayout>
<LinearLayout
android:id="@+id/issueComment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_micro"
android:layout_marginBottom="@dimen/spacing_micro"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/spacing_normal"
android:orientation="horizontal">
<com.fastaccess.github.ui.widget.AvatarImageView
android:id="@+id/userIcon"
android:layout_width="@dimen/large_icon_zie"
android:layout_height="@dimen/large_icon_zie"
android:layout_gravity="center"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:adjustViewBounds="true"
android:scaleType="centerCrop"
tools:src="@drawable/ic_ring" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/author"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="10sp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
tools:text="Place the truffels in an ice blender, and toss immediately with tangy lime.immediately with tangy lime" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/association"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textStyle="bold"
app:autoSizeMaxTextSize="14sp"
app:autoSizeMinTextSize="10sp"
app:autoSizeStepGranularity="1dp"
app:autoSizeTextType="uniform"
tools:text="Owner" />
</LinearLayout>
</LinearLayout>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="@dimen/spacing_micro"
android:breakStrategy="simple"
android:hyphenationFrequency="none"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textIsSelectable="true"
tools:targetApi="m"
tools:text="Place the truffels in an ice blender, and toss immediately with tangy lime.immediately with tangy lime" />
</LinearLayout>
<LinearLayout
android:id="@+id/labelsLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:orientation="vertical"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/labels"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/labels"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:id="@+id/assigneesLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:orientation="vertical"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/assignees"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/assignees"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<LinearLayout
android:id="@+id/milestoneLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal"
android:orientation="vertical"
android:visibility="gone">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/milestone"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
android:textStyle="bold" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/milestone"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
</LinearLayout>
<com.fastaccess.github.ui.widget.AdaptiveEmoticonLayout
android:id="@+id/adaptiveEmoticon"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_normal" />
<LinearLayout
android:id="@+id/prDashboard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_xs_large"
android:orientation="horizontal">
<com.google.android.material.card.MaterialCardView
style="@style/CardViewStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_normal"
android:layout_marginTop="@dimen/spacing_normal"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_marginBottom="@dimen/spacing_normal"
android:layout_weight="1"
android:minHeight="150dp"
app:cardCornerRadius="@dimen/spacing_normal"
app:cardElevation="@dimen/spacing_micro">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_normal"
android:text="@string/commits"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/commits"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/commits_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Commits: 100,00" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/files"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/files_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Files: 100,00" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/addition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/addition_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Addition 100,00" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/deletion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/deletion_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Deletion 100,00" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
style="@style/CardViewStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_normal"
android:layout_marginTop="@dimen/spacing_normal"
android:layout_marginEnd="@dimen/spacing_normal"
android:layout_marginBottom="@dimen/spacing_normal"
android:layout_weight="1"
android:minHeight="150dp"
app:cardCornerRadius="@dimen/spacing_normal"
app:cardElevation="@dimen/spacing_micro">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="@dimen/spacing_normal"
android:text="@string/reviews"
android:textAllCaps="true"
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/approved"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/approved_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Approved 100,00" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/commented"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/commented_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Commented 100,00" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/changes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:text="@string/changes_with_count"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="Changes 100,00" />
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:padding="@dimen/spacing_normal"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:visibility="invisible"
tools:text="Changes 100,00" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/CardViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_xs_large"
android:layout_marginTop="@dimen/spacing_micro"
android:layout_marginEnd="@dimen/spacing_xs_large"
android:layout_marginBottom="@dimen/spacing_micro">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/fileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawableStart="@drawable/ic_eye"
android:drawablePadding="@dimen/spacing_normal"
android:ellipsize="middle"
android:gravity="start|center"
android:maxLines="2"
android:paddingTop="@dimen/spacing_normal"
android:paddingBottom="@dimen/spacing_normal"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
tools:text="/app/src/main/java/blah/blah/blah/blah/MainActivity.kt" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/diffHunk"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="5"
android:paddingTop="@dimen/spacing_normal"
android:paddingBottom="@dimen/spacing_normal"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="long text....." />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?dividerColor" />
<include layout="@layout/comment_small_row_item" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View File

@ -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

View File

@ -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
}
}
}

View File

@ -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
)

View File

@ -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<CommentCannotUpdateReason?>? = null,
@SerializedName("reactionGroups") var reactionGroups: List<ReactionGroupModel>? = 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"),

View File

@ -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(

View File

@ -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<ShortUserModel>? = 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"

View File

@ -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
}
}
}

View File

@ -1,12 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
android:height="26dp"
android:viewportWidth="12"
android:viewportHeight="14">
<path
android:fillColor="?icon_color"
tools:fillColor="#000"
android:pathData="M18,22A2,2 0 0,0 20,20V4C20,2.89 19.1,2 18,2H12V9L9.5,7.5L7,9V2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18Z"/>
</vector>
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" />
<path
android:fillColor="?icon_color"
android:fillType="evenOdd"
android:pathData="M2.09,1A1.09,1.09 0,0 0,1 2.09v9.82c0,-0.604 0.488,-1.092 1.09,-1.092h7.637a1.09,1.09 0,0 0,1.091 -1.09V2.09A1.09,1.09 0,0 0,9.728 1H2.09z"
android:strokeWidth="1"
android:strokeColor="#000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="?icon_color"
android:fillType="evenOdd"
android:pathData="M9.727,13a1.09,1.09 0,0 0,1.091 -1.09V9.727a1.09,1.09 0,0 1,-1.09 1.09H2.09a1.09,1.09 0,1 0,0 2.182h7.636z"
android:strokeWidth="1"
android:strokeColor="#000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View File

@ -1,11 +1,30 @@
<vector
xmlns:android="http://schemas.android.com/apk/res/android"
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportHeight="24"
android:viewportWidth="24">
android:height="18dp"
android:viewportWidth="12"
android:viewportHeight="14">
<path
android:fillColor="?icon_color"
android:pathData="M18,22A2,2 0 0,0 20,20V4C20,2.89 19.1,2 18,2H12V9L9.5,7.5L7,9V2H6A2,2 0 0,0 4,4V20A2,2 0 0,0 6,22H18Z"/>
</vector>
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" />
<path
android:fillColor="?icon_color"
android:fillType="evenOdd"
android:pathData="M2.09,1A1.09,1.09 0,0 0,1 2.09v9.82c0,-0.604 0.488,-1.092 1.09,-1.092h7.637a1.09,1.09 0,0 0,1.091 -1.09V2.09A1.09,1.09 0,0 0,9.728 1H2.09z"
android:strokeWidth="1"
android:strokeColor="#000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
<path
android:fillColor="?icon_color"
android:fillType="evenOdd"
android:pathData="M9.727,13a1.09,1.09 0,0 0,1.091 -1.09V9.727a1.09,1.09 0,0 1,-1.09 1.09H2.09a1.09,1.09 0,1 0,0 2.182h7.636z"
android:strokeWidth="1"
android:strokeColor="#000"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>

View File

@ -15,6 +15,7 @@
<dimen name="fab_margin">16dp</dimen>
<dimen name="grid_spacing">0dp</dimen>
<dimen name="large_icon_zie">48dp</dimen>
<dimen name="small_icon_zie">36dp</dimen>
<dimen name="header_icon_zie">64dp</dimen>
<dimen name="icon_size">24dp</dimen>
<dimen name="divider_height">1px</dimen>

View File

@ -610,4 +610,14 @@
<string name="limit_by">Limit by</string>
<string name="no_internet_connection">Not connected to Internet</string>
<string name="lock_reason">Lock Reason</string>
<string name="reviews">Reviews</string>
<string name="approved">Approved</string>
<string name="commented">Commented</string>
<string name="files_with_count">Files: %d</string>
<string name="addition_with_count">Addition: %d</string>
<string name="changes_with_count">Changes: %d</string>
<string name="deletion_with_count">Deletion: %d</string>
<string name="approved_with_count">Approved: %d</string>
<string name="commented_with_count">Commented: %d</string>
<string name="commits_with_count">Commits: %d</string>
</resources>