Commit 8c54ad70 authored by Ilya Gulya's avatar Ilya Gulya

Merge remote-tracking branch 'trunk/develop' into feature/extend_markdown_support

parents b0dc33df fb2b1ec2
......@@ -57,6 +57,8 @@ android {
)
}
}
vectorDrawables.useSupportLibrary = true
}
signingConfigs {
......@@ -149,9 +151,9 @@ dependencies {
//FlexBox Layout
implementation("com.google.android:flexbox:1.0.0")
//Firebase
implementation("com.google.firebase:firebase-core:16.0.5")
implementation("com.google.firebase:firebase-core:16.0.6")
//Crashlytics
implementation("com.crashlytics.sdk.android:crashlytics:2.9.6")
implementation("com.crashlytics.sdk.android:crashlytics:2.9.8")
//Custom GitLab markdown parsing tools
implementation(project(":markwonx"))
......
......@@ -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
......@@ -27,13 +29,17 @@ import ru.terrakok.gitlabclient.ui.my.todos.MyTodosFragment
import ru.terrakok.gitlabclient.ui.privacypolicy.PrivacyPolicyFragment
import ru.terrakok.gitlabclient.ui.project.ProjectFlowFragment
import ru.terrakok.gitlabclient.ui.project.ProjectFragment
import ru.terrakok.gitlabclient.ui.project.files.ProjectFilesFragment
import ru.terrakok.gitlabclient.ui.project.info.ProjectEventsFragment
import ru.terrakok.gitlabclient.ui.project.info.ProjectInfoContainerFragment
import ru.terrakok.gitlabclient.ui.project.info.ProjectInfoFragment
import ru.terrakok.gitlabclient.ui.project.issues.ProjectIssuesContainerFragment
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
......@@ -155,6 +161,24 @@ object Screens {
override fun getFragment() = ProjectMergeRequestsFragment.create(mrState)
}
object ProjectLabels : SupportAppScreen() {
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()
}
data class UserFlow(
val userId: Long
) : SupportAppScreen() {
......@@ -234,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
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
data class Branch(
@SerializedName("name") val name: String,
@SerializedName("merged") val merged: Boolean,
@SerializedName("protected") val protected: Boolean,
@SerializedName("default") val default: Boolean,
@SerializedName("developers_can_push") val developersCanPush: Boolean,
@SerializedName("developers_can_merge") val developersCanMerge: Boolean,
@SerializedName("can_push") val canPush: Boolean
)
\ No newline at end of file
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
/**
* @author Maxim Myalkin (MaxMyalkin) on 29.10.2018.
*/
data class Label(
@SerializedName("id") val id: Long,
@SerializedName("name") val name: String,
@SerializedName("color") val color: String,
@SerializedName("description") val description: String?,
@SerializedName("open_issues_count") val openIssuesCount: Int,
@SerializedName("closed_issues_count") val closedIssuesCount: Int,
@SerializedName("open_merge_requests_count") val openMergeRequestsCount: Int,
@SerializedName("subscribed") val subscribed: Boolean,
@SerializedName("priority") val priority: Int?
)
\ No newline at end of file
package ru.terrakok.gitlabclient.entity.app
import ru.terrakok.gitlabclient.entity.RepositoryTreeNodeType
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 02.11.18.
*/
data class ProjectFile(
val id: String,
val name: String,
val nodeType: RepositoryTreeNodeType
)
\ No newline at end of file
......@@ -7,6 +7,7 @@ import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.drawable.Drawable
import android.net.Uri
import android.support.annotation.ColorInt
import android.support.annotation.LayoutRes
import android.support.design.widget.Snackbar
import android.support.v4.app.Fragment
......@@ -191,4 +192,9 @@ fun Activity.hideKeyboard() {
}
}
fun Any.objectScopeName() = "${javaClass.simpleName}_${hashCode()}"
\ No newline at end of file
fun Any.objectScopeName() = "${javaClass.simpleName}_${hashCode()}"
fun View.setBackgroundTintByColor(@ColorInt color: Int) {
val wrappedDrawable = DrawableCompat.wrap(background)
DrawableCompat.setTint(wrappedDrawable.mutate(), color)
}
\ No newline at end of file
......@@ -28,6 +28,8 @@ import ru.terrakok.gitlabclient.entity.todo.TodoState
interface GitlabApi {
companion object {
const val API_PATH = "api/v4"
// See GitLab documentation: https://docs.gitlab.com/ee/api/#pagination.
const val MAX_PAGE_SIZE = 100
}
@GET("$API_PATH/projects")
......@@ -63,9 +65,34 @@ interface GitlabApi {
@Path("id") id: Long,
@Query("path") path: String?,
@Query("ref") branchName: String?,
@Query("recursive") recursive: Boolean?
@Query("recursive") recursive: Boolean? = false,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<RepositoryTreeNode>>
@GET("$API_PATH/projects/{project_id}/repository/commits/{sha}")
fun getRepositoryCommit(
@Path("project_id") projectId: Long,
@Path("sha") sha: String,
@Query("stats") stats: Boolean = true
): Single<Commit>
@GET("$API_PATH/projects/{project_id}/repository/commits/")
fun getRepositoryCommits(
@Path("project_id") projectId: Long,
@Query("ref_name") branchName: String?,
@Query("since") since: String?,
@Query("until") until: String?,
@Query("path") path: String?,
@Query("all") all: Boolean?,
@Query("with_stats") withStats: Boolean?
): Single<List<Commit>>
@GET("$API_PATH/projects/{project_id}/repository/branches/")
fun getRepositoryBranches(
@Path("project_id") projectId: Long
): Single<List<Branch>>
@GET("$API_PATH/user")
fun getMyUser(): Single<User>
......@@ -257,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}")
......@@ -296,12 +325,52 @@ 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")
fun getProjectLabels(
@Path("project_id") projectId: Long,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<Label>>
@FormUrlEncoded
@POST("$API_PATH/projects/{project_id}/labels")
fun createLabel(
@Path("project_id") projectId: Long,
@Field("name") name: String,
@Field("color") color: String,
@Field("description") description: String?,
@Field("priority") priority: Int?
): Single<Label>
@FormUrlEncoded
@DELETE("$API_PATH/projects/{project_id}/labels")
fun deleteLabel(
@Path("project_id") projectId: Long,
@Field("name") name: String
): Completable
@POST("$API_PATH/projects/{project_id}/labels/{label_id}/subscribe")
fun subscribeToLabel(
@Path("project_id") projectId: Long,
@Path("label_id") labelId: Long
): Single<Label>
@POST("$API_PATH/projects/{project_id}/labels/{label_id}/unsubscribe")
fun unsubscribeFromLabel(
@Path("project_id") projectId: Long,
@Path("label_id") labelId: Long
): Single<Label>
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.interactor.label
import ru.terrakok.gitlabclient.model.repository.label.LabelRepository
import javax.inject.Inject
/**
* @author Maxim Myalkin (MaxMyalkin) on 30.10.2018.
*/
class LabelInteractor @Inject constructor(
private val labelRepository: LabelRepository
) {
fun getLabelList(
projectId: Long,
page: Int
) = labelRepository.getLabelList(projectId, page)
fun subscribeToLabel(
projectId: Long,
labelId: Long
) = labelRepository.subscribeToLabel(projectId, labelId)
fun unsubscribeFromLabel(
projectId: Long,
labelId: Long
) = labelRepository.unsubscribeFromLabel(projectId, labelId)
}
\ No newline at end of file
......@@ -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
......@@ -50,7 +50,7 @@ class ProjectInteractor @Inject constructor(
val readmePath = project.readmeUrl.substringAfter(
"${project.webUrl}/blob/${project.defaultBranch}/"
)
projectRepository.getFile(project.id, readmePath, project.defaultBranch)
projectRepository.getBlobFile(project.id, readmePath, project.defaultBranch)
} else {
Single.error(ReadmeNotFound())
}
......@@ -59,6 +59,16 @@ class ProjectInteractor @Inject constructor(
.map { file -> base64Tools.decode(file.content) }
.observeOn(schedulers.ui())
fun getProjectFiles(
projectId: Long,
path: String,
branchName: String,
page: Int
) = projectRepository.getProjectFiles(projectId = projectId, path = path, branchName = branchName, page = page)
fun getProjectBranches(
projectId: Long
) = projectRepository.getProjectBranches(projectId)
class ReadmeNotFound : Exception()
}
\ No newline at end of file
......@@ -179,7 +179,7 @@ class IssueRepository @Inject constructor(
private fun getAllIssueNotePages(projectId: Long, issueId: Long, sort: Sort?, orderBy: OrderBy?) =
Observable.range(1, Int.MAX_VALUE)
.concatMap { page ->
api.getIssueNotes(projectId, issueId, sort, orderBy, page, MAX_PAGE_SIZE)
api.getIssueNotes(projectId, issueId, sort, orderBy, page, GitlabApi.MAX_PAGE_SIZE)
.toObservable()
}
.takeWhile { notes -> notes.isNotEmpty() }
......@@ -198,14 +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())
companion object {
// See GitLab documentation: https://docs.gitlab.com/ee/api/#pagination.
private const val MAX_PAGE_SIZE = 100
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.repository.label
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
/**
* @author Maxim Myalkin (MaxMyalkin) on 30.10.2018.
*/
class LabelRepository @Inject constructor(
private val api: GitlabApi,
@DefaultPageSize defaultPageSizeWrapper: PrimitiveWrapper<Int>,
private val schedulers: SchedulersProvider
) {
private val defaultPageSize: Int = defaultPageSizeWrapper.value
fun getLabelList(
projectId: Long,
page: Int
) = api
.getProjectLabels(projectId, page, defaultPageSize)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun createLabel(
projectId: Long,
name: String,
color: String,
description: String?,
priority: Int?
) = api
.createLabel(projectId, name, color, description, priority)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun deleteLabel(
projectId: Long,
name: String
) = api
.deleteLabel(projectId, name)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun subscribeToLabel(
projectId: Long,
labelId: Long
) = api
.subscribeToLabel(projectId, labelId)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun unsubscribeFromLabel(
projectId: Long,
labelId: Long
) = api
.unsubscribeFromLabel(projectId, labelId)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
}
\ No newline at end of file
......@@ -191,7 +191,7 @@ class MergeRequestRepository @Inject constructor(
private fun getAllMergeRequestNotePages(projectId: Long, mergeRequestId: Long, sort: Sort?, orderBy: OrderBy?) =
Observable.range(1, Int.MAX_VALUE)
.concatMap { page ->
api.getMergeRequestNotes(projectId, mergeRequestId, sort, orderBy, page, MAX_PAGE_SIZE)
api.getMergeRequestNotes(projectId, mergeRequestId, sort, orderBy, page, GitlabApi.MAX_PAGE_SIZE)
.toObservable()
}
.takeWhile { notes -> notes.isNotEmpty() }
......@@ -232,7 +232,7 @@ class MergeRequestRepository @Inject constructor(
private fun getAllMergeRequestParticipants(projectId: Long, mergeRequestId: Long) =
Observable.range(1, Int.MAX_VALUE)
.concatMap { page ->
api.getMergeRequestParticipants(projectId, mergeRequestId, page, MAX_PAGE_SIZE)
api.getMergeRequestParticipants(projectId, mergeRequestId, page, GitlabApi.MAX_PAGE_SIZE)
.toObservable()
}
.takeWhile { participants -> participants.isNotEmpty() }
......@@ -247,14 +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())
companion object {
// See GitLab documentation: https://docs.gitlab.com/ee/api/#pagination.
private const val MAX_PAGE_SIZE = 100
}
}
\ 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())
......
package ru.terrakok.gitlabclient.model.repository.project
import io.reactivex.Single
import ru.terrakok.gitlabclient.entity.Branch
import ru.terrakok.gitlabclient.entity.OrderBy
import ru.terrakok.gitlabclient.entity.Sort
import ru.terrakok.gitlabclient.entity.Visibility
import ru.terrakok.gitlabclient.entity.app.ProjectFile
import ru.terrakok.gitlabclient.model.data.server.GitlabApi
import ru.terrakok.gitlabclient.model.system.SchedulersProvider
import ru.terrakok.gitlabclient.toothpick.PrimitiveWrapper
......@@ -53,7 +56,7 @@ class ProjectRepository @Inject constructor(
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun getFile(
fun getBlobFile(
projectId: Long,
path: String,
branchName: String
......@@ -62,13 +65,33 @@ class ProjectRepository @Inject constructor(
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun getRepositoryTree(
fun getProjectFiles(
projectId: Long,
path: String? = null,
branchName: String? = null,
recursive: Boolean? = null
) = api
.getRepositoryTree(projectId, path, branchName, recursive)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
path: String,
branchName: String,
recursive: Boolean? = null,
page: Int,
pageSize: Int = defaultPageSize
): Single<List<ProjectFile>> =
api
.getRepositoryTree(projectId, path, branchName, recursive, page, pageSize)
.map { trees ->
trees.map { tree ->
ProjectFile(
tree.id,
tree.name,
tree.type
)
}
}
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
fun getProjectBranches(
projectId: Long
): Single<List<Branch>> =
api
.getRepositoryBranches(projectId)
.subscribeOn(schedulers.io())
.observeOn(schedulers.ui())
}
\ No newline at end of file
package ru.terrakok.gitlabclient.presentation
import com.arellomobile.mvp.MvpPresenter
import com.arellomobile.mvp.MvpView
import ru.terrakok.cicerone.Router
import ru.terrakok.gitlabclient.Screens
import ru.terrakok.gitlabclient.model.interactor.launch.LaunchInteractor
......@@ -10,14 +8,16 @@ import javax.inject.Inject
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 03.09.18.
*/
class AppPresenter @Inject constructor(
class AppLauncher @Inject constructor(
private val launchInteractor: LaunchInteractor,
private val router: Router
) : MvpPresenter<MvpView>() {
) {
private val isSignedIn = launchInteractor.signInToSession()
fun coldStart() {
val rootScreen =
if (launchInteractor.signInToSession()) Screens.DrawerFlow
if (isSignedIn) Screens.DrawerFlow
else Screens.AuthFlow