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

Merge branch 'feature/right_viewholders' into 'develop'

Apply experimental LayoutContainer for ViewHolders for view caching.

See merge request !232
parents f3c428e2 e908f0b1
......@@ -93,6 +93,10 @@ android {
}
}
androidExtensions {
isExperimental = true
}
dependencies {
val moxyVersion = "1.7.0"
val toothpickVersion = "2.1.0"
......
......@@ -4,7 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_app_library.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_app_library.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.app.develop.AppLibrary
import ru.terrakok.gitlabclient.extension.getHumanName
......@@ -13,7 +14,8 @@ import ru.terrakok.gitlabclient.extension.inflate
/**
* @author Konstantin Tskhovrebov (aka terrakok) on 18.06.17.
*/
class AppLibraryAdapterDelegate(private val clickListener: (AppLibrary) -> Unit) : AdapterDelegate<MutableList<Any>>() {
class AppLibraryAdapterDelegate(private val clickListener: (AppLibrary) -> Unit) :
AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
items[position] is AppLibrary
......@@ -29,19 +31,19 @@ class AppLibraryAdapterDelegate(private val clickListener: (AppLibrary) -> Unit)
) =
(viewHolder as ViewHolder).bind(items[position] as AppLibrary)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
private lateinit var appLibrary: AppLibrary
init {
view.setOnClickListener { clickListener(appLibrary) }
containerView.setOnClickListener { clickListener(appLibrary) }
}
fun bind(appLibrary: AppLibrary) {
this.appLibrary = appLibrary
with(itemView) {
titleTextView.text = appLibrary.name
subtitleTextView.text = appLibrary.license.getHumanName(resources)
}
titleTextView.text = appLibrary.name
subtitleTextView.text = appLibrary.license.getHumanName(subtitleTextView.resources)
}
}
}
\ No newline at end of file
......@@ -4,7 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_assignee.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_assignee.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.extension.inflate
......@@ -31,17 +32,14 @@ class AssigneesAdapterDelegate : AdapterDelegate<MutableList<ShortUser>>() {
payloads: MutableList<Any>
) = (holder as ViewHolder).bind(items[position])
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var item: ShortUser
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(item: ShortUser) {
this.item = item
with(itemView) {
titleTextView.text = item.name
subtitleTextView.text = item.username
avatarImageView.bindShortUser(item)
}
titleTextView.text = item.name
subtitleTextView.text = item.username
avatarImageView.bindShortUser(item)
}
}
}
\ No newline at end of file
......@@ -4,7 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_merge_request_commit.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_merge_request_commit.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.app.CommitWithShortUser
import ru.terrakok.gitlabclient.extension.humanTime
......@@ -32,23 +33,18 @@ class CommitAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
payloads: MutableList<Any>
) = (viewHolder as ViewHolder).bind(items[position] as CommitWithShortUser)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var commitWithShortUser: CommitWithShortUser
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(commitWithShortUser: CommitWithShortUser) {
this.commitWithShortUser = commitWithShortUser
with(itemView) {
val shortUser = commitWithShortUser.shortUser
if (shortUser != null) {
avatarImageView.bindShortUser(shortUser)
}
titleTextView.text = commitWithShortUser.commit.title
descriptionTextView.text = String.format(
context.getString(R.string.merge_request_commits_description),
commitWithShortUser.commit.authorName,
commitWithShortUser.commit.authoredDate.humanTime(resources)
)
}
commitWithShortUser.shortUser?.let { avatarImageView.bindShortUser(it) }
titleTextView.text = commitWithShortUser.commit.title
descriptionTextView.text = String.format(
descriptionTextView.context.getString(R.string.merge_request_commits_description),
commitWithShortUser.commit.authorName,
commitWithShortUser.commit.authoredDate.humanTime(descriptionTextView.resources)
)
}
}
}
\ No newline at end of file
......@@ -4,7 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_member.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_member.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.Member
import ru.terrakok.gitlabclient.extension.inflate
......@@ -21,10 +22,10 @@ class MembersAdapterDelegate(
) : AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
items[position] is Member
items[position] is Member
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
ViewHolder(parent.inflate(R.layout.item_member))
ViewHolder(parent.inflate(R.layout.item_member))
override fun onBindViewHolder(
items: MutableList<Any>,
......@@ -33,36 +34,37 @@ class MembersAdapterDelegate(
payloads: MutableList<Any>
) = (viewHolder as ViewHolder).bind(items[position] as Member)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private val guestTitle = view.context.getString(R.string.members_guest)
private val reporterTitle = view.context.getString(R.string.members_reporter)
private val developerTitle = view.context.getString(R.string.members_developer)
private val maintainerTitle = view.context.getString(R.string.members_maintainer)
private val ownerTitle = view.context.getString(R.string.members_owner)
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
private val guestTitle = containerView.context.getString(R.string.members_guest)
private val reporterTitle = containerView.context.getString(R.string.members_reporter)
private val developerTitle = containerView.context.getString(R.string.members_developer)
private val maintainerTitle = containerView.context.getString(R.string.members_maintainer)
private val ownerTitle = containerView.context.getString(R.string.members_owner)
private lateinit var data: Member
init {
view.setOnClickListener { clickListener(data.id) }
containerView.setOnClickListener { clickListener(data.id) }
}
fun bind(data: Member) {
this.data = data
with(itemView) {
avatarView.bindMember(data)
nameTextView.text = data.name
roleTextView.text = data.accessLevel.accessToString()
}
avatarView.bindMember(data)
nameTextView.text = data.name
roleTextView.text = data.accessLevel.accessToString()
}
private fun Long.accessToString(): String =
when (this) {
10L -> guestTitle
20L -> reporterTitle
30L -> developerTitle
40L -> maintainerTitle
50L -> ownerTitle
else -> throw IllegalArgumentException("You must provide correct value to accessLevel")
}
when (this) {
10L -> guestTitle
20L -> reporterTitle
30L -> developerTitle
40L -> maintainerTitle
50L -> ownerTitle
else -> throw IllegalArgumentException("You must provide correct value to accessLevel")
}
}
}
\ No newline at end of file
......@@ -4,6 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_merge_request_change.*
import kotlinx.android.synthetic.main.item_merge_request_change.view.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestChange
......@@ -38,39 +40,40 @@ class MergeRequestChangeAdapterDelegate(
(viewHolder as ViewHolder).recycle()
}
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
private lateinit var item: MergeRequestChange
init {
view.setOnClickListener { clickListener(item) }
containerView.setOnClickListener { clickListener(item) }
}
fun bind(item: MergeRequestChange) {
this.item = item
with(itemView) {
changePath.text = item.newPath
changeIcon.setImageResource(
when {
item.newFile -> R.drawable.ic_file_added
item.deletedFile -> R.drawable.ic_file_deleted
else -> R.drawable.ic_file_changed
}
)
changeFileName.text = item.newPath.extractFileNameFromPath()
gitDiffView.setGitDiffText(item.diff)
changeAddedCount.text = context.getString(
R.string.merge_request_changes_added_count,
gitDiffView.getAddedLineCount()
)
changeDeletedCount.text = context.getString(
R.string.merge_request_changes_deleted_count,
gitDiffView.getDeletedLineCount()
)
}
changePath.text = item.newPath
changeIcon.setImageResource(
when {
item.newFile -> R.drawable.ic_file_added
item.deletedFile -> R.drawable.ic_file_deleted
else -> R.drawable.ic_file_changed
}
)
changeFileName.text = item.newPath.extractFileNameFromPath()
gitDiffView.setGitDiffText(item.diff)
changeAddedCount.text = changeAddedCount.context.getString(
R.string.merge_request_changes_added_count,
gitDiffView.getAddedLineCount()
)
changeDeletedCount.text = changeAddedCount.context.getString(
R.string.merge_request_changes_deleted_count,
gitDiffView.getDeletedLineCount()
)
}
fun recycle() {
itemView.gitDiffView.release()
containerView.gitDiffView.release()
}
}
}
\ No newline at end of file
......@@ -4,7 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_milestone.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_milestone.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.extension.*
......@@ -32,34 +33,35 @@ class MilestonesAdapterDelegate(
payloads: MutableList<Any>
) = (viewHolder as ViewHolder).bind(items[position] as Milestone)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
private lateinit var item: Milestone
init {
view.setOnClickListener { clickListener(item) }
containerView.setOnClickListener { clickListener(item) }
}
fun bind(item: Milestone) {
this.item = item
with(itemView) {
titleTextView.text = item.title
val startDate = item.startDate
val dueDate = item.dueDate
if (startDate != null && dueDate != null) {
dateTextView.text = String.format(
context.getString(R.string.project_milestone_date),
startDate.humanDate(),
dueDate.humanDate()
)
dateTextView.visible(true)
} else {
dateTextView.visible(false)
}
val (textColor, bgColor) = item.state.getStateColors(context)
stateTextView.setTextColor(textColor)
stateTextView.setBackgroundColor(bgColor)
stateTextView.text = item.state.getHumanName(resources)
titleTextView.text = item.title
val startDate = item.startDate
val dueDate = item.dueDate
if (startDate != null && dueDate != null) {
dateTextView.text = String.format(
dateTextView.context.getString(R.string.project_milestone_date),
startDate.humanDate(),
dueDate.humanDate()
)
dateTextView.visible(true)
} else {
dateTextView.visible(false)
}
val (textColor, bgColor) = item.state.getStateColors(stateTextView.context)
stateTextView.setTextColor(textColor)
stateTextView.setBackgroundColor(bgColor)
stateTextView.text = item.state.getHumanName(stateTextView.resources)
}
}
}
\ No newline at end of file
......@@ -4,6 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_project.*
import kotlinx.android.synthetic.main.item_project.view.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.Project
......@@ -19,7 +21,8 @@ import ru.terrakok.gitlabclient.ui.global.view.custom.bindProject
fun Project.isSame(other: Project) = id == other.id
class ProjectAdapterDelegate(private val clickListener: (Project) -> Unit) : AdapterDelegate<MutableList<Any>>() {
class ProjectAdapterDelegate(private val clickListener: (Project) -> Unit) :
AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
items[position] is Project
......@@ -27,8 +30,18 @@ class ProjectAdapterDelegate(private val clickListener: (Project) -> Unit) : Ada
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder {
val root = parent.inflate(R.layout.item_project)
with(root) {
starsTextView.setStartDrawable(context.getTintDrawable(R.drawable.ic_star_black_24dp, R.color.colorPrimary))
forksTextView.setStartDrawable(context.getTintDrawable(R.drawable.ic_fork, R.color.colorPrimary))
starsTextView.setStartDrawable(
context.getTintDrawable(
R.drawable.ic_star_black_24dp,
R.color.colorPrimary
)
)
forksTextView.setStartDrawable(
context.getTintDrawable(
R.drawable.ic_fork,
R.color.colorPrimary
)
)
}
return ViewHolder(root)
}
......@@ -41,33 +54,35 @@ class ProjectAdapterDelegate(private val clickListener: (Project) -> Unit) : Ada
) =
(viewHolder as ViewHolder).bind(items[position] as Project)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
private lateinit var project: Project
init {
view.setOnClickListener { clickListener(project) }
containerView.setOnClickListener { clickListener(project) }
}
fun bind(project: Project) {
this.project = project
with(itemView) {
titleTextView.text = project.nameWithNamespace
titleTextView.text = project.nameWithNamespace
descriptionTextView.visibility = if (project.description.isNullOrEmpty()) View.GONE else View.VISIBLE
descriptionTextView.text = project.description
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()
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.bindProject(project)
}
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.bindProject(project)
}
}
}
\ No newline at end of file
......@@ -4,7 +4,8 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_project_file.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_project_file.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.RepositoryTreeNodeType
import ru.terrakok.gitlabclient.entity.app.ProjectFile
......@@ -33,24 +34,25 @@ class ProjectFileAdapterDelegate(
payloads: MutableList<Any>
) = (viewHolder as ViewHolder).bind(items[position] as ProjectFile)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
private lateinit var item: ProjectFile
init {
view.setOnClickListener { fileClickListener(item) }
containerView.setOnClickListener { fileClickListener(item) }
}
fun bind(item: ProjectFile) {
this.item = item
with(itemView) {
projectFileIcon.setImageResource(
when (item.nodeType) {
RepositoryTreeNodeType.BLOB -> R.drawable.ic_file
RepositoryTreeNodeType.TREE -> R.drawable.ic_folder
}
)
projectFileName.text = item.name
}
projectFileIcon.setImageResource(
when (item.nodeType) {
RepositoryTreeNodeType.BLOB -> R.drawable.ic_file
RepositoryTreeNodeType.TREE -> R.drawable.ic_folder
}
)
projectFileName.text = item.name
}
}
}
\ No newline at end of file
......@@ -4,10 +4,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_system_note.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_system_note.*
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
......@@ -29,15 +29,13 @@ class SystemNoteAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
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
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(data: NoteWithFormattedBody) {
this.note = data.note
with(itemView) {
titleTextView.text = note.author.name
Markwon.setText(subtitleTextView, data.body)
}
titleTextView.text = data.note.author.name
Markwon.setText(subtitleTextView, data.body)
}
}
}
\ No newline at end of file
......@@ -24,13 +24,8 @@ class TargetHeaderConfidentialAdapterDelegate : AdapterDelegate<MutableList<Any>
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>
) = (viewHolder as ViewHolder).bind(items[position] as TargetHeader.Confidential)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var item: TargetHeader.Confidential
fun bind(item: TargetHeader.Confidential) {
this.item = item
}
) {
}
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view)
}
\ No newline at end of file
......@@ -4,10 +4,10 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.synthetic.main.item_user_note.view.*
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_user_note.*
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.presentation.global.NoteWithFormattedBody
......@@ -31,17 +31,16 @@ class UserNoteAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
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
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
fun bind(data: NoteWithFormattedBody) {
this.note = data.note
with(itemView) {
avatarImageView.bindShortUser(note.author)
titleTextView.text = note.author.name
subtitleTextView.text = note.createdAt.humanTime(context.resources)
Markwon.setText(descriptionTextView, data.body)
}
val note = data.note
avatarImageView.bindShortUser(note.author)
titleTextView.text = note.author.name
subtitleTextView.text = note.createdAt.humanTime(subtitleTextView.context.resources)
Markwon.setText(descriptionTextView, data.body)
}
}
}
\ No newline at end of file
......@@ -7,6 +7,8 @@ import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.recyclerview.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates4.AdapterDelegate
import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.item_label.*
import kotlinx.android.synthetic.main.item_label.view.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.Color
......@@ -39,15 +41,17 @@ class ProjectLabelAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
(holder as ViewHolder).bind(items[position] as Label)
}
private class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private inner class ViewHolder(
override val containerView: View
) : RecyclerView.ViewHolder(containerView), LayoutContainer {
@SuppressLint("SetTextI18n")
fun bind(item: Label) = with(itemView) {
fun bind(item: Label) {
labelTitleTextView.text = item.name
val descriptionIsEmpty = item.description.isNullOrBlank()
labelDescriptionTextView.text = if (descriptionIsEmpty) {
context.getString(R.string.label_description_empty)
labelDescriptionTextView.context.getString(R.string.label_description_empty)
} else {
item.description
}
......@@ -68,10 +72,6 @@ class ProjectLabelAdapterDelegate : AdapterDelegate<MutableList<Any>>() {
setTextColor(textColor)
}
private fun isColorDark(color: Int) = ColorUtils.calculateLuminance(color) < DARK_COLOR_THRESHOLD
companion object {
private const val DARK_COLOR_THRESHOLD = .5f
}
private fun isColorDark(color: Int) = ColorUtils.calculateLuminance(color) < 0.5f
}
}
\ No newline at end of file
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