diff --git a/app/src/main/graphql/pr/PullRequestTimeline.graphql b/app/src/main/graphql/pr/PullRequestTimeline.graphql index 7823ec85..59af0aa1 100644 --- a/app/src/main/graphql/pr/PullRequestTimeline.graphql +++ b/app/src/main/graphql/pr/PullRequestTimeline.graphql @@ -12,11 +12,11 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!, $page: } totalCount nodes { - __typename ... on Commit { id oid url + committedDate messageHeadlineHTML author { name @@ -130,6 +130,11 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!, $page: createdAt isCrossRepository isDirectReference + actor { + avatarUrl + login + url + } commitRepository { nameWithOwner url @@ -178,6 +183,7 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!, $page: } } ... on LabeledEvent { + createdAt actor { login avatarUrl @@ -189,6 +195,7 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!, $page: } } ... on UnlabeledEvent { + createdAt actor { login avatarUrl @@ -224,6 +231,11 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!, $page: createdAt currentTitle previousTitle + actor { + login + avatarUrl + url + } } ... on LockedEvent { createdAt @@ -243,6 +255,11 @@ query PullRequestTimeline($owner: String!, $name: String!, $number: Int!, $page: } ... on DeployedEvent { createdAt + actor { + login + avatarUrl + url + } ref { name prefix diff --git a/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java b/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java index 6ade3ecd..b50d5469 100644 --- a/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java +++ b/app/src/main/java/com/fastaccess/provider/timeline/HtmlHelper.java @@ -129,7 +129,7 @@ public class HtmlHelper { return mySpanner; } - @ColorInt static int getWindowBackground(@PrefGetter.ThemeType int theme) { + @ColorInt public static int getWindowBackground(@PrefGetter.ThemeType int theme) { switch (theme) { case PrefGetter.AMLOD: return Color.parseColor("#0B162A"); diff --git a/app/src/main/java/com/fastaccess/ui/adapter/PullReqeustTimelineAdapter.kt b/app/src/main/java/com/fastaccess/ui/adapter/PullReqeustTimelineAdapter.kt index c7e61e28..79b2fd3e 100644 --- a/app/src/main/java/com/fastaccess/ui/adapter/PullReqeustTimelineAdapter.kt +++ b/app/src/main/java/com/fastaccess/ui/adapter/PullReqeustTimelineAdapter.kt @@ -2,6 +2,7 @@ package com.fastaccess.ui.adapter import android.view.ViewGroup import com.fastaccess.data.dao.timeline.PullRequestTimelineModel +import com.fastaccess.ui.adapter.viewholder.PullRequestEventViewHolder import com.fastaccess.ui.adapter.viewholder.PullStatusViewHolder import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder @@ -16,13 +17,15 @@ class PullReqeustTimelineAdapter constructor(val data: ArrayList PullStatusViewHolder.newInstance(parent) } - return null + return PullRequestEventViewHolder.newInstance(parent, this) } override fun onBindView(holder: BaseViewHolder?, position: Int) { val item = data[position] if (item.type == PullRequestTimelineModel.STATUS) { (holder as PullStatusViewHolder).bind(item.status) + }else { + holder?.bind(item) } } diff --git a/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt new file mode 100644 index 00000000..b824409f --- /dev/null +++ b/app/src/main/java/com/fastaccess/ui/adapter/viewholder/PullRequestEventViewHolder.kt @@ -0,0 +1,368 @@ +package com.fastaccess.ui.adapter.viewholder + +import android.annotation.SuppressLint +import android.graphics.Color +import android.text.style.BackgroundColorSpan +import android.view.View +import android.view.ViewGroup +import butterknife.BindView +import com.fastaccess.R +import com.fastaccess.data.dao.timeline.PullRequestTimelineModel +import com.fastaccess.helper.ParseDateFormat +import com.fastaccess.helper.PrefGetter +import com.fastaccess.helper.ViewHelper +import com.fastaccess.provider.scheme.LinkParserHelper +import com.fastaccess.provider.timeline.HtmlHelper +import com.fastaccess.ui.widgets.AvatarLayout +import com.fastaccess.ui.widgets.FontTextView +import com.fastaccess.ui.widgets.ForegroundImageView +import com.fastaccess.ui.widgets.SpannableBuilder +import com.fastaccess.ui.widgets.recyclerview.BaseRecyclerAdapter +import com.fastaccess.ui.widgets.recyclerview.BaseViewHolder +import com.zzhoujay.markdown.style.CodeSpan +import pr.PullRequestTimelineQuery +import java.util.* + +/** + * Created by kosh on 03/08/2017. + */ + +class PullRequestEventViewHolder private constructor(val view: View, adapter: BaseRecyclerAdapter<*, *, *>) : + BaseViewHolder(view, adapter) { + + @BindView(R.id.stateImage) lateinit var stateImage: ForegroundImageView + @BindView(R.id.avatarLayout) lateinit var avatarLayout: AvatarLayout + @BindView(R.id.stateText) lateinit var stateText: FontTextView + + override fun bind(t: PullRequestTimelineModel) { + val node = t.node + avatarLayout.setUrl(null, null, false, false) + stateText.text = null + node?.let { + it.asAssignedEvent()?.let { assignedEvent(it) } + it.asBaseRefForcePushedEvent()?.let { forcePushEvent(it) } + it.asClosedEvent()?.let { closedEvent(it) } + it.asCommit()?.let { commitEvent(it) } + it.asDemilestonedEvent()?.let { demilestonedEvent(it) } + it.asDeployedEvent()?.let { deployedEvent(it) } + it.asHeadRefDeletedEvent()?.let { refDeletedEvent(it) } + it.asHeadRefForcePushedEvent()?.let { refForPushedEvent(it) } + it.asHeadRefRestoredEvent()?.let { headRefRestoredEvent(it) } + it.asLabeledEvent()?.let { labeledEvent(it) } + it.asLockedEvent()?.let { lockEvent(it) } + it.asMergedEvent()?.let { mergedEvent(it) } + it.asMilestonedEvent()?.let { milestoneEvent(it) } + it.asReferencedEvent()?.let { referenceEvent(it) } + it.asRenamedTitleEvent()?.let { renamedEvent(it) } + it.asReopenedEvent()?.let { reopenedEvent(it) } + it.asUnassignedEvent()?.let { unassignedEvent(it) } + it.asUnlabeledEvent()?.let { unlabeledEvent(it) } + it.asUnlockedEvent()?.let { unlockedEvent(it) } + } + } + + @SuppressLint("SetTextI18n") + private fun unlockedEvent(event: PullRequestTimelineQuery.AsUnlockedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("unlocked this conversation") + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_lock) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun unlabeledEvent(event: PullRequestTimelineQuery.AsUnlabeledEvent) { + event.actor()?.let { + val color = Color.parseColor("#" + event.label().color()) + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("unlabeled") + .append(" ") + .append(event.label().name(), CodeSpan(color, ViewHelper.generateTextColor(color), 5.0f)) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_label) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun unassignedEvent(event: PullRequestTimelineQuery.AsUnassignedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("unassigned") + .append(" ") + .append(event.user()?.login()) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_profile) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun reopenedEvent(event: PullRequestTimelineQuery.AsReopenedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("reopened this") + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_issue_opened) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun renamedEvent(event: PullRequestTimelineQuery.AsRenamedTitleEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("renamed this from").append(" ").append(event.previousTitle()) + .append(" ").append("to").append(" ").bold(event.currentTitle()) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_edit) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun referenceEvent(event: PullRequestTimelineQuery.AsReferencedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append(if (event.isCrossRepository) "cross referenced this" else "referenced this") + .append(" ") + .append("from").append(" ") + .url(if (event.commit() != null) { + substring(event.commit()?.oid()?.toString()) + } else if (event.subject().asIssue() != null) { + "${event.subject().asIssue()?.title()}#${event.subject().asIssue()?.number()}" + } else if (event.subject().asPullRequest() != null) { + "${event.subject().asPullRequest()?.title()}#${event.subject().asPullRequest()?.number()}" + } else { + "" + }) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_push) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun milestoneEvent(event: PullRequestTimelineQuery.AsMilestonedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("added this to the") + .append(" ") + .append(event.milestoneTitle()).append(" ").append("milestone") + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_milestone) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun mergedEvent(event: PullRequestTimelineQuery.AsMergedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("merged this in") + .append(" ") + .url(substring(event.commit()?.oid()?.toString())) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_merge) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun lockEvent(event: PullRequestTimelineQuery.AsLockedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("locked and limited conversation to collaborators") + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_lock) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun labeledEvent(event: PullRequestTimelineQuery.AsLabeledEvent) { + event.actor()?.let { + val color = Color.parseColor("#" + event.label().color()) + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("labeled") + .append(" ") + .append(event.label().name(), CodeSpan(color, ViewHelper.generateTextColor(color), 5.0f)) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_label) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun headRefRestoredEvent(event: PullRequestTimelineQuery.AsHeadRefRestoredEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("head ref restored", BackgroundColorSpan(HtmlHelper.getWindowBackground(PrefGetter.getThemeType()))) + .append(" ") + .url(substring(event.pullRequest().headRefName())) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_push) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun refForPushedEvent(event: PullRequestTimelineQuery.AsHeadRefForcePushedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("reference force pushed to", BackgroundColorSpan(HtmlHelper.getWindowBackground(PrefGetter.getThemeType()))) + .append(" ") + .url(substring(event.afterCommit().oid().toString())) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_push) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun refDeletedEvent(event: PullRequestTimelineQuery.AsHeadRefDeletedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("head ref delete", BackgroundColorSpan(HtmlHelper.getWindowBackground(PrefGetter.getThemeType()))) + .append(" ") + .url(substring(event.headRefName())) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_trash) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun deployedEvent(event: PullRequestTimelineQuery.AsDeployedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("make a deployment", BackgroundColorSpan(HtmlHelper.getWindowBackground(PrefGetter.getThemeType()))) + .append(" ") + .append(event.deployment().latestStatus()?.state()?.name) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_push) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun demilestonedEvent(event: PullRequestTimelineQuery.AsDemilestonedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("removed this from the") + .append(" ") + .append(event.milestoneTitle()).append(" ").append("milestone") + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_milestone) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun commitEvent(event: PullRequestTimelineQuery.AsCommit) { + event.author()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.name()) + .append(" ") + .append("committed") + .append(" ") + .append("${event.messageHeadlineHTML()}#${substring(event.oid().toString())}") + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.committedDate() as Date))) + stateImage.setImageResource(R.drawable.ic_push) + avatarLayout.setUrl(it.user()?.avatarUrl().toString(), it.user()?.login(), false, + LinkParserHelper.isEnterprise(it.user()?.url().toString())) + } + } + + private fun closedEvent(event: PullRequestTimelineQuery.AsClosedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("closed this in") + .append(" ") + .url(substring(event.commit()?.oid()?.toString())) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_merge) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun forcePushEvent(event: PullRequestTimelineQuery.AsBaseRefForcePushedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("force pushed to", BackgroundColorSpan(HtmlHelper.getWindowBackground(PrefGetter.getThemeType()))) + .append(" ") + .url(substring(event.afterCommit().oid().toString())) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_push) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun assignedEvent(event: PullRequestTimelineQuery.AsAssignedEvent) { + event.actor()?.let { + stateText.text = SpannableBuilder.builder() + .bold(it.login()) + .append(" ") + .append("assigned") + .append(" ") + .append(event.user()?.login()) + .append(" ") + .append(ParseDateFormat.getTimeAgo((event.createdAt() as Date))) + stateImage.setImageResource(R.drawable.ic_profile) + avatarLayout.setUrl(it.avatarUrl().toString(), it.login(), false, LinkParserHelper.isEnterprise(it.url().toString())) + } + } + + private fun substring(value: String?): String { + if (value == null) { + return "" + } + if (value.length <= 7) return value + else return value.substring(0, 7) + } + + companion object { + fun newInstance(parent: ViewGroup, adapter: BaseRecyclerAdapter<*, *, *>): PullRequestEventViewHolder { + return PullRequestEventViewHolder(getView(parent, R.layout.label_row_item), adapter) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java index 9730e46b..30642645 100644 --- a/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java +++ b/app/src/main/java/com/fastaccess/ui/widgets/recyclerview/BaseRecyclerAdapter.java @@ -120,6 +120,7 @@ public abstract class BaseRecyclerAdapter