Commit db67ff5e authored by Eugene Shapovalov's avatar Eugene Shapovalov 💬

Merge with develop

parents f8807813 8579d0f8
......@@ -6,10 +6,12 @@ import android.net.Uri
import ru.terrakok.cicerone.android.support.SupportAppScreen
import ru.terrakok.gitlabclient.entity.issue.IssueState
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestState
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.ui.about.AboutFragment
import ru.terrakok.gitlabclient.ui.auth.AuthFlowFragment
import ru.terrakok.gitlabclient.ui.auth.AuthFragment
import ru.terrakok.gitlabclient.ui.drawer.DrawerFlowFragment
import ru.terrakok.gitlabclient.ui.global.StubFragment
import ru.terrakok.gitlabclient.ui.issue.IssueFlowFragment
import ru.terrakok.gitlabclient.ui.issue.IssueFragment
import ru.terrakok.gitlabclient.ui.issue.IssueInfoFragment
......@@ -36,6 +38,8 @@ import ru.terrakok.gitlabclient.ui.project.issues.ProjectIssuesFragment
import ru.terrakok.gitlabclient.ui.project.labels.ProjectLabelsFragment
import ru.terrakok.gitlabclient.ui.project.mergerequest.ProjectMergeRequestsContainerFragment
import ru.terrakok.gitlabclient.ui.project.mergerequest.ProjectMergeRequestsFragment
import ru.terrakok.gitlabclient.ui.project.milestones.ProjectMilestonesContainerFragment
import ru.terrakok.gitlabclient.ui.project.milestones.ProjectMilestonesFragment
import ru.terrakok.gitlabclient.ui.projects.ProjectsContainerFragment
import ru.terrakok.gitlabclient.ui.projects.ProjectsListFragment
import ru.terrakok.gitlabclient.ui.user.UserFlowFragment
......@@ -161,6 +165,16 @@ object Screens {
override fun getFragment() = ProjectLabelsFragment()
}
object ProjectMilestonesContainer : SupportAppScreen() {
override fun getFragment() = ProjectMilestonesContainerFragment()
}
data class ProjectMilestones(
val milestoneState: MilestoneState
) : SupportAppScreen() {
override fun getFragment() = ProjectMilestonesFragment.create(milestoneState)
}
object ProjectFiles : SupportAppScreen() {
override fun getFragment() = ProjectFilesFragment()
}
......@@ -244,4 +258,11 @@ object Screens {
text
)
}
data class MilestoneFlow(
val milestoneId: Long
) : SupportAppScreen() {
//todo: implement milestone flow.
override fun getFragment() = StubFragment()
}
}
\ No newline at end of file
......@@ -284,7 +284,9 @@ interface GitlabApi {
@GET("$API_PATH/projects/{project_id}/milestones")
fun getMilestones(
@Path("project_id") projectId: Long,
@Query("state") state: MilestoneState?
@Query("state") state: MilestoneState?,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<Milestone>>
@GET("$API_PATH/projects/{project_id}/milestones/{milestone_id}")
......@@ -323,13 +325,17 @@ interface GitlabApi {
@GET("$API_PATH/projects/{project_id}/milestones/{milestone_id}/issues")
fun getMilestoneIssues(
@Path("project_id") projectId: Long,
@Path("milestone_id") mileStoneId: Long
@Path("milestone_id") mileStoneId: Long,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<Issue>>
@GET("$API_PATH/projects/{project_id}/milestones/{milestone_id}/merge_requests")
fun getMilestoneMergeRequests(
@Path("project_id") projectId: Long,
@Path("milestone_id") mileStoneId: Long
@Path("milestone_id") mileStoneId: Long,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<MergeRequest>>
@GET("$API_PATH/projects/{project_id}/labels")
......
......@@ -14,21 +14,16 @@ class MilestoneInteractor @Inject constructor(
fun getMilestones(
projectId: Long,
milestoneState: MilestoneState
milestoneState: MilestoneState,
page: Int
) = milestoneRepository
.getMilestones(
projectId = projectId,
state = milestoneState
)
.getMilestones(projectId, milestoneState, page)
fun getMilestone(
projectId: Long,
milestoneId: Long
) = milestoneRepository
.getMilestone(
projectId = projectId,
milestoneId = milestoneId
)
.getMilestone(projectId, milestoneId)
fun createMilestone(
projectId: Long,
......@@ -37,13 +32,7 @@ class MilestoneInteractor @Inject constructor(
dueDate: String?,
startDate: String?
) = milestoneRepository
.createMilestone(
projectId = projectId,
title = title,
description = description,
dueDate = dueDate,
startDate = startDate
)
.createMilestone(projectId, title, description, dueDate, startDate)
fun updateMilestone(
projectId: Long,
......@@ -53,14 +42,7 @@ class MilestoneInteractor @Inject constructor(
dueDate: String?,
startDate: String?
) = milestoneRepository
.updateMilestone(
projectId = projectId,
milestoneId = milestoneId,
title = title,
description = description,
dueDate = dueDate,
startDate = startDate
)
.updateMilestone(projectId, milestoneId, title, description, dueDate, startDate)
fun deleteMilestone(
projectId: Long,
......@@ -69,11 +51,13 @@ class MilestoneInteractor @Inject constructor(
fun getAssignedIssues(
projectId: Long,
milestoneId: Long
) = issueRepository.getMilestoneIssues(projectId, milestoneId)
milestoneId: Long,
page: Int
) = issueRepository.getMilestoneIssues(projectId, milestoneId, page)
fun getAssignedMergeRequests(
projectId: Long,
milestoneId: Long
) = mergeRequestRepository.getMilestoneMergeRequests(projectId, milestoneId)
milestoneId: Long,
page: Int
) = mergeRequestRepository.getMilestoneMergeRequests(projectId, milestoneId, page)
}
\ No newline at end of file
......@@ -198,9 +198,11 @@ class IssueRepository @Inject constructor(
fun getMilestoneIssues(
projectId: Long,
milestoneId: Long
) = api
.getMilestoneIssues(projectId, milestoneId)
milestoneId: Long,
page: Int,
pageSize: Int = defaultPageSize
): Single<List<Issue>> = api
.getMilestoneIssues(projectId, milestoneId, page, pageSize)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
}
\ No newline at end of file
......@@ -247,9 +247,11 @@ class MergeRequestRepository @Inject constructor(
fun getMilestoneMergeRequests(
projectId: Long,
milestoneId: Long
) = api
.getMilestoneMergeRequests(projectId, milestoneId)
milestoneId: Long,
page: Int,
pageSize: Int = defaultPageSize
): Single<List<MergeRequest>> = api
.getMilestoneMergeRequests(projectId, milestoneId, page, pageSize)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
}
\ No newline at end of file
......@@ -6,19 +6,24 @@ import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.model.data.server.GitlabApi
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
import ru.terrakok.gitlabclient.toothpick.PrimitiveWrapper
import ru.terrakok.gitlabclient.toothpick.qualifier.DefaultPageSize
import javax.inject.Inject
class MilestoneRepository @Inject constructor(
private val api: GitlabApi,
private val schedulers: SchedulersProvider
private val schedulers: SchedulersProvider,
@DefaultPageSize private val defaultPageSizeWrapper: PrimitiveWrapper<Int>
) {
private val defaultPageSize = defaultPageSizeWrapper.value
fun getMilestones(
projectId: Long,
state: MilestoneState? = null
state: MilestoneState? = null,
page: Int,
pageSize: Int = defaultPageSize
): Single<List<Milestone>> = api
.getMilestones(
projectId, state
)
.getMilestones(projectId, state, page, pageSize)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
......
......@@ -41,5 +41,7 @@ class ProjectPresenter @Inject constructor(
fun onLabelPressed() = flowRouter.navigateTo(Screens.ProjectLabels)
fun onMilestonesClicked() = flowRouter.navigateTo(Screens.ProjectMilestonesContainer)
fun onFilesPressed() = flowRouter.navigateTo(Screens.ProjectFiles)
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.project.milestones
import com.arellomobile.mvp.InjectViewState
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.model.interactor.milestone.MilestoneInteractor
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.Paginator
import ru.terrakok.gitlabclient.toothpick.PrimitiveWrapper
import ru.terrakok.gitlabclient.toothpick.qualifier.ProjectId
import javax.inject.Inject
/**
* @author Valentin Logvinovitch (glvvl) on 24.11.18.
*/
@InjectViewState
class ProjectMilestonesPresenter @Inject constructor(
@ProjectId private val projectIdWrapper: PrimitiveWrapper<Long>,
private val milestoneState: MilestoneState,
private val milestoneInteractor: MilestoneInteractor,
private val errorHandler: ErrorHandler,
private val router: FlowRouter
) : BasePresenter<ProjectMilestonesView>() {
private val projectId = projectIdWrapper.value
override fun onFirstViewAttach() {
super.onFirstViewAttach()
refreshMilestones()
}
private val paginator = Paginator(
{ milestoneInteractor.getMilestones(projectId, milestoneState, it) },
object : Paginator.ViewController<Milestone> {
override fun showEmptyProgress(show: Boolean) {
viewState.showEmptyProgress(show)
}
override fun showEmptyError(show: Boolean, error: Throwable?) {
if (error != null) {
errorHandler.proceed(error, { viewState.showEmptyError(show, it) })
} else {
viewState.showEmptyError(show, null)
}
}
override fun showErrorMessage(error: Throwable) {
errorHandler.proceed(error, { viewState.showMessage(it) })
}
override fun showEmptyView(show: Boolean) {
viewState.showEmptyView(show)
}
override fun showData(show: Boolean, data: List<Milestone>) {
viewState.showMilestones(show, data)
}
override fun showRefreshProgress(show: Boolean) {
viewState.showRefreshProgress(show)
}
override fun showPageProgress(show: Boolean) {
viewState.showPageProgress(show)
}
}
)
fun onMilestoneClick(milestoneId: Long) = router.startFlow(Screens.MilestoneFlow(milestoneId))
fun refreshMilestones() = paginator.refresh()
fun loadNextMilestonesPage() = paginator.loadNewPage()
override fun onDestroy() {
super.onDestroy()
paginator.release()
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation.project.milestones
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.milestone.Milestone
/**
* @author Valentin Logvinovitch (glvvl) on 24.11.18.
*/
@StateStrategyType(AddToEndSingleStrategy::class)
interface ProjectMilestonesView : MvpView {
fun showRefreshProgress(show: Boolean)
fun showEmptyProgress(show: Boolean)
fun showPageProgress(show: Boolean)
fun showEmptyView(show: Boolean)
fun showEmptyError(show: Boolean, message: String?)
fun showMilestones(show: Boolean, milestones: List<Milestone>)
@StateStrategyType(OneExecutionStateStrategy::class)
fun showMessage(message: String)
}
\ No newline at end of file
......@@ -17,6 +17,7 @@ import ru.terrakok.gitlabclient.model.system.AppSchedulers
import ru.terrakok.gitlabclient.model.system.ResourceManager
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
import ru.terrakok.gitlabclient.model.system.message.SystemMessageNotifier
import ru.terrakok.gitlabclient.presentation.AppLauncher
import ru.terrakok.gitlabclient.toothpick.PrimitiveWrapper
import ru.terrakok.gitlabclient.toothpick.provider.GsonProvider
import ru.terrakok.gitlabclient.toothpick.qualifier.AppDevelopersPath
......@@ -65,5 +66,7 @@ class AppModule(context: Context) : Module() {
)
bind(AppInfoRepository::class.java)
bind(AppInfoInteractor::class.java)
bind(AppLauncher::class.java).singletonInScope()
}
}
\ No newline at end of file
......@@ -11,12 +11,14 @@ import ru.terrakok.gitlabclient.model.data.server.MarkDownUrlResolver
import ru.terrakok.gitlabclient.model.interactor.event.EventInteractor
import ru.terrakok.gitlabclient.model.interactor.issue.IssueInteractor
import ru.terrakok.gitlabclient.model.interactor.mergerequest.MergeRequestInteractor
import ru.terrakok.gitlabclient.model.interactor.milestone.MilestoneInteractor
import ru.terrakok.gitlabclient.model.interactor.project.ProjectInteractor
import ru.terrakok.gitlabclient.model.interactor.todo.TodoListInteractor
import ru.terrakok.gitlabclient.model.interactor.user.UserInteractor
import ru.terrakok.gitlabclient.model.repository.event.EventRepository
import ru.terrakok.gitlabclient.model.repository.issue.IssueRepository
import ru.terrakok.gitlabclient.model.repository.mergerequest.MergeRequestRepository
import ru.terrakok.gitlabclient.model.repository.milestone.MilestoneRepository
import ru.terrakok.gitlabclient.model.repository.profile.ProfileRepository
import ru.terrakok.gitlabclient.model.repository.project.ProjectRepository
import ru.terrakok.gitlabclient.model.repository.todo.TodoRepository
......@@ -87,6 +89,10 @@ class ServerModule(userAccount: UserAccount?) : Module() {
bind(MergeRequestRepository::class.java)
bind(MergeRequestInteractor::class.java)
//Milestone
bind(MilestoneRepository::class.java)
bind(MilestoneInteractor::class.java)
//User info
bind(UserRepository::class.java)
bind(UserInteractor::class.java)
......
package ru.terrakok.gitlabclient.ui.global
import android.os.Bundle
import ru.terrakok.cicerone.Router
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.toothpick.DI
import toothpick.Toothpick
import javax.inject.Inject
......@@ -10,14 +11,16 @@ import javax.inject.Inject
* Created by Konstantin Tskhovrebov (aka @terrakok) on 05.09.18.
*/
class StubFragment : BaseFragment() {
override val parentScopeName = DI.APP_SCOPE
override val layoutRes = R.layout.fragment_stub
@Inject
lateinit var router: FlowRouter
lateinit var router: Router
override fun onCreate(savedInstanceState: Bundle?) {
Toothpick.inject(this, scope)
super.onCreate(savedInstanceState)
Toothpick.inject(this, scope)
}
override fun onBackPressed() {
......
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_milestone.view.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.extension.humanTime
import ru.terrakok.gitlabclient.extension.inflate
/**
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
*/
class MilestonesAdapterDelegate(
private val clickListener: (Long) -> Unit
) : AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
items[position] is Milestone
override fun onCreateViewHolder(parent: ViewGroup): RecyclerView.ViewHolder =
ViewHolder(parent.inflate(R.layout.item_milestone))
override fun onBindViewHolder(
items: MutableList<Any>,
position: Int,
viewHolder: RecyclerView.ViewHolder,
payloads: MutableList<Any>
) = (viewHolder as ViewHolder).bind(items[position] as Milestone)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var milestone: Milestone
init {
//TODO Milestone Flow(uncomment next line when Milestone Flow is ready).
// view.setOnClickListener { clickListener(milestone.id) }
}
fun bind(data: Milestone) {
this.milestone = data
with(itemView) {
titleTextView.text = data.title
dateTextView.text = data.createdAt?.humanTime(resources)
}
}
}
}
\ No newline at end of file
......@@ -19,6 +19,7 @@ import ru.terrakok.gitlabclient.ui.global.BaseFragment
* Created by Eugene Shapovalov (@CraggyHaggy) on 10.02.18.
*/
class ProjectFragment : BaseFragment(), ProjectView {
override val layoutRes: Int = R.layout.fragment_project
private val currentTabFragment: BaseFragment?
......@@ -30,7 +31,8 @@ class ProjectFragment : BaseFragment(), ProjectView {
lateinit var presenter: ProjectPresenter
@ProvidePresenter
fun providePresenter() = scope.getInstance(ProjectPresenter::class.java)
fun providePresenter(): ProjectPresenter =
scope.getInstance(ProjectPresenter::class.java)
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
......@@ -42,6 +44,7 @@ class ProjectFragment : BaseFragment(), ProjectView {
when (item.itemId) {
R.id.shareAction -> shareText(shareUrl)
R.id.labelAction -> presenter.onLabelPressed()
R.id.milestonesAction -> presenter.onMilestonesClicked()
R.id.filesAction -> presenter.onFilesPressed()
}
true
......
package ru.terrakok.gitlabclient.ui.project.milestones
import android.support.v7.util.DiffUtil
import android.support.v7.widget.RecyclerView
import com.hannesdorfmann.adapterdelegates3.ListDelegationAdapter
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.ui.global.list.MilestonesAdapterDelegate
import ru.terrakok.gitlabclient.ui.global.list.ProgressAdapterDelegate
import ru.terrakok.gitlabclient.ui.global.list.ProgressItem
/**
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
*/
class MilestonesAdapter(
clickListener: (Long) -> Unit,
private val nextPageListener: () -> Unit
) : ListDelegationAdapter<MutableList<Any>>() {
init {
items = mutableListOf()
delegatesManager.addDelegate(MilestonesAdapterDelegate(clickListener))
delegatesManager.addDelegate(ProgressAdapterDelegate())
}
fun setData(events: List<Milestone>) {
val oldData = items.toList()
val progress = isProgress()
items.clear()
items.addAll(events)
if (progress) items.add(ProgressItem())
//yes, on main thread...
DiffUtil
.calculateDiff(DiffCallback(items, oldData), false)
.dispatchUpdatesTo(this)
}
fun showProgress(isVisible: Boolean) {
val oldData = items.toList()
val currentProgress = isProgress()
if (isVisible && !currentProgress) {
items.add(ProgressItem())
notifyItemInserted(items.lastIndex)
} else if (!isVisible && currentProgress) {
items.remove(items.last())
notifyItemRemoved(oldData.lastIndex)
}
}
private fun isProgress() = items.isNotEmpty() && items.last() is ProgressItem
override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: MutableList<Any?>
) {
super.onBindViewHolder(holder, position, payloads)
if (position == items.size - 10) nextPageListener()
}
private inner class DiffCallback(
private val newItems: List<Any>,
private val oldItems: List<Any>
) : DiffUtil.Callback() {
override fun getOldListSize() = oldItems.size
override fun getNewListSize() = newItems.size
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return if (newItem is Milestone && oldItem is Milestone) {
newItem.id == oldItem.id && newItem.createdAt == oldItem.createdAt
} else {
newItem is ProgressItem && oldItem is ProgressItem
}
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
val oldItem = oldItems[oldItemPosition]
val newItem = newItems[newItemPosition]
return if (newItem is Milestone && oldItem is Milestone) {
newItem == oldItem
} else {
true
}
}
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.ui.project.milestones
import android.os.Bundle
import android.support.v4.app.FragmentPagerAdapter
import kotlinx.android.synthetic.main.fragment_my_merge_requests_container.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.model.system.flow.FlowRouter
import ru.terrakok.gitlabclient.ui.global.BaseFragment
import toothpick.Toothpick
import javax.inject.Inject
/**
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
*/
class ProjectMilestonesContainerFragment : BaseFragment() {
override val layoutRes = R.layout.fragment_project_milestones_container
@Inject
lateinit var router: FlowRouter
private val adapter: ProjectMilestonesPagesAdapter by lazy { ProjectMilestonesPagesAdapter() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Toothpick.inject(this, scope)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
viewPager.adapter = adapter
toolbar.setNavigationOnClickListener { onBackPressed() }
}
private inner class ProjectMilestonesPagesAdapter :
FragmentPagerAdapter(childFragmentManager) {
override fun getItem(position: Int) = when (position) {
0 -> Screens.ProjectMilestones(MilestoneState.ACTIVE).fragment
1 -> Screens.ProjectMilestones(MilestoneState.CLOSED).fragment
else -> null
}
override fun getCount() = 2
override fun getPageTitle(position: Int) = when (position) {
0 -> getString(R.string.target_status_opened)
1 -> getString(R.string.target_status_closed)
else -> null
}
}
override fun onBackPressed() {
router.exit()
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.ui.project.milestones
import android.os.Bundle
import android.support.v7.widget.LinearLayoutManager
import com.arellomobile.mvp.presenter.InjectPresenter
import com.arellomobile.mvp.presenter.ProvidePresenter
import kotlinx.android.synthetic.main.layout_base_list.*
import kotlinx.android.synthetic.main.layout_zero.*
import ru.terrakok.gitlabclient.R
import ru.terrakok.gitlabclient.entity.app.target.TargetHeader
import ru.terrakok.gitlabclient.entity.mergerequest.MergeRequestState
import ru.terrakok.gitlabclient.entity.milestone.Milestone
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.extension.showSnackMessage
import ru.terrakok.gitlabclient.extension.visible
import ru.terrakok.gitlabclient.presentation.project.milestones.ProjectMilestonesPresenter
import ru.terrakok.gitlabclient.presentation.project.milestones.ProjectMilestonesView
import ru.terrakok.gitlabclient.ui.global.BaseFragment
import ru.terrakok.gitlabclient.ui.global.ZeroViewHolder
import ru.terrakok.gitlabclient.ui.my.TargetsAdapter
import toothpick.Scope
import toothpick.config.Module
/**
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
*/