Commit 02125872 authored by Konstantin Tskhovrebov's avatar Konstantin Tskhovrebov 🤖

added simple notes list for mr

parent e40796a8
......@@ -109,7 +109,7 @@ class MergeRequestRepository @Inject constructor(
projectId: Long,
mergeRequestId: Long,
orderBy: OrderBy? = null,
sort: Sort? = null
sort: Sort? = Sort.ASC
) = api
.getMergeRequestNotes(projectId, mergeRequestId, orderBy, sort)
.subscribeOn(schedulers.io())
......
package ru.terrakok.gitlabclient.presentation.global
import ru.terrakok.gitlabclient.entity.Note
data class NoteWithFormattedBody(val note: Note, val body: CharSequence)
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.mergerequest.notes
import ru.terrakok.cicerone.Router
import com.arellomobile.mvp.InjectViewState
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 ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
import ru.terrakok.gitlabclient.toothpick.PrimitiveWrapper
import ru.terrakok.gitlabclient.toothpick.qualifier.MergeRequestId
import ru.terrakok.gitlabclient.toothpick.qualifier.ProjectId
......@@ -13,10 +14,10 @@ import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 12.02.18.
*/
@InjectViewState
class MergeRequestNotesPresenter @Inject constructor(
@ProjectId projectIdWrapper: PrimitiveWrapper<Long>,
@MergeRequestId mrIdWrapper: PrimitiveWrapper<Long>,
private val router: Router,
private val mrInteractor: MergeRequestInteractor,
private val mdConverter: MarkDownConverter,
private val errorHandler: ErrorHandler
......@@ -33,7 +34,7 @@ class MergeRequestNotesPresenter @Inject constructor(
.flatMapIterable { it }
.flatMap { note ->
mdConverter.markdownToSpannable(note.body)
.map { MergeRequestNotesView.NoteWithFormattedBody(note, it) }
.map { NoteWithFormattedBody(note, it) }
.toObservable()
}
.toList()
......
......@@ -4,14 +4,13 @@ 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.Note
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 12.02.18.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface MergeRequestNotesView : MvpView {
data class NoteWithFormattedBody(val note: Note, val body: CharSequence)
fun showNotes(notes: List<NoteWithFormattedBody>)
fun showProgress(show: Boolean)
......
......@@ -19,25 +19,27 @@ class AppLibraryAdapterDelegate(private val clickListener: (AppLibrary) -> Unit)
items[position] is AppLibrary
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
AppLibraryViewHolder(parent.inflate(R.layout.item_app_library), clickListener)
ViewHolder(parent.inflate(R.layout.item_app_library))
override fun onBindViewHolder(items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>) =
(viewHolder as AppLibraryViewHolder).bind(items[position] as AppLibrary)
(viewHolder as ViewHolder).bind(items[position] as AppLibrary)
private class AppLibraryViewHolder(val view: View, clickListener: (AppLibrary) -> Unit) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var appLibrary: AppLibrary
init {
view.setOnClickListener { clickListener.invoke(appLibrary) }
view.setOnClickListener { clickListener(appLibrary) }
}
fun bind(appLibrary: AppLibrary) {
this.appLibrary = appLibrary
view.titleTextView.text = appLibrary.name
view.subtitleTextView.text = appLibrary.license.getHumanName(view.resources)
with(itemView) {
titleTextView.text = appLibrary.name
subtitleTextView.text = appLibrary.license.getHumanName(resources)
}
}
}
}
\ No newline at end of file
......@@ -16,12 +16,12 @@ class ProgressAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
items[position] is ProgressItem
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
ProgressViewHolder(parent.inflate(R.layout.item_progress))
ViewHolder(parent.inflate(R.layout.item_progress))
override fun onBindViewHolder(items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>) {}
private class ProgressViewHolder(view: View) : RecyclerView.ViewHolder(view)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view)
}
\ No newline at end of file
......@@ -20,37 +20,39 @@ class ProjectAdapterDelegate(private val clickListener: (Project) -> Unit) : Ada
items[position] is Project
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
ProjectViewHolder(parent.inflate(R.layout.item_project), clickListener)
ViewHolder(parent.inflate(R.layout.item_project))
override fun onBindViewHolder(items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>) =
(viewHolder as ProjectViewHolder).bind(items[position] as Project)
(viewHolder as ViewHolder).bind(items[position] as Project)
private class ProjectViewHolder(val view: View, clickListener: (Project) -> Unit) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var project: Project
init {
view.setOnClickListener { clickListener.invoke(project) }
view.setOnClickListener { clickListener(project) }
}
fun bind(project: Project) {
this.project = project
view.titleTextView.text = project.nameWithNamespace
view.descriptionTextView.visibility = if (project.description.isNullOrEmpty()) View.GONE else View.VISIBLE
view.descriptionTextView.text = project.description
view.starsTextView.text = project.starCount.toString()
view.forksTextView.text = project.forksCount.toString()
view.iconImageView.setImageResource(when (project.visibility) {
Visibility.PRIVATE -> R.drawable.ic_lock_white_18dp
Visibility.INTERNAL -> R.drawable.ic_security_white_24dp
else -> R.drawable.ic_globe_18dp
})
view.avatarImageView.loadRoundedImage(project.avatarUrl)
with(itemView) {
titleTextView.text = project.nameWithNamespace
descriptionTextView.visibility = if (project.description.isNullOrEmpty()) View.GONE else View.VISIBLE
descriptionTextView.text = project.description
starsTextView.text = project.starCount.toString()
forksTextView.text = project.forksCount.toString()
iconImageView.setImageResource(when (project.visibility) {
Visibility.PRIVATE -> R.drawable.ic_lock_white_18dp
Visibility.INTERNAL -> R.drawable.ic_security_white_24dp
else -> R.drawable.ic_globe_18dp
})
avatarImageView.loadRoundedImage(project.avatarUrl)
}
}
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.ui.global.list
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.support.v4.content.ContextCompat
import android.support.v7.widget.RecyclerView
import android.view.View
import ru.terrakok.gitlabclient.R
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 16.02.18.
*/
class SimpleDividerDecorator(
private val dividerSizePx: Int,
dividerColor: Int
) : RecyclerView.ItemDecoration() {
private val paint = Paint().apply { color = dividerColor }
constructor(context: Context) : this(
context.resources.getDimensionPixelSize(R.dimen.divider_size),
ContextCompat.getColor(context, R.color.divider)
)
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) {
super.getItemOffsets(outRect, view, parent, state)
if (parent.getChildAdapterPosition(view) == 0) return
outRect.top = dividerSizePx
}
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State?) {
val dividerLeft = parent.paddingLeft
val dividerRight = parent.width - parent.paddingRight
val childCount = parent.childCount
for (i in 0..childCount - 1) {
val child = parent.getChildAt(i)
val params = child.layoutParams as RecyclerView.LayoutParams
val dividerTop = child.bottom + params.bottomMargin
val dividerBottom = dividerTop + dividerSizePx
val divRect = Rect(dividerLeft, dividerTop, dividerRight, dividerBottom)
canvas.drawRect(divRect, paint)
}
}
}
package ru.terrakok.gitlabclient.ui.global.list
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate
import kotlinx.android.synthetic.main.item_system_note.view.*
import ru.noties.markwon.Markwon
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.Note
import ru.terrakok.gitlabclient.extension.inflate
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 18.06.17.
*/
class SystemNoteAdapterDelegate(private val clickListener: (Note) -> Unit) : AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
with(items[position]) { this is NoteWithFormattedBody && this.note.isSystem }
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
ViewHolder(parent.inflate(R.layout.item_system_note))
override fun onBindViewHolder(items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>) =
(viewHolder as ViewHolder).bind(items[position] as NoteWithFormattedBody)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var note: Note
init {
view.setOnClickListener { clickListener(note) }
}
fun bind(data: NoteWithFormattedBody) {
this.note = data.note
with(itemView) {
titleTextView.text = note.author.name
Markwon.setText(subtitleTextView, data.body)
}
}
}
}
\ No newline at end of file
......@@ -27,19 +27,15 @@ class TargetHeaderAdapterDelegate(
items[position] is TargetHeader
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
TargetHeaderViewHolder(parent.inflate(R.layout.item_target_header), avatarClickListener, clickListener)
ViewHolder(parent.inflate(R.layout.item_target_header))
override fun onBindViewHolder(items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>) =
(viewHolder as TargetHeaderViewHolder).bind(items[position] as TargetHeader)
(viewHolder as ViewHolder).bind(items[position] as TargetHeader)
private class TargetHeaderViewHolder(
private val view: View,
private val avatarClickListener: (Long) -> Unit,
private val clickListener: (TargetHeader) -> Unit
) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var item: TargetHeader
init {
......@@ -53,77 +49,79 @@ class TargetHeaderAdapterDelegate(
fun bind(item: TargetHeader) {
this.item = item
val res = view.resources
view.titleTextView.text = item.title.getHumanName(res)
Markwon.setText(view.descriptionTextView, item.body ?: "")
view.descriptionTextView.movementMethod = null //disable internal link click
view.avatarImageView.loadRoundedImage(item.author.avatarUrl)
view.iconImageView.setImageResource(item.icon.getIcon())
view.dateTextView.text = item.date.humanTime(res)
view.descriptionTextView.visible(item.body != null)
view.iconImageView.visible(item.icon != TargetHeaderIcon.NONE)
bindBadges(item.badges)
with(itemView) {
titleTextView.text = item.title.getHumanName(resources)
Markwon.setText(descriptionTextView, item.body ?: "")
descriptionTextView.movementMethod = null //disable internal link click
avatarImageView.loadRoundedImage(item.author.avatarUrl)
iconImageView.setImageResource(item.icon.getIcon())
dateTextView.text = item.date.humanTime(resources)
descriptionTextView.visible(item.body != null)
iconImageView.visible(item.icon != TargetHeaderIcon.NONE)
bindBadges(item.badges)
}
}
private fun bindBadges(badges: List<TargetBadge>) {
view.commentsTextView.visible(false)
view.commitsTextView.visible(false)
view.upVotesTextView.visible(false)
view.downVotesTextView.visible(false)
val badgeViewsCount = view.badgesContainer.childCount
val textBadgesCount = badges.count { it !is TargetBadge.Icon }
if (textBadgesCount > badgeViewsCount) {
(1..textBadgesCount - badgeViewsCount).forEach {
view.badgesContainer.inflate(R.layout.item_target_badge, true)
with(itemView) {
commentsTextView.visible(false)
commitsTextView.visible(false)
upVotesTextView.visible(false)
downVotesTextView.visible(false)
val badgeViewsCount = badgesContainer.childCount
val textBadgesCount = badges.count { it !is TargetBadge.Icon }
if (textBadgesCount > badgeViewsCount) {
(1..textBadgesCount - badgeViewsCount).forEach {
badgesContainer.inflate(R.layout.item_target_badge, true)
}
}
}
(0 until view.badgesContainer.childCount).forEach { i ->
view.badgesContainer.getChildAt(i).visible(false)
}
(0 until badgesContainer.childCount).forEach { i ->
badgesContainer.getChildAt(i).visible(false)
}
var i = 0
badges.forEach { badge ->
when (badge) {
is TargetBadge.Text -> {
val badgeView = view.badgesContainer.getChildAt(i) as TextView
badgeView.textTextView.text = badge.text
badgeView.textTextView.setTextColor(view.context.color(R.color.colorPrimary))
badgeView.textTextView.setBackgroundColor(view.context.color(R.color.colorPrimaryLight))
badgeView.visible(true)
i++
}
is TargetBadge.Status -> {
val badgeView = view.badgesContainer.getChildAt(i) as TextView
badgeView.textTextView.text = badge.status.getHumanName(view.resources)
val (textColor, bgColor) = badge.status.getBadgeColors(view.context)
badgeView.textTextView.setTextColor(textColor)
badgeView.textTextView.setBackgroundColor(bgColor)
badgeView.visible(true)
i++
}
is TargetBadge.Icon -> {
if (badge.count > 0) {
when (badge.icon) {
TargetBadgeIcon.COMMENTS -> {
view.commentsTextView.text = badge.count.toString()
view.commentsTextView.visible(true)
}
TargetBadgeIcon.COMMITS -> {
view.commitsTextView.text = badge.count.toString()
view.commitsTextView.visible(true)
}
TargetBadgeIcon.UP_VOTES -> {
view.upVotesTextView.text = badge.count.toString()
view.upVotesTextView.visible(true)
}
TargetBadgeIcon.DOWN_VOTES -> {
view.downVotesTextView.text = badge.count.toString()
view.downVotesTextView.visible(true)
var i = 0
badges.forEach { badge ->
when (badge) {
is TargetBadge.Text -> {
val badgeView = badgesContainer.getChildAt(i) as TextView
badgeView.textTextView.text = badge.text
badgeView.textTextView.setTextColor(context.color(R.color.colorPrimary))
badgeView.textTextView.setBackgroundColor(context.color(R.color.colorPrimaryLight))
badgeView.visible(true)
i++
}
is TargetBadge.Status -> {
val badgeView = badgesContainer.getChildAt(i) as TextView
badgeView.textTextView.text = badge.status.getHumanName(resources)
val (textColor, bgColor) = badge.status.getBadgeColors(context)
badgeView.textTextView.setTextColor(textColor)
badgeView.textTextView.setBackgroundColor(bgColor)
badgeView.visible(true)
i++
}
is TargetBadge.Icon -> {
if (badge.count > 0) {
when (badge.icon) {
TargetBadgeIcon.COMMENTS -> {
commentsTextView.text = badge.count.toString()
commentsTextView.visible(true)
}
TargetBadgeIcon.COMMITS -> {
commitsTextView.text = badge.count.toString()
commitsTextView.visible(true)
}
TargetBadgeIcon.UP_VOTES -> {
upVotesTextView.text = badge.count.toString()
upVotesTextView.visible(true)
}
TargetBadgeIcon.DOWN_VOTES -> {
downVotesTextView.text = badge.count.toString()
downVotesTextView.visible(true)
}
}
}
}
......
package ru.terrakok.gitlabclient.ui.global.list
import android.support.v7.widget.RecyclerView
import android.view.View
import android.view.ViewGroup
import com.hannesdorfmann.adapterdelegates3.AdapterDelegate
import kotlinx.android.synthetic.main.item_user_note.view.*
import ru.noties.markwon.Markwon
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.Note
import ru.terrakok.gitlabclient.extension.humanTime
import ru.terrakok.gitlabclient.extension.inflate
import ru.terrakok.gitlabclient.extension.loadRoundedImage
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 18.06.17.
*/
class UserNoteAdapterDelegate(private val clickListener: (Note) -> Unit) : AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
with(items[position]) { this is NoteWithFormattedBody && !this.note.isSystem }
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
ViewHolder(parent.inflate(R.layout.item_user_note))
override fun onBindViewHolder(items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>) =
(viewHolder as ViewHolder).bind(items[position] as NoteWithFormattedBody)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var note: Note
init {
view.setOnClickListener { clickListener(note) }
}
fun bind(data: NoteWithFormattedBody) {
this.note = data.note
with(itemView) {
avatarImageView.loadRoundedImage(note.author.avatarUrl)
titleTextView.text = note.author.name
subtitleTextView.text = note.createdAt.humanTime(context.resources)
Markwon.setText(descriptionTextView, data.body)
}
}
}
}
\ No newline at end of file
......@@ -44,9 +44,9 @@ class MergeRequestFragment : BaseFragment(), MergeRequestInfoFragment.ToolbarCon
}
private inner class MergeRequestPagesAdapter : FragmentPagerAdapter(childFragmentManager) {
override fun getItem(position: Int) = when (position) {
override fun getItem(position: Int): BaseFragment? = when (position) {
0 -> MergeRequestInfoFragment()
1 -> MergeRequestInfoFragment()
1 -> MergeRequestNotesFragment()
else -> null
}
......
......@@ -35,7 +35,7 @@ class MergeRequestInfoFragment : BaseFragment(), MergeRequestInfoView {
val mergeRequest = mrInfo.mr
(parentFragment as? ToolbarConfigurator)
?.setTitle("!${mergeRequest.iid}", mrInfo.project.nameWithNamespace)
?.setTitle("!${mergeRequest.iid}", mrInfo.project.name)
titleTextView.text = mergeRequest.title
// TODO: merge request info (Display action user name for the MERGED/CLOSED states).
......
package ru.terrakok.gitlabclient.ui.mergerequest
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.ProvidePresenter
import com.hannesdorfmann.adapterdelegates3.ListDelegationAdapter
import kotlinx.android.synthetic.main.layout_base_list.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.extension.visible
import ru.terrakok.gitlabclient.presentation.global.NoteWithFormattedBody
import ru.terrakok.gitlabclient.presentation.mergerequest.notes.MergeRequestNotesPresenter
import ru.terrakok.gitlabclient.presentation.mergerequest.notes.MergeRequestNotesView
import ru.terrakok.gitlabclient.toothpick.DI
import ru.terrakok.gitlabclient.ui.global.BaseFragment
import ru.terrakok.gitlabclient.ui.global.list.SimpleDividerDecorator
import ru.terrakok.gitlabclient.ui.global.list.SystemNoteAdapterDelegate
import ru.terrakok.gitlabclient.ui.global.list.UserNoteAdapterDelegate
import toothpick.Toothpick
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 15.02.18.
*/
class MergeRequestNotesFragment : BaseFragment(), MergeRequestNotesView {
override val layoutRes = R.layout.fragment_mr_notes
private val adapter by lazy { NotesAdapter() }
@InjectPresenter
lateinit var presenter: MergeRequestNotesPresenter
@ProvidePresenter
fun providePresenter() =
Toothpick.openScope(DI.MERGE_REQUEST_SCOPE)
.getInstance(MergeRequestNotesPresenter::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
with(recyclerView) {
setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
addItemDecoration(SimpleDividerDecorator(context))
adapter = this@MergeRequestNotesFragment.adapter
}
}
override fun showNotes(notes: List<NoteWithFormattedBody>) {
adapter.setData(notes)
}
override fun showProgress(show: Boolean) {
fullscreenProgressView.visible(show)
}
override fun showMessage(message: String) {
showSnackMessage(message)
}
private inner class NotesAdapter : ListDelegationAdapter<MutableList<Any>>() {
init {
items = mutableListOf()
delegatesManager.addDelegate(UserNoteAdapterDelegate({}))
delegatesManager.addDelegate(SystemNoteAdapterDelegate({}))
}
fun setData(data: List<NoteWithFormattedBody>) {
items.clear()
items.addAll(data)
notifyDataSetChanged()
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/layout_base_list"/>
</FrameLayout>
......@@ -5,14 +5,14 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingBottom="16dp">
android:paddingBottom="16dp"
android:paddingTop="16dp">
<ImageView
android:id="@+id/iconImageView"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="28dp"
android:layout_marginTop="16dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_create_24dp"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment