Commit 1ce956cd authored by Konstantin Tskhovrebov's avatar Konstantin Tskhovrebov 🤖

Merge branch 'feature/move_project_milestones_to_tab' into...

Merge branch 'feature/move_project_milestones_to_tab' into 'feature/move_project_labels_milestones_to_tab'

Move project milestones to tabs.

See merge request terrakok/gitlab-client!173
parents 83f91387 48f2c6ff
......@@ -6,7 +6,6 @@ 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
......@@ -37,7 +36,6 @@ 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
......@@ -187,14 +185,8 @@ 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 ProjectMilestones : SupportAppScreen() {
override fun getFragment() = ProjectMilestonesFragment()
}
object ProjectFiles : SupportAppScreen() {
......
......@@ -2,11 +2,13 @@ package ru.terrakok.gitlabclient.di.provider
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Color
import ru.terrakok.gitlabclient.entity.todo.Todo
import ru.terrakok.gitlabclient.model.data.server.deserializer.ColorDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.DateDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.LocalDateTimeDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.LocalDateDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.TodoDeserializer
import javax.inject.Inject
import javax.inject.Provider
......@@ -18,8 +20,9 @@ class GsonProvider @Inject constructor() : Provider<Gson> {
override fun get(): Gson =
GsonBuilder()
.registerTypeAdapter(LocalDateTime::class.java, DateDeserializer())
.registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeDeserializer())
.registerTypeAdapter(Todo::class.java, TodoDeserializer())
.registerTypeAdapter(Color::class.java, ColorDeserializer())
.registerTypeAdapter(LocalDate::class.java, LocalDateDeserializer())
.create()
}
\ No newline at end of file
package ru.terrakok.gitlabclient.entity.milestone
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
data class Milestone(
......@@ -9,9 +10,10 @@ data class Milestone(
@SerializedName("project_id") val projectId: Long,
@SerializedName("description") val description: String?,
@SerializedName("state") val state: MilestoneState,
@SerializedName("due_date") val dueDate: String?,
@SerializedName("start_date") val startDate: String?,
@SerializedName("due_date") val dueDate: LocalDate?,
@SerializedName("start_date") val startDate: LocalDate?,
@SerializedName("created_at") val createdAt: LocalDateTime?,
@SerializedName("title") val title: String?,
@SerializedName("updated_at") val updatedAt: LocalDateTime?
@SerializedName("updated_at") val updatedAt: LocalDateTime?,
@SerializedName("web_url") val webUrl: String?
)
......@@ -4,6 +4,7 @@ import android.content.Context
import android.content.res.Resources
import androidx.annotation.DrawableRes
import org.threeten.bp.Duration
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.format.DateTimeFormatter
import retrofit2.HttpException
......@@ -13,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.milestone.MilestoneState
import ru.terrakok.gitlabclient.entity.todo.TodoAction
import ru.terrakok.gitlabclient.model.system.ResourceManager
import java.io.IOException
......@@ -53,6 +55,8 @@ fun LocalDateTime.humanTime(resources: Resources): String {
return resources.getString(R.string.time_ago, timeStr)
}
fun LocalDate.humanDate() = format(DATE_FORMAT)
fun EventAction.getHumanName(resources: Resources) = when (this) {
EventAction.UPDATED -> resources.getString(R.string.event_action_updated)
EventAction.REOPENED -> resources.getString(R.string.event_action_reopened)
......@@ -167,4 +171,14 @@ fun TargetBadgeStatus.getBadgeColors(context: Context) = when (this) {
TargetBadgeStatus.OPENED -> Pair(context.color(R.color.green), context.color(R.color.lightGreen))
TargetBadgeStatus.CLOSED -> Pair(context.color(R.color.red), context.color(R.color.lightRed))
TargetBadgeStatus.MERGED -> Pair(context.color(R.color.blue), context.color(R.color.lightBlue))
}
fun MilestoneState.getHumanName(resources: Resources) = when (this) {
MilestoneState.ACTIVE -> resources.getString(R.string.milestone_active)
MilestoneState.CLOSED -> resources.getString(R.string.milestone_closed)
}
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))
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.data.server.deserializer
import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
import java.lang.reflect.Type
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 16.05.19.
*/
class LocalDateDeserializer : JsonDeserializer<LocalDate> {
override fun deserialize(
json: JsonElement,
type: Type,
jsonDeserializationContext: JsonDeserializationContext
): LocalDate {
return LocalDate.parse(json.asJsonPrimitive.asString, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
}
}
\ No newline at end of file
......@@ -11,7 +11,7 @@ import java.util.*
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 27.02.18.
*/
class DateDeserializer : JsonDeserializer<LocalDateTime> {
class LocalDateTimeDeserializer : JsonDeserializer<LocalDateTime> {
private val offset = TimeZone.getDefault().rawOffset / 1000L
override fun deserialize(
......
......@@ -14,7 +14,7 @@ class MilestoneInteractor @Inject constructor(
fun getMilestones(
projectId: Long,
milestoneState: MilestoneState,
milestoneState: MilestoneState?,
page: Int
) = milestoneRepository
.getMilestones(projectId, milestoneState, page)
......
......@@ -39,7 +39,5 @@ class ProjectPresenter @Inject constructor(
fun onBackPressed() = flowRouter.exit()
fun onMilestonesClicked() = flowRouter.navigateTo(Screens.ProjectMilestonesContainer)
fun onFilesPressed() = flowRouter.navigateTo(Screens.ProjectFiles)
}
\ No newline at end of file
......@@ -5,7 +5,6 @@ import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.di.PrimitiveWrapper
import ru.terrakok.gitlabclient.di.ProjectId
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
......@@ -19,10 +18,9 @@ import javax.inject.Inject
@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
private val flowRouter: FlowRouter
) : BasePresenter<ProjectMilestonesView>() {
private val projectId = projectIdWrapper.value
......@@ -34,7 +32,7 @@ class ProjectMilestonesPresenter @Inject constructor(
}
private val paginator = Paginator(
{ milestoneInteractor.getMilestones(projectId, milestoneState, it) },
{ milestoneInteractor.getMilestones(projectId, null, it) },
object : Paginator.ViewController<Milestone> {
override fun showEmptyProgress(show: Boolean) {
viewState.showEmptyProgress(show)
......@@ -70,7 +68,12 @@ class ProjectMilestonesPresenter @Inject constructor(
}
)
fun onMilestoneClick(milestoneId: Long) {} //todo: implement milestone screen.
fun onMilestoneClicked(milestone: Milestone) {
milestone.webUrl?.let {
flowRouter.startFlow(Screens.ExternalBrowserFlow(it))
}
}
fun refreshMilestones() = paginator.refresh()
fun loadNextMilestonesPage() = paginator.loadNewPage()
......
......@@ -7,14 +7,13 @@ import com.hannesdorfmann.adapterdelegates4.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
import ru.terrakok.gitlabclient.extension.*
/**
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
*/
class MilestonesAdapterDelegate(
private val clickListener: (Long) -> Unit
private val clickListener: (Milestone) -> Unit
) : AdapterDelegate<MutableList<Any>>() {
override fun isForViewType(items: MutableList<Any>, position: Int) =
......@@ -31,18 +30,32 @@ class MilestonesAdapterDelegate(
) = (viewHolder as ViewHolder).bind(items[position] as Milestone)
private inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
private lateinit var milestone: Milestone
private lateinit var item: Milestone
init {
//TODO Milestone Flow(uncomment next line when Milestone Flow is ready).
// view.setOnClickListener { clickListener(milestone.id) }
view.setOnClickListener { clickListener(item) }
}
fun bind(data: Milestone) {
this.milestone = data
fun bind(item: Milestone) {
this.item = item
with(itemView) {
titleTextView.text = data.title
dateTextView.text = data.createdAt?.humanTime(resources)
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)
}
}
}
......
......@@ -43,7 +43,6 @@ class MainProjectFragment : BaseFragment(), ProjectView {
setOnMenuItemClickListener { item ->
when (item.itemId) {
R.id.shareAction -> shareText(shareUrl)
R.id.milestonesAction -> presenter.onMilestonesClicked()
R.id.filesAction -> presenter.onFilesPressed()
}
true
......
......@@ -37,15 +37,17 @@ class ProjectInfoContainerFragment : BaseFragment() {
override fun getItem(position: Int): BaseFragment = when (position) {
0 -> Screens.ProjectInfo.fragment
1 -> Screens.ProjectEvents.fragment
else -> Screens.ProjectLabels.fragment
2 -> Screens.ProjectLabels.fragment
else -> Screens.ProjectMilestones.fragment
}
override fun getCount() = 3
override fun getCount() = 4
override fun getPageTitle(position: Int) = when (position) {
0 -> getString(R.string.project_info)
1 -> getString(R.string.project_events)
2 -> getString(R.string.project_labels)
3 -> getString(R.string.project_milestones)
else -> null
}
}
......
......@@ -12,7 +12,7 @@ import ru.terrakok.gitlabclient.ui.global.list.ProgressItem
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
*/
class MilestonesAdapter(
clickListener: (Long) -> Unit,
clickListener: (Milestone) -> Unit,
private val nextPageListener: () -> Unit
) : ListDelegationAdapter<MutableList<Any>>() {
......
package ru.terrakok.gitlabclient.ui.project.milestones
import android.os.Bundle
import androidx.fragment.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
else -> Screens.ProjectMilestones(MilestoneState.CLOSED).fragment
}
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
......@@ -7,14 +7,11 @@ import com.arellomobile.mvp.presenter.ProvidePresenter
import kotlinx.android.synthetic.main.layout_base_list.*
import ru.terrakok.gitlabclient.R
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 toothpick.Scope
import toothpick.config.Module
/**
* @author Valentin Logvinovitch (glvvl) on 17.12.18.
......@@ -23,15 +20,6 @@ class ProjectMilestonesFragment : BaseFragment(), ProjectMilestonesView {
override val layoutRes = R.layout.fragment_project_milestones
override fun installModules(scope: Scope) {
scope.installModules(object : Module() {
init {
bind(MilestoneState::class.java)
.toInstance(arguments!!.getSerializable(ARG_MILESTONE_STATE) as MilestoneState)
}
})
}
@InjectPresenter
lateinit var presenter: ProjectMilestonesPresenter
......@@ -41,19 +29,11 @@ class ProjectMilestonesFragment : BaseFragment(), ProjectMilestonesView {
private val adapter: MilestonesAdapter by lazy {
MilestonesAdapter(
{ presenter.onMilestoneClick(it) },
{ presenter.onMilestoneClicked(it) },
{ presenter.loadNextMilestonesPage() }
)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (arguments?.getSerializable(ARG_MILESTONE_STATE) == null) {
throw IllegalArgumentException("Provide milestone state as args.")
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
......@@ -99,15 +79,4 @@ class ProjectMilestonesFragment : BaseFragment(), ProjectMilestonesView {
override fun showMessage(message: String) {
showSnackMessage(message)
}
companion object {
private const val ARG_MILESTONE_STATE = "arg milestone state"
fun create(milestoneState: MilestoneState) =
ProjectMilestonesFragment().apply {
arguments = Bundle().apply {
putSerializable(ARG_MILESTONE_STATE, milestoneState)
}
}
}
}
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
......@@ -12,7 +13,8 @@
<com.google.android.material.tabs.TabLayout
style="@style/ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content"
app:tabMode="scrollable" />
</androidx.viewpager.widget.ViewPager>
</LinearLayout>
</LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
style="@style/ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="0dp"
app:navigationIcon="@drawable/ic_arrow_back_24dp"
app:title="@string/project_milestones" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.tabs.TabLayout
style="@style/ToolbarStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.viewpager.widget.ViewPager>
</LinearLayout>
\ No newline at end of file
......@@ -12,33 +12,47 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:lines="1"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Konstantin Tskhovrebov" />
tools:text="v 1.5" />
<TextView
android:id="@+id/dateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginTop="4dp"
android:textColor="@color/silver"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="@id/titleTextView"
app:layout_constraintTop_toBottomOf="@+id/titleTextView"
tools:text="29 min ago" />
tools:text="2017-12-31 ― 2017-12-31" />
<TextView
android:id="@+id/stateTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginEnd="4dp"
android:padding="4dp"
android:textSize="14sp"
app:layout_constraintLeft_toLeftOf="@id/dateTextView"
app:layout_constraintTop_toBottomOf="@id/dateTextView"
tools:background="@color/lightRed"
tools:text="Opened"
tools:textColor="@color/red" />
<View
android:layout_width="0dp"
android:layout_height="@dimen/divider_size"
android:layout_marginTop="10dp"
android:layout_marginTop="8dp"
android:background="@color/divider"
app:layout_constraintLeft_toLeftOf="@id/dateTextView"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/dateTextView" />
app:layout_constraintTop_toBottomOf="@id/stateTextView" />
</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
......@@ -14,11 +14,4 @@
android:title="@string/project_files"
app:showAsAction="ifRoom" />
<item
android:id="@+id/milestonesAction"
android:icon="@drawable/ic_milestone"
android:title="@string/menu_project_milestones"
app:iconTint="@android:color/white"
app:showAsAction="ifRoom" />
</menu>
\ No newline at end of file
......@@ -178,6 +178,9 @@
<string name="project_labels">Labels</string>
<string name="project_milestones">Milestones</string>
<string name="project_files">Files</string>
<string name="project_milestone_date">%s ― %s</string>
<string name="milestone_active">Opened</string>
<string name="milestone_closed">Closed</string>
<!--Project files screen-->
<string name="project_files_show_branches">Branches</string>
......
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