Commit 6a05de19 authored by Konstantin Tskhovrebov's avatar Konstantin Tskhovrebov 🤖

Merge branch 'feature/merge_request_info' into 'feature/issue_merge_request_info'

Implement merge request info screen.

See merge request !197
parents 717de291 5bff4e60
......@@ -202,6 +202,10 @@ object Screens {
override fun getFragment() = MainMergeRequestFragment()
}
object MergeRequestDetails : SupportAppScreen() {
override fun getFragment() = MergeRequestDetailsFragment()
}
object MergeRequestInfo : SupportAppScreen() {
override fun getFragment() = MergeRequestInfoFragment()
}
......
......@@ -3,6 +3,7 @@ package ru.terrakok.gitlabclient.entity.mergerequest
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.TimeStats
import ru.terrakok.gitlabclient.entity.milestone.Milestone
data class MergeRequest(
......@@ -25,14 +26,13 @@ data class MergeRequest(
@SerializedName("work_in_progress") val workInProgress: Boolean,
@SerializedName("milestone") val milestone: Milestone?,
@SerializedName("merge_when_pipeline_succeeds") val mergeWhenPipelineSucceeds: Boolean,
@SerializedName("merge_status") val mergeStatus: String?,
@SerializedName("merge_status") val mergeStatus: MergeRequestMergeStatus,
@SerializedName("sha") val sha: String,
@SerializedName("merge_commit_sha") val mergeCommitSha: String?,
@SerializedName("user_notes_count") val userNotesCount: Int,
@SerializedName("should_remove_source_branch") val shouldRemoveSourceBranch: Boolean,
@SerializedName("force_remove_source_branch") val forceRemoveSourceBranch: Boolean,
@SerializedName("web_url") val webUrl: String?,
@SerializedName("time_stats") val timeStats: MergeRequestTimeStats?,
@SerializedName("labels") val labels: List<String>,
// The closed_by attribute was introduced in GitLab 10.6.
// This value will only be present for merge requests which were closed/merged after GitLab 10.6
......@@ -41,5 +41,8 @@ data class MergeRequest(
@SerializedName("closed_at") val closedAt: LocalDateTime?,
@SerializedName("merged_by") val mergedBy: ShortUser?,
@SerializedName("merged_at") val mergedAt: LocalDateTime?,
@SerializedName("changes") val changes: List<MergeRequestChange>?
@SerializedName("changes") val changes: List<MergeRequestChange>?,
@SerializedName("assignees") val assignees: List<ShortUser>,
@SerializedName("time_stats") val timeStats: TimeStats,
@SerializedName("discussion_locked") val discussionLocked: Boolean
)
package ru.terrakok.gitlabclient.entity.mergerequest
import com.google.gson.annotations.SerializedName
data class MergeRequestTimeStats(
@SerializedName("time_estimate") val timeEstimate: Int,
@SerializedName("total_time_spent") val totalTimeSpent: Int,
@SerializedName("human_time_estimate") val humanTimeEstimate: String?,
@SerializedName("human_total_time_spent") val humanTotalTimeSpent: String?
)
\ No newline at end of file
......@@ -14,6 +14,7 @@ import ru.terrakok.gitlabclient.entity.app.target.TargetBadgeStatus
import ru.terrakok.gitlabclient.entity.app.target.TargetHeaderIcon
import ru.terrakok.gitlabclient.entity.app.target.TargetHeaderTitle
import ru.terrakok.gitlabclient.entity.event.EventAction
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestMergeStatus
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.entity.todo.TodoAction
import ru.terrakok.gitlabclient.model.system.ResourceManager
......@@ -135,7 +136,9 @@ fun TargetHeaderTitle.getHumanName(resources: Resources) = when (this) {
when (action) {
TodoAction.ASSIGNED -> {
"$author $actionName $targetName ${resources.getString(R.string.at)} $projectName ${resources.getString(R.string.to)} $assignee"
"$author $actionName $targetName ${resources.getString(R.string.at)} $projectName ${resources.getString(
R.string.to
)} $assignee"
}
TodoAction.DIRECTLY_ADDRESSED,
TodoAction.MENTIONED -> {
......@@ -189,4 +192,10 @@ fun MilestoneState.getHumanName(resources: Resources) = when (this) {
fun MilestoneState.getStateColors(context: Context) = when (this) {
MilestoneState.ACTIVE -> Pair(context.color(R.color.green), context.color(R.color.lightGreen))
MilestoneState.CLOSED -> Pair(context.color(R.color.red), context.color(R.color.lightRed))
}
fun MergeRequestMergeStatus.getHumanName(resources: Resources) = when (this) {
MergeRequestMergeStatus.CANNOT_BE_MERGED -> resources.getString(R.string.merge_request_status_cannot_be_merged)
MergeRequestMergeStatus.CAN_BE_MERGED -> resources.getString(R.string.merge_request_status_can_be_merged)
MergeRequestMergeStatus.UNCHECKED -> resources.getString(R.string.merge_request_status_unchecked)
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.mergerequest.details
import com.arellomobile.mvp.InjectViewState
import ru.terrakok.gitlabclient.di.MergeRequestId
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.di.ProjectId
import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestInteractor
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import javax.inject.Inject
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 31.05.19.
*/
@InjectViewState
class MergeRequestDetailsPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@MergeRequestId mrIdWrapper: PrimitiveWrapper<Long>,
private val mrInteractor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
) : BasePresenter<MergeRequestDetailsView>() {
private val projectId = projectIdWrapper.value
private val mrId = mrIdWrapper.value
override fun onFirstViewAttach() {
super.onFirstViewAttach()
mrInteractor
.getMergeRequest(projectId, mrId)
.flatMap { mr ->
mdConverter
.markdownToSpannable(mr.description)
.map { Pair(mr, it) }
}
.doOnSubscribe { viewState.showEmptyProgress(true) }
.doAfterTerminate { viewState.showEmptyProgress(false) }
.subscribe(
{ (mr, mdDescription) -> viewState.showDetails(mr, mdDescription) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.mergerequest.details
import com.arellomobile.mvp.MvpView
import com.arellomobile.mvp.viewstate.strategy.AddToEndSingleStrategy
import com.arellomobile.mvp.viewstate.strategy.OneExecutionStateStrategy
import com.arellomobile.mvp.viewstate.strategy.StateStrategyType
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequest
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 31.05.19.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface MergeRequestDetailsView : MvpView {
fun showDetails(mr: MergeRequest, mdDescription: CharSequence)
fun showEmptyProgress(show: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
fun showMessage(message: String)
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.mergerequest.info
import com.arellomobile.mvp.InjectViewState
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.di.MergeRequestId
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.di.ProjectId
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestInteractor
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.presentation.global.BasePresenter
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import javax.inject.Inject
/**
......@@ -18,8 +20,8 @@ class MergeRequestInfoPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@MergeRequestId mrIdWrapper: PrimitiveWrapper<Long>,
private val mrInteractor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
private val errorHandler: ErrorHandler,
private val router: FlowRouter
) : BasePresenter<MergeRequestInfoView>() {
private val projectId = projectIdWrapper.value
......@@ -30,17 +32,14 @@ class MergeRequestInfoPresenter @Inject constructor(
mrInteractor
.getMergeRequest(projectId, mrId)
.flatMap { mr ->
mdConverter
.markdownToSpannable(mr.description)
.map { Pair(mr, it) }
}
.doOnSubscribe { viewState.showEmptyProgress(true) }
.doAfterTerminate { viewState.showEmptyProgress(false) }
.subscribe(
{ (mr, mdDescription) -> viewState.showInfo(mr, mdDescription) },
{ viewState.showInfo(it) },
{ errorHandler.proceed(it, { viewState.showMessage(it) }) }
)
.connect()
}
fun onAssigneeClicked(assignee: ShortUser) = router.startFlow(Screens.UserFlow(assignee.id))
}
\ No newline at end of file
......@@ -11,7 +11,8 @@ import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequest
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface MergeRequestInfoView : MvpView {
fun showInfo(mr: MergeRequest, mdDescription: CharSequence)
fun showInfo(mr: MergeRequest)
fun showEmptyProgress(show: Boolean)
@StateStrategyType(OneExecutionStateStrategy::class)
......
......@@ -49,6 +49,7 @@ class IssueInfoFragment : BaseFragment(), IssueInfoView {
if (assignees.isNotEmpty()) {
assigneesNone.visible(false)
with(assigneesList) {
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
setHasFixedSize(true)
adapter = AssigneesAdapter { presenter.onAssigneeClicked(it) }.apply {
......@@ -88,7 +89,7 @@ class IssueInfoFragment : BaseFragment(), IssueInfoView {
}
private fun showLockIssue(discussionLocked: Boolean) {
val stringRes = if (discussionLocked) R.string.lock_issue_locked else R.string.lock_issue_unlocked
val stringRes = if (discussionLocked) R.string.lock_locked else R.string.lock_unlocked
lockIssueValue.text = getString(stringRes)
lockIssueValue.alpha = if (discussionLocked) ALPHA_VALUE else ALPHA_NONE
}
......
......@@ -63,16 +63,18 @@ class MainMergeRequestFragment : BaseFragment(), MergeRequestView {
private inner class MergeRequestPagesAdapter : FragmentPagerAdapter(childFragmentManager) {
override fun getItem(position: Int): BaseFragment = when (position) {
TAB_DETAILS -> Screens.MergeRequestInfo.fragment
TAB_DETAILS -> Screens.MergeRequestDetails.fragment
TAB_INFO -> Screens.MergeRequestInfo.fragment
TAB_COMMITS -> Screens.MergeRequestCommits.fragment
TAB_NOTES -> Screens.MergeRequestNotes.fragment
else -> Screens.MergeRequestChanges.fragment
}
override fun getCount() = 4
override fun getCount() = 5
override fun getPageTitle(position: Int) = when (position) {
TAB_DETAILS -> getString(R.string.merge_request_info_tab)
TAB_DETAILS -> getString(R.string.merge_request_details_tab)
TAB_INFO -> getString(R.string.merge_request_info_tab)
TAB_COMMITS -> getString(R.string.merge_request_commits_tab)
TAB_NOTES -> getString(R.string.merge_request_discussion_tab)
TAB_CHANGES -> getString(R.string.merge_request_changes_tab)
......@@ -82,8 +84,9 @@ class MainMergeRequestFragment : BaseFragment(), MergeRequestView {
companion object {
private const val TAB_DETAILS = 0
private const val TAB_COMMITS = 1
private const val TAB_NOTES = 2
private const val TAB_CHANGES = 3
private const val TAB_INFO = 1
private const val TAB_COMMITS = 2
private const val TAB_NOTES = 3
private const val TAB_CHANGES = 4
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.ui.mergerequest
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.ProvidePresenter
import kotlinx.android.synthetic.main.fragment_mr_details.*
import ru.noties.markwon.Markwon
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequest
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestState
import ru.terrakok.gitlabclient.extension.*
import ru.terrakok.gitlabclient.presentation.mergerequest.details.MergeRequestDetailsPresenter
import ru.terrakok.gitlabclient.presentation.mergerequest.details.MergeRequestDetailsView
import ru.terrakok.gitlabclient.ui.global.BaseFragment
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 31.05.19.
*/
class MergeRequestDetailsFragment : BaseFragment(), MergeRequestDetailsView {
override val layoutRes = R.layout.fragment_mr_details
@InjectPresenter
lateinit var presenter: MergeRequestDetailsPresenter
@ProvidePresenter
fun providePresenter() =
scope.getInstance(MergeRequestDetailsPresenter::class.java)
override fun showDetails(mr: MergeRequest, mdDescription: CharSequence) {
titleTextView.text = mr.title
when (mr.state) {
MergeRequestState.OPENED -> {
stateImageView.tint(R.color.green)
subtitleTextView.text = String.format(
getString(R.string.merge_request_info_subtitle),
getString(R.string.target_status_opened),
mr.author.name,
mr.createdAt.humanTime(resources)
)
}
MergeRequestState.MERGED -> {
stateImageView.tint(R.color.blue)
subtitleTextView.text =
if (mr.mergedBy != null && mr.mergedAt != null) {
String.format(
getString(R.string.issue_info_subtitle),
getString(R.string.target_status_merged),
mr.mergedBy.name,
mr.mergedAt.humanTime(resources)
)
} else {
getString(R.string.target_status_merged)
}
}
MergeRequestState.CLOSED -> {
stateImageView.tint(R.color.red)
subtitleTextView.text =
if (mr.closedBy != null && mr.closedAt != null) {
String.format(
getString(R.string.issue_info_subtitle),
getString(R.string.target_status_closed),
mr.closedBy.name,
mr.closedAt.humanTime(resources)
)
} else {
getString(R.string.target_status_closed)
}
}
}
avatarImageView.loadRoundedImage(mr.author.avatarUrl, context)
Markwon.setText(descriptionTextView, mdDescription)
}
override fun showEmptyProgress(show: Boolean) {
mrDetailsContainer.visible(!show)
fullscreenProgressView.visible(show)
}
override fun showMessage(message: String) {
showSnackMessage(message)
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.ui.mergerequest
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.ProvidePresenter
import kotlinx.android.synthetic.main.fragment_mr_info.*
import ru.noties.markwon.Markwon
import kotlinx.android.synthetic.main.item_target_badge.view.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.TimeStats
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequest
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestMergeStatus
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestState
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.extension.*
import ru.terrakok.gitlabclient.presentation.mergerequest.info.MergeRequestInfoPresenter
import ru.terrakok.gitlabclient.presentation.mergerequest.info.MergeRequestInfoView
import ru.terrakok.gitlabclient.ui.global.BaseFragment
import ru.terrakok.gitlabclient.ui.global.list.AssigneesAdapter
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 03.02.18.
......@@ -26,49 +33,78 @@ class MergeRequestInfoFragment : BaseFragment(), MergeRequestInfoView {
fun providePresenter() =
scope.getInstance(MergeRequestInfoPresenter::class.java)
override fun showInfo(mr: MergeRequest, mdDescription: CharSequence) {
titleTextView.text = mr.title
when (mr.state) {
MergeRequestState.OPENED -> {
stateImageView.tint(R.color.green)
subtitleTextView.text = String.format(
getString(R.string.merge_request_info_subtitle),
getString(R.string.target_status_opened),
mr.author.name,
mr.createdAt.humanTime(resources)
)
override fun showInfo(mr: MergeRequest) {
with(mr) {
showAssignees(assignees)
showMilestone(milestone)
showMergeStatus(state, mergeStatus)
showTimeStats(timeStats)
showLockMergeRequest(discussionLocked)
showLabels(labels)
}
}
private fun showAssignees(assignees: List<ShortUser>) {
if (assignees.isNotEmpty()) {
assigneesNone.visible(false)
with(assigneesList) {
isNestedScrollingEnabled = false
layoutManager = LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
setHasFixedSize(true)
adapter = AssigneesAdapter { presenter.onAssigneeClicked(it) }.apply {
setData(assignees)
}
}
MergeRequestState.MERGED -> {
stateImageView.tint(R.color.blue)
subtitleTextView.text =
if (mr.mergedBy != null && mr.mergedAt != null) {
String.format(
getString(R.string.issue_info_subtitle),
getString(R.string.target_status_merged),
mr.mergedBy.name,
mr.mergedAt.humanTime(resources)
)
} else {
getString(R.string.target_status_merged)
}
} else {
assigneesList.visible(false)
}
}
private fun showMilestone(milestone: Milestone?) {
milestoneValue.text = if (milestone != null) {
milestone.title
} else {
getString(R.string.issue_merge_request_none)
}
milestoneValue.alpha = if (milestone != null) ALPHA_VALUE else ALPHA_NONE
}
private fun showMergeStatus(state: MergeRequestState, mergeStatus: MergeRequestMergeStatus) {
mergeStatusValue.text = if (state == MergeRequestState.OPENED) {
mergeStatus.getHumanName(resources)
} else {
getString(R.string.issue_merge_request_none)
}
mergeStatusValue.alpha = if (state == MergeRequestState.OPENED) ALPHA_VALUE else ALPHA_NONE
}
private fun showTimeStats(timeStats: TimeStats) {
timeStatsValue.setTimeStats(timeStats)
}
private fun showLockMergeRequest(discussionLocked: Boolean) {
val stringRes = if (discussionLocked) R.string.lock_locked else R.string.lock_unlocked
lockMergeRequestValue.text = getString(stringRes)
lockMergeRequestValue.alpha = if (discussionLocked) ALPHA_VALUE else ALPHA_NONE
}
private fun showLabels(labels: List<String>) {
if (labels.isNotEmpty()) {
labelsNone.visible(false)
val colorPrimary = requireContext().color(R.color.colorPrimary)
val colorPrimaryLight = requireContext().color(R.color.colorPrimaryLight)
(1..labels.size).forEach { _ ->
labelsValue.inflate(R.layout.item_target_badge, true)
}
MergeRequestState.CLOSED -> {
stateImageView.tint(R.color.red)
subtitleTextView.text =
if (mr.closedBy != null && mr.closedAt != null) {
String.format(
getString(R.string.issue_info_subtitle),
getString(R.string.target_status_closed),
mr.closedBy.name,
mr.closedAt.humanTime(resources)
)
} else {
getString(R.string.target_status_closed)
}
labels.forEachIndexed { index, label ->
val labelView = labelsValue.getChildAt(index)
labelView.textTextView.text = label
labelView.textTextView.setTextColor(colorPrimary)
labelView.textTextView.setBackgroundColor(colorPrimaryLight)
}
} else {
labelsValue.visible(false)
}
avatarImageView.loadRoundedImage(mr.author.avatarUrl, context)
Markwon.setText(descriptionTextView, mdDescription)
}
override fun showEmptyProgress(show: Boolean) {
......@@ -79,4 +115,9 @@ class MergeRequestInfoFragment : BaseFragment(), MergeRequestInfoView {
override fun showMessage(message: String) {
showSnackMessage(message)
}
companion object {
private const val ALPHA_VALUE = 1f
private const val ALPHA_NONE = 0.38f
}
}
\ No newline at end of file
......@@ -13,7 +13,9 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="wrap_content"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<TextView
android:id="@+id/assigneesTitle"
......@@ -21,7 +23,6 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@string/assignees_title"
app:layout_constraintEnd_toEndOf="parent"
......@@ -32,6 +33,7 @@
android:id="@+id/assigneesList"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:overScrollMode="never"
app:layout_constraintEnd_toEndOf="@id/assigneesTitle"
app:layout_constraintStart_toStartOf="@id/assigneesTitle"
app:layout_constraintTop_toBottomOf="@id/assigneesTitle" />
......@@ -78,7 +80,7 @@
app:layout_constraintStart_toStartOf="@id/milestoneTitle"
app:layout_constraintTop_toBottomOf="@id/milestoneTitle"
tools:alpha="0.38"
tools:text="@string/issue_merge_request_none" />
tools:text="dadklakdskldakldkdsakdalkdsadklsadklaskdlkldasklldsadlka" />
<TextView
android:id="@+id/dueDateTitle"
......@@ -189,7 +191,7 @@
app:layout_constraintStart_toStartOf="@id/lockIssueTitle"
app:layout_constraintTop_toBottomOf="@id/lockIssueTitle"
tools:alpha="0.38"
tools:text="@string/lock_issue_unlocked" />
tools:text="@string/lock_unlocked" />
<TextView
android:id="@+id/confidentialityTitle"
......
<?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:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:id="@+id/mrDetailsContainer"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/avatarImageView"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:src="@drawable/default_img" />
<TextView
android:id="@+id/titleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textColor="@color/primary_text"
android:textSize="18sp"
app:fontFamily="@font/roboto_medium"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/avatarImageView"
app:layout_constraintTop_toTopOf="@id/avatarImageView"
tools:text="MergeRequest title" />
<ImageView
android:id="@+id/stateImageView"
android:layout_width="16dp"
android:layout_height="16dp"
android:layout_marginTop="6dp"
android:src="@drawable/circle"
app:layout_constraintEnd_toStartOf="@id/subtitleTextView"
app:layout_constraintStart_toStartOf="@id/titleTextView"
app:layout_constraintTop_toBottomOf="@id/titleTextView" />
<TextView
android:id="@+id/subtitleTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="4dp"
android:layout_marginEnd="16dp"
android:textColor="@color/secondary_text"
android:textSize="14sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/stateImageView"
app:layout_constraintTop_toBottomOf="@id/titleTextView"
tools:text="Opened by CraggyHaggy 3d ago" />
<View
android:id="@+id/divider"
android:layout_width="0dp"
android:layout_height="@dimen/divider_size"
android:layout_marginTop="16dp"
android:background="@color/divider"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/subtitleTextView" />
<TextView
android:id="@+id/descriptionTextView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:linksClickable="true"
android:padding="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@+id/divider" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<include
android:id="@+id/fullscreenProgressView"
layout="@layout/layout_gitlab_progress"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:visibility="gone" />
</FrameLayout>
\ No newline at end of file
......@@ -148,8 +148,8 @@
<!--Merge Request Info screen-->
<string name="merge_request_title">!%d</string>
<string name="merge_request_info_tab">Info</string>
<string name="merge_request_details_tab">Details</string>
<string name="merge_request_info_tab">Info</string>
<string name="merge_request_commits_tab">Commits</string>
<string name="merge_request_discussion_tab">Discussion</string>
<string name="merge_request_changes_tab">Changes</string>
......@@ -202,15 +202,21 @@
<string name="time_stats_title">Time tracker</string>
<string name="weight_title">Weight</string>
<string name="lock_issue_title">Lock issue</string>
<string name="lock_issue_unlocked">Unlocked</string>
<string name="lock_issue_locked">Locked</string>
<string name="lock_unlocked">Unlocked</string>
<string name="lock_locked">Locked</string>
<string name="confidentiality_title">Confidentiality</string>
<string name="confidentiality_confidential">This issue is confidential</string>
<string name="confidentiality_not_confidential">Not confidential</string>
<string name="labels_title">Labels</string>
<string name="time_stats_empty_stats">No estimate or time spent</string>
<string name="time_stats_spent_time">Spent: </string>