...
 
Commits (75)
......@@ -16,8 +16,8 @@ android {
minSdkVersion(19)
targetSdkVersion(28)
versionName = "1.5.1"
versionCode = 16
versionName = "1.5.5"
versionCode = 20
buildToolsVersion = "28.0.3"
......@@ -94,7 +94,7 @@ dependencies {
//Support
implementation("androidx.appcompat:appcompat:1.0.2")
implementation("com.google.android.material:material:1.1.0-alpha06")
implementation("com.google.android.material:material:1.1.0-alpha07")
implementation("androidx.cardview:cardview:1.0.0")
implementation("androidx.constraintlayout:constraintlayout:1.1.3")
//Kotlin
......@@ -131,17 +131,17 @@ dependencies {
implementation("ru.noties:markwon:$markwonVersion")
implementation("ru.noties:markwon-image-loader:$markwonVersion")
//Bottom navigation bar
implementation("com.aurelhubert:ahbottomnavigation:2.1.0")
implementation("com.aurelhubert:ahbottomnavigation:2.3.4")
//Lottie
implementation("com.airbnb.android:lottie:2.5.1")
//Date
implementation("com.jakewharton.threetenabp:threetenabp:1.0.5")
implementation("com.jakewharton.threetenabp:threetenabp:1.2.1")
//FlexBox Layout
implementation("com.google.android:flexbox:1.0.0")
//Firebase
implementation("com.google.firebase:firebase-core:16.0.9")
//Crashlytics
implementation("com.crashlytics.sdk.android:crashlytics:2.10.0")
implementation("com.crashlytics.sdk.android:crashlytics:2.10.1")
//JUnit
testImplementation("junit:junit:4.12")
......
......@@ -12,10 +12,7 @@ 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.file.ProjectFileFragment
import ru.terrakok.gitlabclient.ui.issue.IssueFlowFragment
import ru.terrakok.gitlabclient.ui.issue.IssueInfoFragment
import ru.terrakok.gitlabclient.ui.issue.IssueNotesFragment
import ru.terrakok.gitlabclient.ui.issue.MainIssueFragment
import ru.terrakok.gitlabclient.ui.issue.*
import ru.terrakok.gitlabclient.ui.libraries.LibrariesFragment
import ru.terrakok.gitlabclient.ui.main.MainFragment
import ru.terrakok.gitlabclient.ui.mergerequest.*
......@@ -36,6 +33,7 @@ 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.members.ProjectMembersFragment
import ru.terrakok.gitlabclient.ui.project.mergerequest.ProjectMergeRequestsContainerFragment
import ru.terrakok.gitlabclient.ui.project.mergerequest.ProjectMergeRequestsFragment
import ru.terrakok.gitlabclient.ui.project.milestones.ProjectMilestonesFragment
......@@ -193,6 +191,10 @@ object Screens {
override fun getFragment() = ProjectMilestonesFragment()
}
object ProjectMembers : SupportAppScreen() {
override fun getFragment() = ProjectMembersFragment()
}
object ProjectFiles : SupportAppScreen() {
override fun getFragment() = ProjectFilesFragment()
}
......@@ -205,6 +207,10 @@ object Screens {
override fun getFragment() = MainMergeRequestFragment()
}
object MergeRequestDetails : SupportAppScreen() {
override fun getFragment() = MergeRequestDetailsFragment()
}
object MergeRequestInfo : SupportAppScreen() {
override fun getFragment() = MergeRequestInfoFragment()
}
......@@ -229,6 +235,10 @@ object Screens {
override fun getFragment() = IssueInfoFragment()
}
object IssueDetails : SupportAppScreen() {
override fun getFragment() = IssueDetailsFragment()
}
object IssueNotes : SupportAppScreen() {
override fun getFragment() = IssueNotesFragment()
}
......
......@@ -10,16 +10,12 @@ import ru.terrakok.gitlabclient.BuildConfig
import ru.terrakok.gitlabclient.di.*
import ru.terrakok.gitlabclient.di.provider.GsonProvider
import ru.terrakok.gitlabclient.entity.app.develop.AppInfo
import ru.terrakok.gitlabclient.model.data.storage.RawAppData
import ru.terrakok.gitlabclient.model.interactor.app.AppInfoInteractor
import ru.terrakok.gitlabclient.model.repository.app.AppInfoRepository
import ru.terrakok.gitlabclient.model.repository.session.SessionRepository
import ru.terrakok.gitlabclient.model.repository.tools.Base64Tools
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 toothpick.config.Module
/**
......@@ -37,7 +33,6 @@ class AppModule(context: Context) : Module() {
bind(ResourceManager::class.java).singletonInScope()
bind(Base64Tools::class.java).toInstance(Base64Tools())
bind(AssetManager::class.java).toInstance(context.assets)
bind(RawAppData::class.java)
bind(SystemMessageNotifier::class.java).toInstance(SystemMessageNotifier())
bind(Gson::class.java).toProvider(GsonProvider::class.java).providesSingletonInScope()
......@@ -60,9 +55,5 @@ class AppModule(context: Context) : Module() {
BuildConfig.FEEDBACK_URL
)
)
bind(AppInfoRepository::class.java)
bind(AppInfoInteractor::class.java)
bind(AppLauncher::class.java).singletonInScope()
}
}
\ No newline at end of file
......@@ -13,22 +13,7 @@ import ru.terrakok.gitlabclient.entity.app.session.OAuthParams
import ru.terrakok.gitlabclient.entity.app.session.UserAccount
import ru.terrakok.gitlabclient.model.data.cache.ProjectCache
import ru.terrakok.gitlabclient.model.data.server.GitlabApi
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
import ru.terrakok.gitlabclient.model.repository.user.UserRepository
import ru.terrakok.gitlabclient.model.data.state.AccountMainBadgesStateModel
import ru.terrakok.gitlabclient.presentation.global.ErrorHandler
import ru.terrakok.gitlabclient.presentation.global.MarkDownConverter
import toothpick.config.Module
......@@ -56,7 +41,6 @@ class ServerModule(userAccount: UserAccount?) : Module() {
bind(ProjectCache::class.java).singletonInScope()
bind(GitlabApi::class.java).toProvider(ApiProvider::class.java).providesSingletonInScope()
bind(MarkDownConverter::class.java).toProvider(MarkDownConverterProvider::class.java).providesSingletonInScope()
bind(MarkDownUrlResolver::class.java)
//Auth
bind(OAuthParams::class.java).toInstance(
......@@ -70,35 +54,7 @@ class ServerModule(userAccount: UserAccount?) : Module() {
//Error handler with logout logic
bind(ErrorHandler::class.java).singletonInScope()
//Profile
bind(ProfileRepository::class.java)
//Project
bind(ProjectRepository::class.java)
bind(ProjectInteractor::class.java)
//Issue
bind(IssueRepository::class.java)
bind(IssueInteractor::class.java)
//Event
bind(EventRepository::class.java)
bind(EventInteractor::class.java)
//Merge request
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)
//Todos
bind(TodoRepository::class.java)
bind(TodoListInteractor::class.java)
//Account badges
bind(AccountMainBadgesStateModel::class.java).toInstance(AccountMainBadgesStateModel())
}
}
\ No newline at end of file
......@@ -3,13 +3,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 org.threeten.bp.ZonedDateTime
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.LocalDateTimeDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.LocalDateDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.TodoDeserializer
import ru.terrakok.gitlabclient.model.data.server.deserializer.ZonedDateTimeDeserializer
import javax.inject.Inject
import javax.inject.Provider
......@@ -20,9 +20,9 @@ class GsonProvider @Inject constructor() : Provider<Gson> {
override fun get(): Gson =
GsonBuilder()
.registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeDeserializer())
.registerTypeAdapter(Todo::class.java, TodoDeserializer())
.registerTypeAdapter(Color::class.java, ColorDeserializer())
.registerTypeAdapter(LocalDate::class.java, LocalDateDeserializer())
.registerTypeAdapter(ZonedDateTime::class.java, ZonedDateTimeDeserializer())
.create()
}
\ No newline at end of file
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZonedDateTime
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 20.10.18.
......@@ -12,11 +12,11 @@ data class Commit(
@SerializedName("title") val title: String,
@SerializedName("author_name") val authorName: String,
@SerializedName("author_email") val authorEmail: String?,
@SerializedName("authored_date") val authoredDate: LocalDateTime,
@SerializedName("authored_date") val authoredDate: ZonedDateTime,
@SerializedName("commiter_name") val commiterName: String?,
@SerializedName("commiter_email") val commiterEmail: String?,
@SerializedName("commited_date") val commitedDate: LocalDateTime?,
@SerializedName("created_at") val createdAt: LocalDateTime,
@SerializedName("commited_date") val commitedDate: ZonedDateTime?,
@SerializedName("created_at") val createdAt: ZonedDateTime,
@SerializedName("message") val message: String,
@SerializedName("parent_ids") val parentIds: List<String>
)
\ No newline at end of file
......@@ -2,11 +2,13 @@ package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
data class Author(
data class Member(
@SerializedName("id") val id: Long,
@SerializedName("state") val state: String?,
@SerializedName("web_url") val webUrl: String?,
@SerializedName("username") val username: String,
@SerializedName("name") val name: String,
@SerializedName("state") val state: String?,
@SerializedName("avatar_url") val avatarUrl: String?,
@SerializedName("username") val username: String
)
@SerializedName("web_url") val webUrl: String?,
@SerializedName("expires_at") val expiresDate: String?,
@SerializedName("access_level") val accessLevel: Long
)
\ No newline at end of file
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.event.EventTargetType
data class Note(
@SerializedName("id") val id: Long,
@SerializedName("body") val body: String,
@SerializedName("author") val author: Author,
@SerializedName("created_at") val createdAt: LocalDateTime,
@SerializedName("updated_at") val updatedAt: LocalDateTime?,
@SerializedName("author") val author: ShortUser,
@SerializedName("created_at") val createdAt: ZonedDateTime,
@SerializedName("updated_at") val updatedAt: ZonedDateTime?,
@SerializedName("system") val isSystem: Boolean,
@SerializedName("noteable_id") val noteableId: Long,
@SerializedName("noteable_type") val noteableType: EventTargetType?,
......
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZonedDateTime
data class Owner(
@SerializedName("id") val id: Long,
@SerializedName("name") val name: String,
@SerializedName("username") val username: String,
@SerializedName("created_at") val createdAt: LocalDateTime?
@SerializedName("created_at") val createdAt: ZonedDateTime?
)
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZonedDateTime
data class Project(
@SerializedName("id") val id: Long,
......@@ -24,8 +24,8 @@ data class Project(
@SerializedName("wiki_enabled") val wikiEnabled: Boolean,
@SerializedName("snippets_enabled") val snippetsEnabled: Boolean,
@SerializedName("container_registry_enabled") val containerRegistryEnabled: Boolean,
@SerializedName("created_at") val createdAt: LocalDateTime?,
@SerializedName("last_activity_at") val lastActivityAt: LocalDateTime?,
@SerializedName("created_at") val createdAt: ZonedDateTime?,
@SerializedName("last_activity_at") val lastActivityAt: ZonedDateTime?,
@SerializedName("creator_id") val creatorId: Long,
@SerializedName("namespace") val namespace: Namespace?,
@SerializedName("permissions") val permissions: Permissions?,
......
......@@ -2,7 +2,7 @@ package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
data class Assignee(
data class ShortUser(
@SerializedName("id") val id: Long,
@SerializedName("state") val state: String?,
@SerializedName("name") val name: String,
......
package ru.terrakok.gitlabclient.entity.target
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
......
package ru.terrakok.gitlabclient.entity
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZonedDateTime
data class User(
@SerializedName("id") val id: Long,
......@@ -11,7 +11,7 @@ data class User(
@SerializedName("state") val state: String?,
@SerializedName("avatar_url") val avatarUrl: String?,
@SerializedName("web_url") val webUrl: String?,
@SerializedName("created_at") val createdAt: LocalDateTime,
@SerializedName("created_at") val createdAt: ZonedDateTime,
@SerializedName("is_admin") val isAdmin: Boolean,
@SerializedName("bio") val bio: String?,
@SerializedName("location") val location: String?,
......@@ -20,11 +20,11 @@ data class User(
@SerializedName("twitter") val twitter: String?,
@SerializedName("website_url") val websiteUrl: String?,
@SerializedName("organization") val organization: String?,
@SerializedName("last_sign_in_at") val lastSignInAt: LocalDateTime,
@SerializedName("confirmed_at") val confirmedAt: LocalDateTime,
@SerializedName("last_sign_in_at") val lastSignInAt: ZonedDateTime,
@SerializedName("confirmed_at") val confirmedAt: ZonedDateTime,
@SerializedName("color_scheme_id") val colorSchemeId: Long,
@SerializedName("projects_limit") val projectsLimit: Long,
@SerializedName("current_sign_in_at") val currentSignInAt: LocalDateTime,
@SerializedName("current_sign_in_at") val currentSignInAt: ZonedDateTime,
@SerializedName("identities") val identities: List<Identity>?,
@SerializedName("can_create_group") val canCreateGroup: Boolean,
@SerializedName("can_create_project") val canCreateProject: Boolean,
......
package ru.terrakok.gitlabclient.entity.app
data class AccountMainBadges(
val issueCount: Int,
val mergeRequestCount: Int,
val todoCount: Int
)
\ No newline at end of file
package ru.terrakok.gitlabclient.entity.app
import ru.terrakok.gitlabclient.entity.Commit
import ru.terrakok.gitlabclient.entity.ShortUser
/**
* Created by Eugene Shapovalov (@CraggyHaggy) on 20.10.18.
*/
data class CommitWithAvatarUrl(val commit: Commit, val authorAvatarUrl: String?)
\ No newline at end of file
data class CommitWithShortUser(val commit: Commit, val shortUser: ShortUser?)
\ No newline at end of file
package ru.terrakok.gitlabclient.entity.app.target
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Author
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.ShortUser
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 24.12.17.
*/
sealed class TargetHeader {
data class Public(
val author: Author,
val author: ShortUser,
val icon: TargetHeaderIcon,
val title: TargetHeaderTitle,
val body: CharSequence,
val date: LocalDateTime,
val date: ZonedDateTime,
val target: AppTarget,
val targetId: Long,
val internal: TargetInternal?,
......
package ru.terrakok.gitlabclient.entity.event
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Author
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.Note
import ru.terrakok.gitlabclient.entity.PushData
import ru.terrakok.gitlabclient.entity.ShortUser
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 22.07.17.
......@@ -18,8 +17,8 @@ data class Event(
@SerializedName("target_type") val targetType: EventTargetType?,
@SerializedName("author_id") val authorId: Long,
@SerializedName("target_title") val targetTitle: String?,
@SerializedName("created_at") val createdAt: LocalDateTime,
@SerializedName("author") val author: Author,
@SerializedName("created_at") val createdAt: ZonedDateTime,
@SerializedName("author") val author: ShortUser,
@SerializedName("author_username") val authorUsername: String,
@SerializedName("push_data") val pushData: PushData?,
@SerializedName("note") val note: Note?
......
package ru.terrakok.gitlabclient.entity.issue
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Assignee
import ru.terrakok.gitlabclient.entity.Author
import org.threeten.bp.LocalDate
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.TimeStats
import ru.terrakok.gitlabclient.entity.milestone.Milestone
data class Issue(
......@@ -11,16 +12,17 @@ data class Issue(
@SerializedName("iid") val iid: Long,
@SerializedName("state") val state: IssueState,
@SerializedName("description") val description: String,
@SerializedName("author") val author: Author,
@SerializedName("author") val author: ShortUser,
@SerializedName("milestone") val milestone: Milestone?,
@SerializedName("project_id") val projectId: Long,
@SerializedName("assignees") val assignees: List<Assignee>,
@SerializedName("updated_at") val updatedAt: LocalDateTime?,
// Assignees can be null in MergeRequest, so assume it can be null too.
@SerializedName("assignees") val assignees: List<ShortUser>?,
@SerializedName("updated_at") val updatedAt: ZonedDateTime?,
@SerializedName("title") val title: String?,
@SerializedName("created_at") val createdAt: LocalDateTime,
@SerializedName("created_at") val createdAt: ZonedDateTime,
@SerializedName("labels") val labels: List<String>,
@SerializedName("user_notes_count") val userNotesCount: Int,
@SerializedName("due_date") val dueDate: String?,
@SerializedName("due_date") val dueDate: LocalDate?,
@SerializedName("web_url") val webUrl: String?,
@SerializedName("confidential") val confidential: Boolean,
@SerializedName("upvotes") val upvotes: Int,
......@@ -28,8 +30,12 @@ data class Issue(
// The closed_by attribute was introduced in GitLab 10.6.
// This value will only be present for issues which were closed after GitLab 10.6 and
// when the user account that closed the issue still exists.
@SerializedName("closed_by") val closedBy: Author?,
@SerializedName("closed_at") val closedAt: LocalDateTime?,
@SerializedName("closed_by") val closedBy: ShortUser?,
@SerializedName("closed_at") val closedAt: ZonedDateTime?,
// The merge_requests_count attribute was introduced in GitLab 11.9.
@SerializedName("merge_requests_count") val relatedMergeRequestCount: Int
@SerializedName("merge_requests_count") val relatedMergeRequestCount: Int,
@SerializedName("time_stats") val timeStats: TimeStats,
@SerializedName("weight") val weight: Int?,
@SerializedName("discussion_locked") val discussionLocked: Boolean,
@SerializedName("assignee") val assignee: ShortUser?
)
\ No newline at end of file
package ru.terrakok.gitlabclient.entity.mergerequest
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Author
import ru.terrakok.gitlabclient.entity.User
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.TimeStats
import ru.terrakok.gitlabclient.entity.milestone.Milestone
data class MergeRequest(
@SerializedName("id") val id: Long,
@SerializedName("iid") val iid: Long,
@SerializedName("created_at") val createdAt: LocalDateTime,
@SerializedName("updated_at") val updatedAt: LocalDateTime?,
@SerializedName("created_at") val createdAt: ZonedDateTime,
@SerializedName("updated_at") val updatedAt: ZonedDateTime?,
@SerializedName("target_branch") val targetBranch: String,
@SerializedName("source_branch") val sourceBranch: String,
@SerializedName("project_id") val projectId: Long,
......@@ -18,29 +18,32 @@ data class MergeRequest(
@SerializedName("state") val state: MergeRequestState,
@SerializedName("upvotes") val upvotes: Int,
@SerializedName("downvotes") val downvotes: Int,
@SerializedName("author") val author: Author,
@SerializedName("assignee") val assignee: User?,
@SerializedName("author") val author: ShortUser,
@SerializedName("assignee") val assignee: ShortUser?,
@SerializedName("source_project_id") val sourceProjectId: Int,
@SerializedName("target_project_id") val targetProjectId: Int,
@SerializedName("description") val description: String,
@SerializedName("work_in_progress") val workInProgress: Boolean,
@SerializedName("milestone") val milestone: Milestone?,
@SerializedName("merge_when_pipeline_succeeds") val mergeWhenPipelineSucceeds: Boolean,
@SerializedName("merge_status") val mergeStatus: String?,
@SerializedName("merge_status") val mergeStatus: MergeRequestMergeStatus,
@SerializedName("sha") val sha: String,
@SerializedName("merge_commit_sha") val mergeCommitSha: String?,
@SerializedName("user_notes_count") val userNotesCount: Int,
@SerializedName("should_remove_source_branch") val shouldRemoveSourceBranch: Boolean,
@SerializedName("force_remove_source_branch") val forceRemoveSourceBranch: Boolean,
@SerializedName("web_url") val webUrl: String?,
@SerializedName("time_stats") val timeStats: MergeRequestTimeStats?,
@SerializedName("labels") val labels: List<String>,
// The closed_by attribute was introduced in GitLab 10.6.
// This value will only be present for merge requests which were closed/merged after GitLab 10.6
// and when the user account that closed/merged the issue still exists.
@SerializedName("closed_by") val closedBy: Author?,
@SerializedName("closed_at") val closedAt: LocalDateTime?,
@SerializedName("merged_by") val mergedBy: Author?,
@SerializedName("merged_at") val mergedAt: LocalDateTime?,
@SerializedName("changes") val changes: List<MergeRequestChange>?
@SerializedName("closed_by") val closedBy: ShortUser?,
@SerializedName("closed_at") val closedAt: ZonedDateTime?,
@SerializedName("merged_by") val mergedBy: ShortUser?,
@SerializedName("merged_at") val mergedAt: ZonedDateTime?,
@SerializedName("changes") val changes: List<MergeRequestChange>?,
// It sometimes can be null.
@SerializedName("assignees") val assignees: List<ShortUser>?,
@SerializedName("time_stats") val timeStats: TimeStats,
@SerializedName("discussion_locked") val discussionLocked: Boolean
)
package ru.terrakok.gitlabclient.entity.mergerequest
import com.google.gson.annotations.SerializedName
data class MergeRequestTimeStats(
@SerializedName("time_estimate") val timeEstimate: Int,
@SerializedName("total_time_spent") val totalTimeSpent: Int,
@SerializedName("human_time_estimate") val humanTimeEstimate: String?,
@SerializedName("human_total_time_spent") val humanTotalTimeSpent: String?
)
\ No newline at end of file
......@@ -2,7 +2,7 @@ package ru.terrakok.gitlabclient.entity.milestone
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDate
import org.threeten.bp.LocalDateTime
import org.threeten.bp.ZonedDateTime
data class Milestone(
@SerializedName("id") val id: Long,
......@@ -12,8 +12,8 @@ data class Milestone(
@SerializedName("state") val state: MilestoneState,
@SerializedName("due_date") val dueDate: LocalDate?,
@SerializedName("start_date") val startDate: LocalDate?,
@SerializedName("created_at") val createdAt: LocalDateTime?,
@SerializedName("created_at") val createdAt: ZonedDateTime?,
@SerializedName("title") val title: String?,
@SerializedName("updated_at") val updatedAt: LocalDateTime?,
@SerializedName("updated_at") val updatedAt: ZonedDateTime?,
@SerializedName("web_url") val webUrl: String?
)
package ru.terrakok.gitlabclient.entity.target
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Assignee
import ru.terrakok.gitlabclient.entity.Author
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.TimeStats
import ru.terrakok.gitlabclient.entity.milestone.Milestone
/**
......@@ -21,19 +21,19 @@ abstract class Target {
@SerializedName("state")
private val _state: TargetState? = null
@SerializedName("updated_at")
val updatedAt: LocalDateTime? = null
val updatedAt: ZonedDateTime? = null
@SerializedName("created_at")
val createdAt: LocalDateTime? = null
val createdAt: ZonedDateTime? = null
@SerializedName("labels")
private val _labels: List<String>? = null
@SerializedName("milestone")
val milestone: Milestone? = null
@SerializedName("assignees")
private val _assignees: List<Assignee>? = null
private val _assignees: List<ShortUser>? = null
@SerializedName("author")
val _author: Author? = null
val _author: ShortUser? = null
@SerializedName("assignee")
val assignee: Assignee? = null
val assignee: ShortUser? = null
@SerializedName("user_notes_count")
private val _userNotesCount: Int? = null
@SerializedName("upvotes")
......@@ -53,7 +53,6 @@ abstract class Target {
val title get() = _title!!
val state get() = _state!!
val labels get() = _labels!!
val assignees get() = _assignees!!
val userNotesCount get() = _userNotesCount!!
val upVotes get() = _upVotes!!
val downVotes get() = _downVotes!!
......
package ru.terrakok.gitlabclient.entity.target.issue
import com.google.gson.annotations.SerializedName
import org.threeten.bp.LocalDate
import ru.terrakok.gitlabclient.entity.Links
import ru.terrakok.gitlabclient.entity.target.Target
......@@ -9,7 +10,7 @@ import ru.terrakok.gitlabclient.entity.target.Target
*/
class Issue : Target() {
@SerializedName("due_date")
val dueDate: String? = null
val dueDate: LocalDate? = null
@SerializedName("confidential")
private val _confidential: Boolean? = null
@SerializedName("weight")
......
package ru.terrakok.gitlabclient.entity.todo
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Author
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.Project
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.target.Target
import ru.terrakok.gitlabclient.entity.target.TargetType
......@@ -12,12 +12,12 @@ import ru.terrakok.gitlabclient.entity.target.TargetType
data class Todo(
val id: Long,
val project: Project,
val author: Author,
val author: ShortUser,
val actionName: TodoAction,
val targetType: TargetType,
val target: Target,
val targetUrl: String,
val body: String,
val state: TodoState,
val createdAt: LocalDateTime
val createdAt: ZonedDateTime
)
\ No newline at end of file
......@@ -9,6 +9,7 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.TextUtils
import android.text.style.ForegroundColorSpan
import android.view.LayoutInflater
import android.view.View
......@@ -19,12 +20,12 @@ import android.widget.ImageView
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes
import androidx.appcompat.widget.Toolbar
import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.google.android.material.snackbar.Snackbar
import retrofit2.adapter.rxjava2.Result
import ru.terrakok.cicerone.Navigator
import ru.terrakok.cicerone.android.support.SupportAppScreen
import ru.terrakok.cicerone.commands.BackTo
......@@ -137,20 +138,6 @@ fun Fragment.sendEmail(email: String?) {
}
}
fun ImageView.loadRoundedImage(
url: String?,
ctx: Context? = null
) {
Glide.with(ctx ?: context)
.load(url)
.apply(RequestOptions().apply {
placeholder(R.drawable.default_img)
error(R.drawable.default_img)
})
.apply(RequestOptions.circleCropTransform())
.into(this)
}
fun TargetHeader.Public.openInfo(router: FlowRouter) {
when (target) {
AppTarget.PROJECT -> {
......@@ -218,4 +205,21 @@ fun Any.objectScopeName() = "${javaClass.simpleName}_${hashCode()}"
fun View.setBackgroundTintByColor(@ColorInt color: Int) {
val wrappedDrawable = DrawableCompat.wrap(background)
DrawableCompat.setTint(wrappedDrawable.mutate(), color)
}
fun Toolbar.setTitleEllipsize(ellipsize: TextUtils.TruncateAt) {
val fakeTitle = "fakeTitle"
title = fakeTitle
for(i in 0..childCount) {
val child = getChildAt(i)
if (child is TextView && child.text == fakeTitle) {
child.ellipsize = ellipsize
break
}
}
title = ""
}
fun Result<*>.getXTotalHeader(): Int {
return if (!isError) response().headers().get("X-Total")?.toInt() ?: 0 else 0
}
\ No newline at end of file
......@@ -5,7 +5,7 @@ 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.ZonedDateTime
import org.threeten.bp.format.DateTimeFormatter
import retrofit2.HttpException
import ru.terrakok.gitlabclient.R
......@@ -14,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.mergerequest.MergeRequestMergeStatus
import ru.terrakok.gitlabclient.entity.milestone.MilestoneState
import ru.terrakok.gitlabclient.entity.todo.TodoAction
import ru.terrakok.gitlabclient.model.system.ResourceManager
......@@ -41,8 +42,11 @@ fun Throwable.userMessage(resourceManager: ResourceManager) = when (this) {
}
private val DATE_FORMAT = DateTimeFormatter.ofPattern("dd MMM yyyy")
fun LocalDateTime.humanTime(resources: Resources): String {
val delta = Duration.between(this, LocalDateTime.now()).seconds
fun ZonedDateTime.humanTime(resources: Resources): String {
val delta = Duration.between(this, ZonedDateTime.now())
.seconds
.let { maxOf(0, it) }
val timeStr =
when {
delta < 60 -> resources.getString(R.string.time_sec, delta)
......@@ -55,7 +59,7 @@ fun LocalDateTime.humanTime(resources: Resources): String {
return resources.getString(R.string.time_ago, timeStr)
}
fun LocalDate.humanDate() = format(DATE_FORMAT)
fun LocalDate.humanDate(): String = format(DATE_FORMAT)
fun EventAction.getHumanName(resources: Resources) = when (this) {
EventAction.UPDATED -> resources.getString(R.string.event_action_updated)
......@@ -132,7 +136,9 @@ fun TargetHeaderTitle.getHumanName(resources: Resources) = when (this) {
when (action) {
TodoAction.ASSIGNED -> {
"$author $actionName $targetName ${resources.getString(R.string.at)} $projectName ${resources.getString(R.string.to)} $assignee"
"$author $actionName $targetName ${resources.getString(R.string.at)} $projectName ${resources.getString(
R.string.to
)} $assignee"
}
TodoAction.DIRECTLY_ADDRESSED,
TodoAction.MENTIONED -> {
......@@ -186,4 +192,10 @@ fun MilestoneState.getHumanName(resources: Resources) = when (this) {
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))
}
fun MergeRequestMergeStatus.getHumanName(resources: Resources) = when (this) {
MergeRequestMergeStatus.CANNOT_BE_MERGED -> resources.getString(R.string.merge_request_status_cannot_be_merged)
MergeRequestMergeStatus.CAN_BE_MERGED -> resources.getString(R.string.merge_request_status_can_be_merged)
MergeRequestMergeStatus.UNCHECKED -> resources.getString(R.string.merge_request_status_unchecked)
}
\ No newline at end of file
......@@ -2,7 +2,9 @@ package ru.terrakok.gitlabclient.model.data.server
import io.reactivex.Completable
import io.reactivex.Single
import org.threeten.bp.LocalDateTime
import org.threeten.bp.LocalDate
import org.threeten.bp.ZonedDateTime
import retrofit2.adapter.rxjava2.Result
import retrofit2.http.*
import ru.terrakok.gitlabclient.entity.*
import ru.terrakok.gitlabclient.entity.event.Event
......@@ -26,6 +28,7 @@ import ru.terrakok.gitlabclient.entity.todo.TodoState
* @author Konstantin Tskhovrebov (aka terrakok). Date: 28.03.17
*/
interface GitlabApi {
companion object {
const val API_PATH = "api/v4"
// See GitLab documentation: https://docs.gitlab.com/ee/api/#pagination.
......@@ -162,8 +165,8 @@ interface GitlabApi {
@Query("milestone") milestone: String?,
@Query("view") viewType: MergeRequestViewType?,
@Query("labels") labels: String?,
@Query("created_before") createdBefore: LocalDateTime?,
@Query("created_after") createdAfter: LocalDateTime?,
@Query("created_before") createdBefore: ZonedDateTime?,
@Query("created_after") createdAfter: ZonedDateTime?,
@Query("scope") scope: MergeRequestScope?,
@Query("author_id") authorId: Int?,
@Query("assignee_id") assigneeId: Int?,
......@@ -181,8 +184,8 @@ interface GitlabApi {
@Query("milestone") milestone: String?,
@Query("view") viewType: MergeRequestViewType?,
@Query("labels") labels: String?,
@Query("created_before") createdBefore: LocalDateTime?,
@Query("created_after") createdAfter: LocalDateTime?,
@Query("created_before") createdBefore: ZonedDateTime?,
@Query("created_after") createdAfter: ZonedDateTime?,
@Query("scope") scope: MergeRequestScope?,
@Query("author_id") authorId: Int?,
@Query("assignee_id") assigneeId: Int?,
......@@ -273,7 +276,7 @@ interface GitlabApi {
@Path("merge_request_id") mergeRequestId: Long,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<Author>>
): Single<List<ShortUser>>
@GET("$API_PATH/projects/{project_id}/merge_requests/{merge_request_id}/changes")
fun getMergeRequestChanges(
......@@ -297,23 +300,23 @@ interface GitlabApi {
@FormUrlEncoded
@POST("$API_PATH/projects/{project_id}/milestones")
fun createMileStone(
fun createMilestone(
@Path("project_id") projectId: Long,
@Field("title") title: String,
@Field("description") description: String?,
@Field("due_date") dueDate: String?,
@Field("start_date") startDate: String?
@Field("due_date") dueDate: LocalDate?,
@Field("start_date") startDate: LocalDate?
): Single<Milestone>
@FormUrlEncoded
@PUT("$API_PATH/projects/{project_id}/milestones/{milestone_id}")
fun updateMileStone(
fun updateMilestone(
@Path("project_id") projectId: Long,
@Path("milestone_id") mileStoneId: Long,
@Field("title") title: String?,
@Field("description") description: String?,
@Field("due_date") dueDate: String?,
@Field("start_date") startDate: String?
@Field("due_date") dueDate: LocalDate?,
@Field("start_date") startDate: LocalDate?
): Single<Milestone>
@DELETE("$API_PATH/projects/{project_id}/milestones/{milestone_id}")
......@@ -373,4 +376,59 @@ interface GitlabApi {
@Path("project_id") projectId: Long,
@Path("label_id") labelId: Long
): Single<Label>
@HEAD("$API_PATH/merge_requests")
fun getMyAssignedMergeRequestHeaders(
@Query("scope") scope: MergeRequestScope = MergeRequestScope.ASSIGNED_TO_ME,
@Query("state") state: MergeRequestState = MergeRequestState.OPENED,
@Query("per_page") pageSize: Int = 1
): Single<Result<Void>>
@HEAD("$API_PATH/issues")
fun getMyAssignedIssueHeaders(
@Query("scope") scope: IssueScope = IssueScope.ASSIGNED_BY_ME,
@Query("state") state: IssueState = IssueState.OPENED,
@Query("per_page") pageSize: Int = 1
): Single<Result<Void>>
@HEAD("$API_PATH/todos")
fun getMyAssignedTodoHeaders(
@Query("state") state: TodoState = TodoState.PENDING,
@Query("per_page") pageSize: Int = 1
): Single<Result<Void>>
@GET("$API_PATH/projects/{project_id}/members")
fun getMembers(
@Path("project_id") projectId: Long,
@Query("page") page: Int,
@Query("per_page") pageSize: Int
): Single<List<Member>>
@GET("$API_PATH/projects/{project_id}/members/{member_id}")
fun getMember(
@Path("project_id") projectId: Long,
@Path("member_id") memberId: Long
): Single<Member>
@POST("$API_PATH/projects/{project_id}/members")
fun addMember(
@Path("project_id") projectId: Long,
@Field("user_id") userId: Long,
@Field("access_level") accessLevel: Long,
@Field("expires_at") expiresDate: String?
): Completable
@PUT("$API_PATH/projects/{project_id}/members/{user_id}")
fun editMember(
@Path("project_id") projectId: Long,
@Path("user_id") userId: Long,
@Field("access_level") accessLevel: Long,
@Field("expires_at") expiresDate: String?
): Completable
@DELETE("$API_PATH/projects/{project_id}/members/{user_id}")
fun deleteMember(
@Path("project_id") projectId: Long,
@Path("user_id") userId: Long
): Completable
}
\ No newline at end of file
......@@ -12,11 +12,11 @@ import java.lang.reflect.Type
*/
class LocalDateDeserializer : JsonDeserializer<LocalDate> {
private val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd")
override fun deserialize(
json: JsonElement,
type: Type,
jsonDeserializationContext: JsonDeserializationContext
): LocalDate {
return LocalDate.parse(json.asJsonPrimitive.asString, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
}
): LocalDate = LocalDate.parse(json.asJsonPrimitive.asString, dateTimeFormatter)
}
\ No newline at end of file
......@@ -4,9 +4,9 @@ import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import org.threeten.bp.LocalDateTime
import ru.terrakok.gitlabclient.entity.Author
import org.threeten.bp.ZonedDateTime
import ru.terrakok.gitlabclient.entity.Project
import ru.terrakok.gitlabclient.entity.ShortUser
import ru.terrakok.gitlabclient.entity.target.Target
import ru.terrakok.gitlabclient.entity.target.TargetType
import ru.terrakok.gitlabclient.entity.target.issue.Issue
......@@ -34,7 +34,7 @@ class TodoDeserializer : JsonDeserializer<Todo> {
Todo(
jsonObject.get("id").asLong,
context.deserialize<Project>(jsonObject.get("project"), Project::class.java),
context.deserialize<Author>(jsonObject.get("author"), Author::class.java),
context.deserialize<ShortUser>(jsonObject.get("author"), ShortUser::class.java),
context.deserialize<TodoAction>(
jsonObject.get("action_name"),
TodoAction::class.java
......@@ -51,7 +51,10 @@ class TodoDeserializer : JsonDeserializer<Todo> {
jsonObject.get("target_url").asString,
jsonObject.get("body").asString,
context.deserialize<TodoState>(jsonObject.get("state"), TodoState::class.java),
context.deserialize<LocalDateTime>(jsonObject.get("created_at"), LocalDateTime::class.java)
context.deserialize<ZonedDateTime>(
jsonObject.get("created_at"),
ZonedDateTime::class.java
)
)
} else {
throw JsonParseException("Configure Gson in GsonProvider.")
......
......@@ -3,22 +3,14 @@ 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.LocalDateTime
import org.threeten.bp.ZonedDateTime
import java.lang.reflect.Type
import java.util.*
/**
* Created by Konstantin Tskhovrebov (aka @terrakok) on 27.02.18.
*/
class LocalDateTimeDeserializer : JsonDeserializer<LocalDateTime> {
private val offset = TimeZone.getDefault().rawOffset / 1000L
class ZonedDateTimeDeserializer : JsonDeserializer<ZonedDateTime> {
override fun deserialize(
json: JsonElement,
type: Type,
jsonDeserializationContext: JsonDeserializationContext
): LocalDateTime {
return ZonedDateTime.parse(json.asJsonPrimitive.asString).plusSeconds(offset).toLocalDateTime()
}
typeOfT: Type,
context: JsonDeserializationContext?
): ZonedDateTime = ZonedDateTime.parse(json.asJsonPrimitive.asString)
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.data.state
import com.jakewharton.rxrelay2.BehaviorRelay
import io.reactivex.Observable
import ru.terrakok.gitlabclient.entity.app.AccountMainBadges
class AccountMainBadgesStateModel {
private val relay = BehaviorRelay.create<AccountMainBadges>()
val observable: Observable<AccountMainBadges> = relay.hide()
fun accept(value: AccountMainBadges) {
relay.accept(value)
}
fun acceptIssueCount(value: Int) {
relay.value?.let { current ->
relay.accept(current.copy(issueCount = value))
}
}
fun acceptMrCount(value: Int) {
relay.value?.let { current ->
relay.accept(current.copy(mergeRequestCount = value))
}
}
fun acceptTodoCount(value: Int) {
relay.value?.let { current ->
relay.accept(current.copy(todoCount = value))
}
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.interactor.account
import io.reactivex.Observable
import ru.terrakok.gitlabclient.entity.app.AccountMainBadges
import ru.terrakok.gitlabclient.model.repository.account.AccountRepository
import javax.inject.Inject
class AccountInteractor @Inject constructor(
private val accountRepository: AccountRepository
) {
fun getAccountMainBadges(): Observable<AccountMainBadges> =
accountRepository.getAccountMainBadges()
}
\ No newline at end of file
......@@ -25,12 +25,15 @@ class LaunchInteractor @Inject constructor(
}
}
fun signInToSession(): Boolean {
val account = sessionRepository.getCurrentUserAccount()
Toothpick.closeScope(DI.SERVER_SCOPE)
Toothpick
.openScopes(DI.APP_SCOPE, DI.SERVER_SCOPE)
.installModules(ServerModule(account))
return account != null
val hasAccount: Boolean
get() = sessionRepository.getCurrentUserAccount() != null
fun signInToLastSession() {
if (!Toothpick.isScopeOpen(DI.SERVER_SCOPE)) {
val account = sessionRepository.getCurrentUserAccount()
Toothpick
.openScopes(DI.APP_SCOPE, DI.SERVER_SCOPE)
.installModules(ServerModule(account))
}
}
}
\ No newline at end of file
package ru.terrakok.gitlabclient.model.interactor.members
import ru.terrakok.gitlabclient.model.repository.members.MembersRepository
import javax.inject.Inject
/**
* @author Valentin Logvinovitch (glvvl) on 27.02.19.
*/
class MembersInteractor @Inject constructor(
private val membersRepository: MembersRepository
) {
fun getMembers(projectId: Long, page: Int) =
membersRepository.getMembers(projectId, page)
fun getMember(projectId: Long, memberId: Long) =
membersRepository.getMember(projectId, memberId)
fun addMember(projectId: Long, userId: Long, accessLevel: Long) =