From 0470524f6df78d4b6bfda37bcfc0b2fa6aae1f93 Mon Sep 17 00:00:00 2001 From: k0shk0sh Date: Tue, 27 Aug 2019 14:47:45 +0200 Subject: [PATCH] display pullrequest --- .../di/modules/ActivityBindingModule.kt | 2 + .../di/modules/FragmentBindingModule.kt | 2 + .../github/di/modules/FragmentModule.kt | 6 + .../github/di/modules/ViewModelModule.kt | 4 + .../platform/extension/ClickExtenion.kt | 5 +- .../modules/issue/fragment/IssueFragment.kt | 2 +- .../issuesprs/BaseIssuePrTimelineFragment.kt | 25 +- .../ui/modules/pr/PullRequestActivity.kt | 3 +- .../pr/fragment/PullRequestFragment.kt | 217 ++++++++++++++++ .../viewmodel/PullRequestTimelineViewModel.kt | 222 ++++++++++++++++ .../profile/fragment/ProfileFragment.kt | 11 +- .../usecase/issuesprs/BaseTimelineUseCase.kt | 212 +++++++++++++++ .../issuesprs/CreateIssueCommentUseCase.kt | 3 +- .../issuesprs/GetIssueTimelineUseCase.kt | 242 ++---------------- .../issuesprs/GetPullRequestUseCase.kt | 3 +- .../src/main/graphql/github/fragments.graphql | 201 +++++++++++++-- .../main/graphql/github/issues_prs.graphql | 173 ++----------- .../fastaccess/data/model/TimelineModel.kt | 4 +- .../data/persistence/db/FastHubDatabase.kt | 2 +- .../persistence/models/PullRequestModel.kt | 4 +- .../PullRequestRepositoryProvider.kt | 3 +- 21 files changed, 929 insertions(+), 417 deletions(-) create mode 100644 app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt create mode 100644 app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/viewmodel/PullRequestTimelineViewModel.kt create mode 100644 app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt diff --git a/app/src/main/java/com/fastaccess/github/di/modules/ActivityBindingModule.kt b/app/src/main/java/com/fastaccess/github/di/modules/ActivityBindingModule.kt index 3815683e..cabe3bbb 100644 --- a/app/src/main/java/com/fastaccess/github/di/modules/ActivityBindingModule.kt +++ b/app/src/main/java/com/fastaccess/github/di/modules/ActivityBindingModule.kt @@ -8,6 +8,7 @@ import com.fastaccess.github.ui.modules.issue.IssueActivity import com.fastaccess.github.ui.modules.issuesprs.edit.EditIssuePrActivity import com.fastaccess.github.ui.modules.main.MainActivity import com.fastaccess.github.ui.modules.multipurpose.MultiPurposeActivity +import com.fastaccess.github.ui.modules.pr.PullRequestActivity import com.fastaccess.github.ui.modules.profile.ProfileActivity import com.fastaccess.github.ui.modules.trending.TrendingActivity import dagger.Module @@ -34,4 +35,5 @@ abstract class ActivityBindingModule { @PerActivity @ContributesAndroidInjector abstract fun editorActivity(): EditorActivity @PerActivity @ContributesAndroidInjector abstract fun editIssuePrActivity(): EditIssuePrActivity @PerActivity @ContributesAndroidInjector abstract fun commentActivity(): CommentActivity + @PerActivity @ContributesAndroidInjector abstract fun pullRequestActivity(): PullRequestActivity } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/di/modules/FragmentBindingModule.kt b/app/src/main/java/com/fastaccess/github/di/modules/FragmentBindingModule.kt index e6277ec0..9b35a446 100644 --- a/app/src/main/java/com/fastaccess/github/di/modules/FragmentBindingModule.kt +++ b/app/src/main/java/com/fastaccess/github/di/modules/FragmentBindingModule.kt @@ -17,6 +17,7 @@ import com.fastaccess.github.ui.modules.main.fragment.MainFragment import com.fastaccess.github.ui.modules.notifications.NotificationPagerFragment import com.fastaccess.github.ui.modules.notifications.fragment.read.AllNotificationsFragment import com.fastaccess.github.ui.modules.notifications.fragment.unread.UnreadNotificationsFragment +import com.fastaccess.github.ui.modules.pr.fragment.PullRequestFragment import com.fastaccess.github.ui.modules.profile.feeds.ProfileFeedFragment import com.fastaccess.github.ui.modules.profile.followersandfollowings.ProfileFollowersFragment import com.fastaccess.github.ui.modules.profile.fragment.ProfileFragment @@ -60,4 +61,5 @@ abstract class FragmentBindingModule { @PerFragment @ContributesAndroidInjector(modules = [EditorModule::class]) abstract fun provideEditorFragment(): EditorFragment @PerFragment @ContributesAndroidInjector(modules = [EditIssuePrModule::class]) abstract fun provideEditIssuePrFragment(): EditIssuePrFragment @PerFragment @ContributesAndroidInjector(modules = [CommentModule::class]) abstract fun provideCommentFragment(): CommentFragment + @PerFragment @ContributesAndroidInjector(modules = [PullRequestModule::class]) abstract fun providePullRequestFragment(): PullRequestFragment } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/di/modules/FragmentModule.kt b/app/src/main/java/com/fastaccess/github/di/modules/FragmentModule.kt index 25eeb99d..3c34b22c 100644 --- a/app/src/main/java/com/fastaccess/github/di/modules/FragmentModule.kt +++ b/app/src/main/java/com/fastaccess/github/di/modules/FragmentModule.kt @@ -11,6 +11,7 @@ import com.fastaccess.github.ui.modules.comment.CommentFragment import com.fastaccess.github.ui.modules.editor.EditorFragment import com.fastaccess.github.ui.modules.issue.fragment.IssueFragment import com.fastaccess.github.ui.modules.issuesprs.edit.EditIssuePrFragment +import com.fastaccess.github.ui.modules.pr.fragment.PullRequestFragment import com.fastaccess.github.usecase.search.FilterSearchUsersUseCase import com.fastaccess.github.utils.extensions.theme import com.fastaccess.markdown.GrammarLocatorDef @@ -96,4 +97,9 @@ class EditIssuePrModule { @Module(includes = [FragmentModule::class]) class CommentModule { @PerFragment @Provides fun provideEditorContext(fragment: CommentFragment) = fragment.requireContext() +} + +@Module(includes = [FragmentModule::class]) +class PullRequestModule { + @PerFragment @Provides fun provideEditorContext(fragment: PullRequestFragment) = fragment.requireContext() } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/di/modules/ViewModelModule.kt b/app/src/main/java/com/fastaccess/github/di/modules/ViewModelModule.kt index 08f95396..2e4de4ce 100644 --- a/app/src/main/java/com/fastaccess/github/di/modules/ViewModelModule.kt +++ b/app/src/main/java/com/fastaccess/github/di/modules/ViewModelModule.kt @@ -17,6 +17,7 @@ import com.fastaccess.github.ui.modules.issuesprs.fragment.viewmodel.FilterIssue import com.fastaccess.github.ui.modules.main.fragment.viewmodel.MainFragmentViewModel import com.fastaccess.github.ui.modules.notifications.fragment.read.AllNotificationsViewModel import com.fastaccess.github.ui.modules.notifications.fragment.unread.viewmodel.UnreadNotificationsViewModel +import com.fastaccess.github.ui.modules.pr.fragment.viewmodel.PullRequestTimelineViewModel import com.fastaccess.github.ui.modules.profile.feeds.viewmodel.ProfileFeedsViewModel import com.fastaccess.github.ui.modules.profile.followersandfollowings.viewmodel.FollowersFollowingViewModel import com.fastaccess.github.ui.modules.profile.fragment.viewmodel.ProfileViewModel @@ -104,4 +105,7 @@ abstract class ViewModelModule { @Binds @IntoMap @ViewModelKey(EditIssuePrViewModel::class) abstract fun bindEditIssuePrViewModel(viewModel: EditIssuePrViewModel): ViewModel + + @Binds @IntoMap @ViewModelKey(PullRequestTimelineViewModel::class) + abstract fun bindPullRequestTimelineViewModel(viewModel: PullRequestTimelineViewModel): ViewModel } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/platform/extension/ClickExtenion.kt b/app/src/main/java/com/fastaccess/github/platform/extension/ClickExtenion.kt index d4e8705b..f510f051 100644 --- a/app/src/main/java/com/fastaccess/github/platform/extension/ClickExtenion.kt +++ b/app/src/main/java/com/fastaccess/github/platform/extension/ClickExtenion.kt @@ -27,7 +27,7 @@ fun MainScreenModel.onClick(fragment: Fragment) { MainScreenModelRowType.ISSUES_TITLE -> fragment.route(FILTER_ISSUE_LINK) MainScreenModelRowType.ISSUES -> fragment.route("${model.issuesPullsModel?.url}") MainScreenModelRowType.PRS_TITLE -> fragment.route(FILTER_PR_LINK) - MainScreenModelRowType.PRS -> Timber.e("${model.issuesPullsModel}") + MainScreenModelRowType.PRS -> fragment.route(model.issuesPullsModel?.url) } } @@ -36,6 +36,9 @@ fun FeedModel.onClick(fragment: Fragment) { when (type) { EventsType.IssueCommentEvent -> fragment.route("${payload?.issue?.htmlUrl}") EventsType.IssuesEvent -> fragment.route("${payload?.issue?.htmlUrl}") + EventsType.PullRequestEvent -> fragment.route("${payload?.pullRequest?.htmlUrl}") + EventsType.PullRequestReviewCommentEvent -> fragment.route("${payload?.pullRequest?.htmlUrl}") + EventsType.PullRequestReviewEvent -> fragment.route("${payload?.pullRequest?.htmlUrl}") else -> fragment.route(actor?.url) // TODO(handle click) } } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/issue/fragment/IssueFragment.kt b/app/src/main/java/com/fastaccess/github/ui/modules/issue/fragment/IssueFragment.kt index 3fff73cd..1f8999bd 100644 --- a/app/src/main/java/com/fastaccess/github/ui/modules/issue/fragment/IssueFragment.kt +++ b/app/src/main/java/com/fastaccess/github/ui/modules/issue/fragment/IssueFragment.kt @@ -160,7 +160,7 @@ class IssueFragment : BaseIssuePrTimelineFragment() { } val isAuthor = login == me?.login || model.authorAssociation?.equals(CommentAuthorAssociation.OWNER.rawValue(), true) == true || model.authorAssociation?.equals(CommentAuthorAssociation.COLLABORATOR.rawValue(), true) == true - menuClick(model, isAuthor) + menuClick(model.url, model.labels, model.assignees, model.title, model.body, isAuthor) initLabels(model.labels) initAssignees(model.assignees) initMilestone(model.milestone) diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/issuesprs/BaseIssuePrTimelineFragment.kt b/app/src/main/java/com/fastaccess/github/ui/modules/issuesprs/BaseIssuePrTimelineFragment.kt index caa7bf97..7670e46d 100644 --- a/app/src/main/java/com/fastaccess/github/ui/modules/issuesprs/BaseIssuePrTimelineFragment.kt +++ b/app/src/main/java/com/fastaccess/github/ui/modules/issuesprs/BaseIssuePrTimelineFragment.kt @@ -19,6 +19,7 @@ import com.fastaccess.data.model.parcelable.LabelModel import com.fastaccess.data.model.parcelable.LoginRepoParcelableModel import com.fastaccess.data.model.parcelable.MilestoneModel import com.fastaccess.data.persistence.models.IssueModel +import com.fastaccess.data.persistence.models.PullRequestModel import com.fastaccess.github.R import com.fastaccess.github.base.BaseFragment import com.fastaccess.github.extensions.* @@ -215,7 +216,11 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(), } protected fun menuClick( - model: IssueModel, + url: String?, + labels: List?, + assignees: List?, + title: String?, + body: String?, isOwner: Boolean ) { toolbar.setOnMenuItemClickListener { item -> @@ -230,7 +235,7 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(), recyclerView.scrollToPosition(0) } R.id.closeIssue -> closeOpenIssuePr() - R.id.share -> requireActivity().shareUrl(model.url) + R.id.share -> requireActivity().shareUrl(url) R.id.lockIssue -> if (item.title == getString(R.string.lock_issue)) { MultiPurposeBottomSheetDialog.show(childFragmentManager, MultiPurposeBottomSheetDialog.BottomSheetFragmentType.LOCK_UNLOCK) } else { @@ -238,26 +243,30 @@ abstract class BaseIssuePrTimelineFragment : BaseFragment(), } R.id.labels -> MultiPurposeBottomSheetDialog.show( childFragmentManager, - MultiPurposeBottomSheetDialog.BottomSheetFragmentType.LABELS, LoginRepoParcelableModel(login, repo, model.labels, number) + MultiPurposeBottomSheetDialog.BottomSheetFragmentType.LABELS, LoginRepoParcelableModel(login, repo, labels, number) ) R.id.assignees -> MultiPurposeBottomSheetDialog.show( childFragmentManager, - MultiPurposeBottomSheetDialog.BottomSheetFragmentType.ASSIGNEES, LoginRepoParcelableModel(login, repo, model.assignees, number) + MultiPurposeBottomSheetDialog.BottomSheetFragmentType.ASSIGNEES, LoginRepoParcelableModel(login, repo, assignees, number) ) R.id.milestone -> MultiPurposeBottomSheetDialog.show( childFragmentManager, - MultiPurposeBottomSheetDialog.BottomSheetFragmentType.MILESTONE, LoginRepoParcelableModel(login, repo, model.assignees, number) + MultiPurposeBottomSheetDialog.BottomSheetFragmentType.MILESTONE, LoginRepoParcelableModel(login, repo, assignees, number) ) - R.id.edit -> startEditingIssue(model, isOwner) + R.id.edit -> startEditingIssue(title, body, isOwner) } return@setOnMenuItemClickListener true } } - private fun startEditingIssue(model: IssueModel, isOwner: Boolean) { + private fun startEditingIssue( + title: String?, + body: String?, + isOwner: Boolean + ) { EditIssuePrActivity.startForResult( this, EditIssuePrBundleModel( - login, repo, number, model.title, model.body, false, isOwner = isOwner + login, repo, number, title, body, false, isOwner = isOwner ), EDIT_ISSUE_REQUEST_CODE ) } diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/pr/PullRequestActivity.kt b/app/src/main/java/com/fastaccess/github/ui/modules/pr/PullRequestActivity.kt index 0f3a014e..0f7b79da 100644 --- a/app/src/main/java/com/fastaccess/github/ui/modules/pr/PullRequestActivity.kt +++ b/app/src/main/java/com/fastaccess/github/ui/modules/pr/PullRequestActivity.kt @@ -6,6 +6,7 @@ import com.fastaccess.github.base.BaseActivity import com.fastaccess.github.extensions.replace import com.fastaccess.github.platform.deeplink.WebDeepLink import com.fastaccess.github.ui.modules.issue.fragment.IssueFragment +import com.fastaccess.github.ui.modules.pr.fragment.PullRequestFragment /** * Created by Kosh on 28.01.19. @@ -23,7 +24,7 @@ class PullRequestActivity : BaseActivity() { finish() return } - replace(R.id.container, IssueFragment.newInstance(login, repo, number), IssueFragment.TAG) + replace(R.id.container, PullRequestFragment.newInstance(login, repo, number), IssueFragment.TAG) } } diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt b/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt new file mode 100644 index 00000000..39e905a6 --- /dev/null +++ b/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/PullRequestFragment.kt @@ -0,0 +1,217 @@ +package com.fastaccess.github.ui.modules.pr.fragment + +import android.annotation.SuppressLint +import android.os.Bundle +import android.view.View +import androidx.core.os.bundleOf +import androidx.core.view.isVisible +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelProviders +import com.fastaccess.data.model.CommentModel +import com.fastaccess.data.model.TimelineModel +import com.fastaccess.data.persistence.models.LoginModel +import com.fastaccess.data.persistence.models.PullRequestModel +import com.fastaccess.data.storage.FastHubSharedPreference +import com.fastaccess.github.R +import com.fastaccess.github.base.BaseViewModel +import com.fastaccess.github.extensions.isTrue +import com.fastaccess.github.extensions.observeNotNull +import com.fastaccess.github.extensions.routeForResult +import com.fastaccess.github.extensions.timeAgo +import com.fastaccess.github.ui.adapter.IssueTimelineAdapter +import com.fastaccess.github.ui.modules.issuesprs.BaseIssuePrTimelineFragment +import com.fastaccess.github.ui.modules.pr.fragment.viewmodel.PullRequestTimelineViewModel +import com.fastaccess.github.utils.EDITOR_DEEPLINK +import com.fastaccess.github.utils.EXTRA +import com.fastaccess.github.utils.EXTRA_THREE +import com.fastaccess.github.utils.EXTRA_TWO +import com.fastaccess.github.utils.extensions.hideKeyboard +import com.fastaccess.github.utils.extensions.theme +import com.fastaccess.markdown.widget.SpannableBuilder +import github.type.CommentAuthorAssociation +import github.type.IssueState +import github.type.LockReason +import io.noties.markwon.Markwon +import io.noties.markwon.utils.NoCopySpannableFactory +import kotlinx.android.synthetic.main.comment_box_layout.* +import kotlinx.android.synthetic.main.issue_header_row_item.* +import kotlinx.android.synthetic.main.issue_pr_view_layout.* +import kotlinx.android.synthetic.main.recyclerview_fastscroll_empty_state_layout.* +import kotlinx.android.synthetic.main.title_toolbar_layout.* +import javax.inject.Inject + +/** + * Created by Kosh on 28.01.19. + */ +class PullRequestFragment : BaseIssuePrTimelineFragment() { + + @Inject lateinit var viewModelFactory: ViewModelProvider.Factory + @Inject lateinit var markwon: Markwon + @Inject lateinit var preference: FastHubSharedPreference + + private val viewModel by lazy { ViewModelProviders.of(this, viewModelFactory).get(PullRequestTimelineViewModel::class.java) } + + override val adapter by lazy { + IssueTimelineAdapter(markwon, preference.theme, onCommentClicked(), onDeleteCommentClicked(), onEditCommentClicked()) + } + + override fun layoutRes(): Int = R.layout.issue_pr_fragment_layout + override fun viewModel(): BaseViewModel? = viewModel + override fun isPr(): Boolean = true + override fun lockIssuePr(lockReason: LockReason?) = viewModel.lockUnlockIssue(login, repo, number, lockReason, true) + override fun onMilestoneAdd(timeline: TimelineModel) = viewModel.addTimeline(timeline) + override fun reload(refresh: Boolean) = viewModel.loadData(login, repo, number, refresh) + override fun sendComment(comment: String) = viewModel.createComment(login, repo, number, comment) + + override fun onFragmentCreatedWithUser( + view: View, + savedInstanceState: Bundle? + ) { + super.onFragmentCreatedWithUser(view, savedInstanceState) + observeChanges() + } + + override fun onDestroyView() { + mentionsPresenter.onDispose() + super.onDestroyView() + } + + override fun editIssuerPr( + title: String?, + description: String? + ) = viewModel.editIssue(login, repo, number, title, description) + + override fun lockUnlockIssuePr() = viewModel.lockUnlockIssue(login, repo, number) + override fun closeOpenIssuePr() = viewModel.closeOpenIssue(login, repo, number) + + private fun observeChanges() { + viewModel.getPullRequest(login, repo, number).observeNotNull(this) { + initIssue(it.first, it.second) + } + viewModel.timeline.observeNotNull(this) { timeline -> + adapter.submitList(timeline) + } + viewModel.userNamesLiveData.observeNotNull(this) { + mentionsPresenter.setUsers(it) + } + viewModel.commentProgress.observeNotNull(this) { + commentProgress.isVisible = it + sendComment.isVisible = !it + if (!it) { + commentText.setText("") + commentText.hideKeyboard() + recyclerView.scrollToPosition(adapter.itemCount) + } + } + viewModel.forceAdapterUpdate.observeNotNull(this) { + it.isTrue { adapter.notifyDataSetChanged() } + } + } + + @SuppressLint("DefaultLocale") + private fun initIssue( + model: PullRequestModel, + me: LoginModel? + ) { + issueHeaderWrapper.isVisible = true + val theme = preference.theme + title.text = model.title + toolbar.title = SpannableBuilder.builder() + .append(getString(R.string.pull_request)) + .bold("#${model.number}") + + opener.text = if (model.merged == true) { + SpannableBuilder.builder() + .bold(model.mergedBy?.login) + .space() + .append(getString(R.string.merged).toLowerCase()) + .space() + .bold(model.headRefName) + .space() + .append(getString(R.string.to)) + .space() + .bold(model.baseRefName) + .space() + .append(model.mergedAt?.timeAgo()) + } else { + SpannableBuilder.builder() + .bold(model.author?.login) + .space() + .append(getString(R.string.want_to_merge)) + .space() + .bold(model.headRefName) + .space() + .append(getString(R.string.to)) + .space() + .bold(model.baseRefName) + .space() + .append(model.createdAt?.timeAgo()) + } + + userIcon.loadAvatar(model.author?.avatarUrl, model.author?.url ?: "") + author.text = model.author?.login + association.text = if (CommentAuthorAssociation.NONE.rawValue() == model.authorAssociation) { + model.updatedAt?.timeAgo() + } else { + "${model.authorAssociation?.toLowerCase()?.replace("_", "")} ${model.updatedAt?.timeAgo()}" + } + + description.post { + val bodyMd = model.body +// description.setMovementMethod(LinkMovementMethod.getInstance()) + description.setSpannableFactory(NoCopySpannableFactory.getInstance()) + markwon.setMarkdown( + description, if (!bodyMd.isNullOrEmpty()) bodyMd else "**${getString(R.string.no_description_provided)}**" + ) + } + + state.text = model.state?.toLowerCase() + state.setChipBackgroundColorResource( + if (IssueState.OPEN.rawValue().equals(model.state, true)) { + R.color.material_green_500 + } else { + R.color.material_red_500 + } + ) + + adaptiveEmoticon.init(requireNotNull(model.id), model.reactionGroups) { + adaptiveEmoticon.initReactions(model.reactionGroups) + } + val isAuthor = login == me?.login || model.authorAssociation?.equals(CommentAuthorAssociation.OWNER.rawValue(), true) == true || + model.authorAssociation?.equals(CommentAuthorAssociation.COLLABORATOR.rawValue(), true) == true + menuClick(model.url, model.labels, model.assignees, model.title, model.body, isAuthor) + initLabels(model.labels) + initAssignees(model.assignees) + initMilestone(model.milestone) + initToolbarMenu(isAuthor, model.viewerCanUpdate == true, model.viewerDidAuthor, model.locked, state = model.state) + recyclerView.removeEmptyView() + } + + override fun onEditCommentClicked(): (position: Int, comment: CommentModel) -> Unit = { position, comment -> + routeForResult( + EDITOR_DEEPLINK, EDIT_COMMENT_REQUEST_CODE, bundleOf( + EXTRA to comment.body, + EXTRA_TWO to comment.databaseId + ) + ) + } + + override fun onDeleteCommentClicked(): (position: Int, comment: CommentModel) -> Unit = { position, comment -> + viewModel.deleteComment(login, repo, comment.databaseId?.toLong() ?: 0L) + } + + override fun onEditComment(comment: String?, commentId: Int?) { + viewModel.editComment(login, repo, comment, commentId?.toLong()) + } + + companion object { + const val TAG = "IssueFragment" + fun newInstance( + login: String, + repo: String, + number: Int + ) = PullRequestFragment().apply { + arguments = bundleOf(EXTRA to login, EXTRA_TWO to repo, EXTRA_THREE to number) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/viewmodel/PullRequestTimelineViewModel.kt b/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/viewmodel/PullRequestTimelineViewModel.kt new file mode 100644 index 00000000..a14868b4 --- /dev/null +++ b/app/src/main/java/com/fastaccess/github/ui/modules/pr/fragment/viewmodel/PullRequestTimelineViewModel.kt @@ -0,0 +1,222 @@ +package com.fastaccess.github.ui.modules.pr.fragment.viewmodel + +import androidx.lifecycle.MutableLiveData +import com.fastaccess.data.model.PageInfoModel +import com.fastaccess.data.model.TimelineModel +import com.fastaccess.data.repository.LoginLocalRepository +import com.fastaccess.data.repository.PullRequestRepository +import com.fastaccess.github.base.BaseViewModel +import com.fastaccess.github.extensions.filterNull +import com.fastaccess.github.extensions.map +import com.fastaccess.github.usecase.issuesprs.* +import github.type.LockReason +import io.reactivex.Observable +import timber.log.Timber +import javax.inject.Inject + +/** + * Created by Kosh on 20.10.18. + */ +class PullRequestTimelineViewModel @Inject constructor( + private val issueUseCase: GetPullRequestUseCase, + private val timelineUseCase: GetIssueTimelineUseCase, + private val issueRepositoryProvider: PullRequestRepository, + private val closeOpenIssuePrUseCase: CloseOpenIssuePrUseCase, + private val lockUnlockIssuePrUseCase: LockUnlockIssuePrUseCase, + private val loginRepositoryProvider: LoginLocalRepository, + private val createIssueCommentUseCase: CreateIssueCommentUseCase, + private val editIssuePrUseCase: EditIssuePrUseCase, + private val deleteCommentUseCase: DeleteCommentUseCase, + private val editCommentUseCase: EditCommentUseCase +) : BaseViewModel() { + + private var pageInfo: PageInfoModel? = null + private val list = arrayListOf() + val forceAdapterUpdate = MutableLiveData() + val timeline = MutableLiveData>() + val userNamesLiveData = MutableLiveData>() + val commentProgress = MutableLiveData() + + fun getPullRequest( + login: String, + repo: String, + number: Int + ) = issueRepositoryProvider.getPullRequestByNumber("$login/$repo", number) + .filterNull() + .map { Pair(it, loginRepositoryProvider.getLoginBlocking()) } + + fun loadData( + login: String, + repo: String, + number: Int, + reload: Boolean = false + ) { + if (reload) { + pageInfo = null + list.clear() + } + val pageInfo = pageInfo + if (!reload && (pageInfo != null && !pageInfo.hasNextPage)) return + val cursor = if (hasNext()) pageInfo?.endCursor else null + if (pageInfo == null) { + issueUseCase.login = login + issueUseCase.repo = repo + issueUseCase.number = number + justSubscribe(issueUseCase.buildObservable() + .flatMap { loadTimeline(login, repo, number, cursor) } + .map { mapToUserNames(it.second) }) + } else { + justSubscribe(loadTimeline(login, repo, number, cursor) + .map { mapToUserNames(it.second) }) + } + } + + private fun loadTimeline( + login: String, + repo: String, + number: Int, + cursor: String? + ): Observable>> { + return Observable.empty() +// timelineUseCase.login = login +// timelineUseCase.repo = repo +// timelineUseCase.number = number +// timelineUseCase.page = Input.optional(cursor) +// return timelineUseCase.buildObservable() +// .doOnNext { +// this.pageInfo = it.first +// list.addAll(it.second) +// timeline.postValue(ArrayList(list)) +// } + } + + fun closeOpenIssue( + login: String, + repo: String, + number: Int + ) { + closeOpenIssuePrUseCase.repo = repo + closeOpenIssuePrUseCase.login = login + closeOpenIssuePrUseCase.number = number + justSubscribe(closeOpenIssuePrUseCase.buildObservable() + .doOnNext { + addTimeline(it) + }) + } + + fun lockUnlockIssue( + login: String, + repo: String, + number: Int, + lockReason: LockReason? = null, + lock: Boolean = false + ) { + lockUnlockIssuePrUseCase.repo = repo + lockUnlockIssuePrUseCase.login = login + lockUnlockIssuePrUseCase.number = number + lockUnlockIssuePrUseCase.lockReason = lockReason + lockUnlockIssuePrUseCase.lock = lock + justSubscribe(lockUnlockIssuePrUseCase.buildObservable() + .doOnNext { + addTimeline(it) + }) + } + + fun createComment( + login: String, + repo: String, + number: Int, + comment: String + ) { + createIssueCommentUseCase.login = login + createIssueCommentUseCase.repo = repo + createIssueCommentUseCase.number = number + createIssueCommentUseCase.body = comment + add(createIssueCommentUseCase.buildObservable() + .doOnSubscribe { commentProgress.postValue(true) } + .subscribe({ + addTimeline(it) + commentProgress.postValue(false) + }, { + commentProgress.postValue(false) + handleError(it) + }) + ) + } + + fun addTimeline(it: TimelineModel) { + list.add(it) + timeline.postValue(ArrayList(list)) + } + + fun hasNext() = pageInfo?.hasNextPage ?: false + + private fun mapToUserNames(list: List) { + val _list = userNamesLiveData.value ?: arrayListOf() + _list.addAll(list.map { it.comment?.author?.login ?: it.comment?.author?.name ?: "" }) + userNamesLiveData.postValue(_list) + } + + fun editIssue( + login: String, + repo: String, + number: Int, + title: String?, + description: String? + ) { + editIssuePrUseCase.login = login + editIssuePrUseCase.repo = repo + editIssuePrUseCase.number = number + editIssuePrUseCase.title = title + editIssuePrUseCase.description = description + justSubscribe(editIssuePrUseCase.buildObservable()) + } + + fun deleteComment( + login: String, + repo: String, + commentId: Long + ) { + deleteCommentUseCase.commentId = commentId + deleteCommentUseCase.login = login + deleteCommentUseCase.repo = repo + justSubscribe(deleteCommentUseCase.buildObservable() + .map { + val index = list.indexOfFirst { it.comment?.databaseId?.toLong() == commentId } + if (index != -1) { + list.removeAt(index) + } + return@map list + } + .doOnNext { list -> + timeline.postValue(ArrayList(list)) + }) + } + + fun editComment( + login: String, + repo: String, + comment: String?, + commentId: Long? + ) { + if (!comment.isNullOrBlank() && commentId != null) { + editCommentUseCase.comment = comment + editCommentUseCase.login = login + editCommentUseCase.repo = repo + editCommentUseCase.commentId = commentId + justSubscribe(editCommentUseCase.buildObservable() + .map { + val index = list.indexOfFirst { it.comment?.databaseId?.toLong() == commentId } + val item = list.getOrNull(index) ?: return@map list + item.comment?.body = comment + list[index] = item + Timber.e("${list[index]}") + return@map list + } + .doOnNext { list -> + timeline.postValue(ArrayList(list)) + forceAdapterUpdate.postValue(true) + }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/ui/modules/profile/fragment/ProfileFragment.kt b/app/src/main/java/com/fastaccess/github/ui/modules/profile/fragment/ProfileFragment.kt index 97e364ed..2fe3d4b1 100644 --- a/app/src/main/java/com/fastaccess/github/ui/modules/profile/fragment/ProfileFragment.kt +++ b/app/src/main/java/com/fastaccess/github/ui/modules/profile/fragment/ProfileFragment.kt @@ -85,9 +85,7 @@ class ProfileFragment : BasePagerFragment() { organizationList.adapter = orgsAdapter pinnedList.adapter = pinnedReposAdapter pinnedList.addDivider() - observeChanges() - behaviour.state = AnchorSheetBehavior.STATE_ANCHOR toggleArrow.setOnClickListener { @@ -120,6 +118,7 @@ class ProfileFragment : BasePagerFragment() { } followers.setOnClickListener { if (pager.adapter != null) selectTab(FragmentType.FOLLOWERS) } following.setOnClickListener { if (pager.adapter != null) selectTab(FragmentType.FOLLOWINGS) } + viewModel.getUserFromRemote(loginBundle) } override fun onBackPressed(): Boolean { @@ -132,12 +131,8 @@ class ProfileFragment : BasePagerFragment() { } private fun observeChanges() { - viewModel.getUser(loginBundle).observeNull(this) { user -> - if (user == null) { - viewModel.getUserFromRemote(loginBundle) - } else { - initUI(user) - } + viewModel.getUser(loginBundle).observeNotNull(this) { user -> + initUI(user) } viewModel.isBlocked.observeNotNull(this) { diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt new file mode 100644 index 00000000..a2355250 --- /dev/null +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/BaseTimelineUseCase.kt @@ -0,0 +1,212 @@ +package com.fastaccess.github.usecase.issuesprs + +import com.fastaccess.data.model.* +import com.fastaccess.data.model.parcelable.LabelModel +import com.fastaccess.domain.usecase.base.BaseObservableUseCase +import com.fastaccess.extension.* +import github.fragment.* + +abstract class BaseTimelineUseCase : BaseObservableUseCase() { + + + protected fun getTransferred(node: Transferred): TimelineModel = TimelineModel( + transferredEventModel = TransferredEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.fromRepository?.nameWithOwner + ) + ) + + protected fun getRenamed(node: Renamed): TimelineModel = TimelineModel( + renamedEventModel = RenamedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.currentTitle, node.previousTitle + ) + ) + + protected fun getDemilestoned(node: Demilestoned): TimelineModel = TimelineModel( + milestoneEventModel = MilestoneDemilestonedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.milestoneTitle, false + ) + ) + + protected fun getMilestone(node: Milestoned): TimelineModel = TimelineModel( + milestoneEventModel = MilestoneDemilestonedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.milestoneTitle, true + ) + ) + + protected fun getUnassigned( + node: UnAssigned, + list: ArrayList + ): TimelineModel? { + var shouldAdd = true + list.filter { it.assignedEventModel != null }.map { + if (it.assignedEventModel?.createdAt?.time == node.createdAt.time) { + it.assignedEventModel?.users?.add(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) + shouldAdd = false + } + } + if (shouldAdd) { + return TimelineModel( + assignedEventModel = AssignedUnAssignedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false, + arrayListOf(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) + ) + ) + } + return null + } + + protected fun getAssigned( + node: Assigned, + list: ArrayList + ): TimelineModel? { + var shouldAdd = true + list.filter { it.assignedEventModel != null }.map { + if (it.assignedEventModel?.createdAt?.time == node.createdAt.time) { + it.assignedEventModel?.users?.add(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) + shouldAdd = false + } + } + if (shouldAdd) { + return TimelineModel( + assignedEventModel = AssignedUnAssignedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), true, + arrayListOf(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) + ) + ) + } + return null + } + + protected fun getUnsubscribed( + node: Unsubscribed + ): TimelineModel = TimelineModel( + subscribedUnsubscribedEvent = SubscribedUnsubscribedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false + ) + ) + + protected fun getSubscribed(node: Subscribed): TimelineModel = TimelineModel( + subscribedUnsubscribedEvent = SubscribedUnsubscribedEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false + ) + ) + + protected fun getUnlabeled( + node: UnLabeled, + list: ArrayList + ): TimelineModel? { + var shouldAdd = true + list.filter { it.labelUnlabeledEvent != null }.map { + if (it.labelUnlabeledEvent?.createdAt?.time == node.createdAt.time) { + it.labelUnlabeledEvent?.labels?.add(constructLabel(node.label)) + shouldAdd = false + } + } + if (shouldAdd) { + return TimelineModel( + labelUnlabeledEvent = LabelUnlabeledEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false, arrayListOf(constructLabel(node.label)) + ) + ) + } + return null + } + + protected fun getLabel( + node: Labeled, + list: ArrayList + ): TimelineModel? { + var shouldAdd = true + list.filter { it.labelUnlabeledEvent != null }.map { + if (it.labelUnlabeledEvent?.createdAt?.time == node.createdAt.time) { + it.labelUnlabeledEvent?.labels?.add(constructLabel(node.label)) + shouldAdd = false + } + } + if (shouldAdd) { + return TimelineModel( + labelUnlabeledEvent = LabelUnlabeledEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), true, arrayListOf(constructLabel(node.label)) + ) + ) + } + return null + } + + protected fun constructLabel(m: Any): LabelModel { + return when (m) { + is Labels -> LabelModel(m.name, m.color) + else -> throw IllegalArgumentException("$m is not instance of any Label") + } + } + + + protected fun getUnlocked(node: Unlocked): TimelineModel = TimelineModel( + lockUnlockEventModel = LockUnlockEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), null, node.lockable.activeLockReason?.rawValue() + ) + ) + + protected fun getLock(node: Locked): TimelineModel = TimelineModel( + lockUnlockEventModel = LockUnlockEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.lockReason?.rawValue(), node.lockable.activeLockReason?.rawValue(), true + ) + ) + + protected fun getReopened(node: Reopened): TimelineModel { + return TimelineModel( + closeOpenEventModel = CloseOpenEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser() + ) + ) + } + + protected fun getClosed(node: Closed): TimelineModel { + val commit = node.closer?.fragments?.commitFragment?.toCommit() + val pr = node.closer?.fragments?.shortPullRequestRowItem?.toPullRequest() + return TimelineModel( + closeOpenEventModel = CloseOpenEventModel( + node.createdAt, node.actor?.fragments?.shortActor?.toUser(), commit, pr, true + ) + ) + } + + protected fun getReference(node: Referenced): TimelineModel { + val issueModel = node.subject.fragments.shortIssueRowItem?.toIssue() + val pullRequest = node.subject.fragments.shortPullRequestRowItem?.toPullRequest() + return TimelineModel( + referencedEventModel = ReferencedEventModel( + node.commitRepository.nameWithOwner, node.createdAt, ShortUserModel( + node.actor?.fragments?.shortActor?.login, node.actor?.fragments?.shortActor?.login, + node.actor?.fragments?.shortActor?.url?.toString(), + avatarUrl = node.actor?.fragments?.shortActor?.avatarUrl?.toString() + ), node.isCrossRepository, node.isDirectReference, + node.commit?.fragments?.commitFragment?.toCommit(), issueModel, pullRequest + ) + ) + } + + protected fun getCrossReference(node: CrossReferenced): TimelineModel { + val actor = node.actor?.fragments?.shortActor?.toUser() + val issueModel = node.source.fragments.shortIssueRowItem?.toIssue() + val pullRequest = node.source.fragments.shortPullRequestRowItem?.toPullRequest() + return TimelineModel( + crossReferencedEventModel = CrossReferencedEventModel( + node.createdAt, node.referencedAt, + node.isCrossRepository, node.isWillCloseTarget, actor, issueModel, pullRequest + ) + ) + } + + protected fun getComment(node: Comment) = TimelineModel( + comment = CommentModel( + node.id, node.databaseId, + ShortUserModel(node.author?.login, node.author?.login, node.author?.url?.toString(), avatarUrl = node.author?.avatarUrl.toString()), + node.bodyHTML.toString(), node.body, CommentAuthorAssociation.fromName(node.authorAssociation.rawValue()), + node.viewerCannotUpdateReasons.map { reason -> CommentCannotUpdateReason.fromName(reason.rawValue()) }.toList(), + node.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, node.createdAt, node.updatedAt, + node.isViewerCanReact, node.isViewerCanDelete, node.isViewerCanUpdate, node.isViewerDidAuthor, node.isViewerCanMinimize + ) + ) + +} \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt index 6acc56a3..a8e37736 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/CreateIssueCommentUseCase.kt @@ -31,8 +31,9 @@ class CreateIssueCommentUseCase @Inject constructor( .observeOn(schedulerProvider.uiThread()) .flatMap { Rx2Apollo.from(apolloClient.query(GetLastIssueCommentQuery(login, repo, number))) } .map { - val node = it.data()?.repositoryOwner?.repository?.issue?.timelineItems?.nodes?.firstOrNull() + val type = it.data()?.repositoryOwner?.repository?.issue?.timelineItems?.nodes?.firstOrNull() as? GetLastIssueCommentQuery.AsIssueComment ?: return@map TimelineModel() + val node = type.fragments.comment ?: return@map TimelineModel() return@map TimelineModel( comment = CommentModel( node.id, diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetIssueTimelineUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetIssueTimelineUseCase.kt index 31742ada..2f3f9307 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetIssueTimelineUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetIssueTimelineUseCase.kt @@ -11,6 +11,7 @@ import com.fastaccess.extension.* import com.fastaccess.github.extensions.addIfNotNull import github.GetIssueTimelineQuery import github.GetIssueTimelineQuery.* +import github.fragment.* import io.reactivex.Observable import javax.inject.Inject @@ -20,7 +21,7 @@ import javax.inject.Inject class GetIssueTimelineUseCase @Inject constructor( private val apolloClient: ApolloClient, private val schedulerProvider: SchedulerProvider -) : BaseObservableUseCase() { +) : BaseTimelineUseCase() { var login: String? = null var repo: String? = null @@ -40,236 +41,35 @@ class GetIssueTimelineUseCase @Inject constructor( .subscribeOn(schedulerProvider.ioThread()) .observeOn(schedulerProvider.uiThread()) .map { it.data()?.repositoryOwner?.repository?.issue } - .map { + .map { issue -> val list = arrayListOf() - val timeline = it.timelineItems + val timeline = issue.timelineItems val pageInfo = PageInfoModel( timeline.pageInfo.startCursor, timeline.pageInfo.endCursor, timeline.pageInfo.isHasNextPage, timeline.pageInfo.isHasPreviousPage ) timeline.nodes?.forEach { node -> when (node) { - is AsIssueComment -> list.add(getComment(node)) - is AsCrossReferencedEvent -> list.add(getCrossReference(node)) - is AsClosedEvent -> list.add(getClosed(node)) - is AsReopenedEvent -> list.add(getReopened(node)) - is AsSubscribedEvent -> list.add(getSubscribed(node)) - is AsUnsubscribedEvent -> list.add(getUnsubscribed(node)) - is AsReferencedEvent -> list.add(getReference(node)) - is AsAssignedEvent -> list.addIfNotNull(getAssigned(node, list)) - is AsUnassignedEvent -> list.addIfNotNull(getUnassigned(node, list)) - is AsLabeledEvent -> list.addIfNotNull(getLabel(node, list)) - is AsUnlabeledEvent -> list.addIfNotNull(getUnlabeled(node, list)) - is AsMilestonedEvent -> list.add(getMilestone(node)) - is AsDemilestonedEvent -> list.add(getDemilestoned(node)) - is AsRenamedTitleEvent -> list.add(getRenamed(node)) - is AsLockedEvent -> list.add(getLock(node)) - is AsUnlockedEvent -> list.add(getUnlocked(node)) - is AsTransferredEvent -> list.add(getTransferred(node)) + is AsIssueComment -> node.fragments.comment?.let { list.add(getComment(it)) } + is AsCrossReferencedEvent -> node.fragments.crossReferenced?.let { list.add(getCrossReference(it)) } + is AsClosedEvent -> node.fragments.closed?.let { list.add(getClosed(it)) } + is AsReopenedEvent -> node.fragments.reopened?.let { list.add(getReopened(it)) } + is AsSubscribedEvent -> node.fragments.subscribed?.let { list.add(getSubscribed(it)) } + is AsUnsubscribedEvent -> node.fragments.unsubscribed?.let { list.add(getUnsubscribed(it)) } + is AsReferencedEvent -> node.fragments.referenced?.let { list.add(getReference(it)) } + is AsAssignedEvent -> node.fragments.assigned?.let { list.addIfNotNull(getAssigned(it, list)) } + is AsUnassignedEvent -> node.fragments.unAssigned?.let { list.addIfNotNull(getUnassigned(it, list)) } + is AsLabeledEvent -> node.fragments.labeled?.let { list.addIfNotNull(getLabel(it, list)) } + is AsUnlabeledEvent -> node.fragments.unLabeled?.let { list.addIfNotNull(getUnlabeled(it, list)) } + is AsMilestonedEvent -> node.fragments.milestoned?.let { list.add(getMilestone(it)) } + is AsDemilestonedEvent -> node.fragments.demilestoned?.let { list.add(getDemilestoned(it)) } + is AsRenamedTitleEvent -> node.fragments.renamed?.let { list.add(getRenamed(it)) } + is AsLockedEvent -> node.fragments.locked?.let { list.add(getLock(it)) } + is AsUnlockedEvent -> node.fragments.unlocked?.let { list.add(getUnlocked(it)) } + is AsTransferredEvent -> node.fragments.transferred?.let { list.add(getTransferred(it)) } } } return@map Pair(pageInfo, list) } } - - private fun getTransferred(node: AsTransferredEvent): TimelineModel = TimelineModel( - transferredEventModel = TransferredEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.fromRepository?.nameWithOwner - ) - ) - - private fun getRenamed(node: AsRenamedTitleEvent): TimelineModel = TimelineModel( - renamedEventModel = RenamedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.currentTitle, node.previousTitle - ) - ) - - private fun getDemilestoned(node: AsDemilestonedEvent): TimelineModel = TimelineModel( - milestoneEventModel = MilestoneDemilestonedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.milestoneTitle, false - ) - ) - - private fun getMilestone(node: AsMilestonedEvent): TimelineModel = TimelineModel( - milestoneEventModel = MilestoneDemilestonedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.milestoneTitle, true - ) - ) - - private fun getUnassigned( - node: AsUnassignedEvent, - list: ArrayList - ): TimelineModel? { - var shouldAdd = true - list.filter { it.assignedEventModel != null }.map { - if (it.assignedEventModel?.createdAt?.time == node.createdAt.time) { - it.assignedEventModel?.users?.add(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) - shouldAdd = false - } - } - if (shouldAdd) { - return TimelineModel( - assignedEventModel = AssignedUnAssignedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false, - arrayListOf(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) - ) - ) - } - return null - } - - private fun getAssigned( - node: AsAssignedEvent, - list: ArrayList - ): TimelineModel? { - var shouldAdd = true - list.filter { it.assignedEventModel != null }.map { - if (it.assignedEventModel?.createdAt?.time == node.createdAt.time) { - it.assignedEventModel?.users?.add(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) - shouldAdd = false - } - } - if (shouldAdd) { - return TimelineModel( - assignedEventModel = AssignedUnAssignedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), true, - arrayListOf(ShortUserModel(node.user?.login, node.user?.login, avatarUrl = node.user?.avatarUrl?.toString())) - ) - ) - } - return null - } - - private fun getUnsubscribed( - node: AsUnsubscribedEvent - ): TimelineModel = TimelineModel( - subscribedUnsubscribedEvent = SubscribedUnsubscribedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false - ) - ) - - private fun getSubscribed(node: AsSubscribedEvent): TimelineModel = TimelineModel( - subscribedUnsubscribedEvent = SubscribedUnsubscribedEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false - ) - ) - - private fun getUnlabeled( - node: AsUnlabeledEvent, - list: ArrayList - ): TimelineModel? { - var shouldAdd = true - list.filter { it.labelUnlabeledEvent != null }.map { - if (it.labelUnlabeledEvent?.createdAt?.time == node.createdAt.time) { - it.labelUnlabeledEvent?.labels?.add(constructLabel(node.label)) - shouldAdd = false - } - } - if (shouldAdd) { - return TimelineModel( - labelUnlabeledEvent = LabelUnlabeledEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), false, arrayListOf(constructLabel(node.label)) - ) - ) - } - return null - } - - private fun getLabel( - node: AsLabeledEvent, - list: ArrayList - ): TimelineModel? { - var shouldAdd = true - list.filter { it.labelUnlabeledEvent != null }.map { - if (it.labelUnlabeledEvent?.createdAt?.time == node.createdAt.time) { - it.labelUnlabeledEvent?.labels?.add(constructLabel(node.label)) - shouldAdd = false - } - } - if (shouldAdd) { - return TimelineModel( - labelUnlabeledEvent = LabelUnlabeledEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), true, arrayListOf(constructLabel(node.label)) - ) - ) - } - return null - } - - private fun constructLabel(m: Any): LabelModel { - return when (m) { - is Label -> LabelModel(m.name, m.color) - is Label1 -> LabelModel(m.name, m.color) - else -> throw IllegalArgumentException("$m is not instance of any Label") - } - } - - - private fun getUnlocked(node: AsUnlockedEvent): TimelineModel = TimelineModel( - lockUnlockEventModel = LockUnlockEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), null, node.lockable.activeLockReason?.rawValue() - ) - ) - - private fun getLock(node: AsLockedEvent): TimelineModel = TimelineModel( - lockUnlockEventModel = LockUnlockEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), node.lockReason?.rawValue(), node.lockable.activeLockReason?.rawValue(), true - ) - ) - - private fun getReopened(node: AsReopenedEvent): TimelineModel { - return TimelineModel( - closeOpenEventModel = CloseOpenEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser() - ) - ) - } - - private fun getClosed(node: AsClosedEvent): TimelineModel { - val commit = node.closer?.fragments?.commitFragment?.toCommit() - val pr = node.closer?.fragments?.shortPullRequestRowItem?.toPullRequest() - return TimelineModel( - closeOpenEventModel = CloseOpenEventModel( - node.createdAt, node.actor?.fragments?.shortActor?.toUser(), commit, pr, true - ) - ) - } - - private fun getReference(node: AsReferencedEvent): TimelineModel { - val issueModel = node.subject.fragments.shortIssueRowItem?.toIssue() - val pullRequest = node.subject.fragments.shortPullRequestRowItem?.toPullRequest() - return TimelineModel( - referencedEventModel = ReferencedEventModel( - node.commitRepository.nameWithOwner, node.createdAt, ShortUserModel( - node.actor?.fragments?.shortActor?.login, node.actor?.fragments?.shortActor?.login, - node.actor?.fragments?.shortActor?.url?.toString(), - avatarUrl = node.actor?.fragments?.shortActor?.avatarUrl?.toString() - ), node.isCrossRepository, node.isDirectReference, - node.commit?.fragments?.commitFragment?.toCommit(), issueModel, pullRequest - ) - ) - } - - private fun getCrossReference(node: AsCrossReferencedEvent): TimelineModel { - val actor = node.actor?.fragments?.shortActor?.toUser() - val issueModel = node.source.fragments.shortIssueRowItem?.toIssue() - val pullRequest = node.source.fragments.shortPullRequestRowItem?.toPullRequest() - return TimelineModel( - crossReferencedEventModel = CrossReferencedEventModel( - node.createdAt, node.referencedAt, - node.isCrossRepository, node.isWillCloseTarget, actor, issueModel, pullRequest - ) - ) - } - - private fun getComment(node: AsIssueComment) = TimelineModel( - comment = CommentModel( - node.id, node.databaseId, - ShortUserModel(node.author?.login, node.author?.login, node.author?.url?.toString(), avatarUrl = node.author?.avatarUrl.toString()), - node.bodyHTML.toString(), node.body, CommentAuthorAssociation.fromName(node.authorAssociation.rawValue()), - node.viewerCannotUpdateReasons.map { reason -> CommentCannotUpdateReason.fromName(reason.rawValue()) }.toList(), - node.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, node.createdAt, node.updatedAt, - node.isViewerCanReact, node.isViewerCanDelete, node.isViewerCanUpdate, node.isViewerDidAuthor, node.isViewerCanMinimize - ) - ) } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt index 197dd222..d689ddc7 100644 --- a/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt +++ b/app/src/main/java/com/fastaccess/github/usecase/issuesprs/GetPullRequestUseCase.kt @@ -60,7 +60,8 @@ class GetPullRequestUseCase @Inject constructor( issue.isViewerCanReact, issue.isViewerCanSubscribe, issue.isViewerCanUpdate, issue.isViewerDidAuthor, issue.mergeable.rawValue(), issue.isMerged, issue.mergedAt, issue.authorAssociation.rawValue(), issue.url.toString(), issue.labels?.nodes?.map { it.fragments.labels.toLabels() }, - issue.milestone?.toMilestone(), issue.assignees.nodes?.map { it.fragments }?.map { it.shortUserRowItem.toUser() }) + issue.milestone?.toMilestone(), issue.assignees.nodes?.map { it.fragments }?.map { it.shortUserRowItem.toUser() }, + issue.headRefName, issue.baseRefName) ) } } diff --git a/data/src/main/graphql/github/fragments.graphql b/data/src/main/graphql/github/fragments.graphql index 10434bbe..6478f121 100644 --- a/data/src/main/graphql/github/fragments.graphql +++ b/data/src/main/graphql/github/fragments.graphql @@ -83,6 +83,10 @@ fragment CommitFragment on Commit { authoredDate committedViaWeb id + oid + status { + state + } } fragment FullIssue on Issue { @@ -165,40 +169,42 @@ fragment FullPullRequest on PullRequest { mergeable merged mergedAt + headRefName + baseRefName mergedBy { - avatarUrl - login - url + avatarUrl + login + url } url userContentEdits(first: 0) { - totalCount + totalCount } author { - avatarUrl - login - url + avatarUrl + login + url } reactionGroups { - ...Reactions + ...Reactions } createdViaEmail repository { - nameWithOwner + nameWithOwner } authorAssociation labels(first: 30) { - nodes { - ...Labels - } + nodes { + ...Labels + } } assignees(first: 30) { - nodes { - ...ShortUserRowItem - } + nodes { + ...ShortUserRowItem + } } milestone { - ...MilestoneFragment + ...MilestoneFragment } } @@ -230,4 +236,167 @@ fragment MilestoneFragment on Milestone { url title state +} + +fragment Comment on IssueComment { + author { + login + avatarUrl + url + } + createdAt + authorAssociation + bodyHTML + body + id + databaseId + updatedAt + viewerCanReact + viewerCanDelete + viewerCanUpdate + viewerDidAuthor + viewerCanMinimize + viewerCannotUpdateReasons + reactionGroups { + ... Reactions + } +} + +fragment CrossReferenced on CrossReferencedEvent { + createdAt + isCrossRepository + referencedAt + source { + ... ShortPullRequestRowItem + ... ShortIssueRowItem + } + willCloseTarget + actor { ... ShortActor } +} + +fragment Closed on ClosedEvent { + actor { ... ShortActor } + createdAt + closer { + ... CommitFragment + ... ShortPullRequestRowItem + } +} + +fragment Reopened on ReopenedEvent { + actor { ... ShortActor } + createdAt +} + +fragment Subscribed on SubscribedEvent { + actor { ... ShortActor } + createdAt +} + +fragment Unsubscribed on UnsubscribedEvent { + actor { ... ShortActor } + createdAt +} + +fragment Referenced on ReferencedEvent { + actor { ... ShortActor } + createdAt + commitRepository { + nameWithOwner + } + isCrossRepository + isDirectReference + commit { + ... CommitFragment + } + subject { + ... ShortPullRequestRowItem + ... ShortIssueRowItem + } +} + +fragment Assigned on AssignedEvent { + actor { ... ShortActor } + createdAt + user { + login + avatarUrl + } +} + +fragment UnAssigned on UnassignedEvent { + actor { ... ShortActor } + createdAt + user { + login + avatarUrl + } +} + +fragment Labeled on LabeledEvent { + actor { ... ShortActor } + createdAt + label { + color + name + isDefault + } +} + +fragment UnLabeled on UnlabeledEvent { + actor { ... ShortActor } + createdAt + label { + ... Labels + } +} + +fragment Milestoned on MilestonedEvent { + actor { ... ShortActor } + createdAt + milestoneTitle +} + +fragment Demilestoned on DemilestonedEvent { + actor { ... ShortActor } + createdAt + milestoneTitle +} + +fragment Renamed on RenamedTitleEvent { + actor { ... ShortActor } + createdAt + currentTitle + previousTitle +} + +fragment Locked on LockedEvent { + actor { ... ShortActor } + createdAt + lockReason + lockable { + activeLockReason + } +} + +fragment Unlocked on UnlockedEvent { + actor { ... ShortActor } + createdAt + lockable { + activeLockReason + } +} + +fragment Transferred on TransferredEvent { + actor { ... ShortActor } + createdAt + fromRepository { + id + nameWithOwner + url + } + issue { + number + url + } } \ No newline at end of file diff --git a/data/src/main/graphql/github/issues_prs.graphql b/data/src/main/graphql/github/issues_prs.graphql index 9e7139b8..7ddd8013 100644 --- a/data/src/main/graphql/github/issues_prs.graphql +++ b/data/src/main/graphql/github/issues_prs.graphql @@ -55,26 +55,7 @@ query getLastIssueComment($login: String!, $repo: String!, $number: Int!) { timelineItems(last: 1, itemTypes: [ISSUE_COMMENT]) { nodes { ... on IssueComment { - author { - login - avatarUrl - } - createdAt - authorAssociation - bodyHTML - body - id - databaseId - updatedAt - viewerCanReact - viewerCanDelete - viewerCanUpdate - viewerDidAuthor - viewerCanMinimize - viewerCannotUpdateReasons - reactionGroups { - ... Reactions - } + ... Comment } } } @@ -90,26 +71,7 @@ query getLastPrComment($login: String!, $repo: String!, $number: Int!) { timelineItems(last: 1, itemTypes: [ISSUE_COMMENT]) { nodes { ... on IssueComment { - author { - login - avatarUrl - } - createdAt - authorAssociation - bodyHTML - body - id - databaseId - updatedAt - viewerCanReact - viewerCanDelete - viewerCanUpdate - viewerDidAuthor - viewerCanMinimize - viewerCannotUpdateReasons - reactionGroups { - ... Reactions - } + ... Comment } } } @@ -133,152 +95,55 @@ query getIssueTimeline($login: String!, $repo: String!, $number: Int!, $page: St nodes { __typename ... on IssueComment { - author { - login - avatarUrl - url - } - createdAt - authorAssociation - bodyHTML - body - id - databaseId - updatedAt - viewerCanReact - viewerCanDelete - viewerCanUpdate - viewerDidAuthor - viewerCanMinimize - viewerCannotUpdateReasons - reactionGroups { - ... Reactions - } + ... Comment } ... on CrossReferencedEvent { - createdAt - isCrossRepository - referencedAt - source { - ... ShortPullRequestRowItem - ... ShortIssueRowItem - } - willCloseTarget - actor { ... ShortActor } + ... CrossReferenced } ... on ClosedEvent { - actor { ... ShortActor } - createdAt - closer { - ... CommitFragment - ... ShortPullRequestRowItem - } + ... Closed } ... on ReopenedEvent { - actor { ... ShortActor } - createdAt + ... Reopened } ... on SubscribedEvent { - actor { ... ShortActor } - createdAt + ... Subscribed } ... on UnsubscribedEvent { - actor { ... ShortActor } - createdAt + ... Unsubscribed } ... on ReferencedEvent { - actor { ... ShortActor } - createdAt - commitRepository { - nameWithOwner - } - isCrossRepository - isDirectReference - commit { - ... CommitFragment - } - subject { - ... ShortPullRequestRowItem - ... ShortIssueRowItem - } + ... Referenced } ... on AssignedEvent { - actor { ... ShortActor } - createdAt - user { - login - avatarUrl - } + ... Assigned } ... on UnassignedEvent { - actor { ... ShortActor } - createdAt - user { - login - avatarUrl - } + ... UnAssigned } ... on LabeledEvent { - actor { ... ShortActor } - createdAt - label { - color - name - isDefault - } + ... Labeled } ... on UnlabeledEvent { - actor { ... ShortActor } - createdAt - label { - color - name - isDefault - } + ... UnLabeled } ... on MilestonedEvent { - actor { ... ShortActor } - createdAt - milestoneTitle + ... Milestoned } ... on DemilestonedEvent { - actor { ... ShortActor } - createdAt - milestoneTitle + ... Demilestoned } ... on RenamedTitleEvent { - actor { ... ShortActor } - createdAt - currentTitle - previousTitle + ... Renamed } ... on LockedEvent { - actor { ... ShortActor } - createdAt - lockReason - lockable { - activeLockReason - } + ... Locked } ... on UnlockedEvent { - actor { ... ShortActor } - createdAt - lockable { - activeLockReason - } + ... Unlocked } ... on TransferredEvent { - actor { ... ShortActor } - createdAt - fromRepository { - id - nameWithOwner - url - } - issue { - number - url - } + ... Transferred } } } diff --git a/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt b/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt index eec4f4a3..404b6d13 100644 --- a/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt +++ b/data/src/main/java/com/fastaccess/data/model/TimelineModel.kt @@ -3,6 +3,7 @@ package com.fastaccess.data.model import com.fastaccess.data.model.parcelable.LabelModel import com.fastaccess.data.persistence.models.IssueModel import com.fastaccess.data.persistence.models.MyIssuesPullsModel +import com.fastaccess.data.persistence.models.PullRequestModel import com.google.gson.annotations.SerializedName import java.util.* @@ -22,7 +23,8 @@ data class TimelineModel( @SerializedName("assignedUnassignedEvent") val assignedEventModel: AssignedUnAssignedEventModel? = null, @SerializedName("milestoneDemilestoneEvent") val milestoneEventModel: MilestoneDemilestonedEventModel? = null, @SerializedName("renamedEvent") val renamedEventModel: RenamedEventModel? = null, - @SerializedName("transferredEvent") val transferredEventModel: TransferredEventModel? = null + @SerializedName("transferredEvent") val transferredEventModel: TransferredEventModel? = null, + @SerializedName("pullRequest") val pullRequest: PullRequestModel? = null ) data class CommitModel( diff --git a/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt b/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt index b52e1a4f..6105efc6 100644 --- a/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt +++ b/data/src/main/java/com/fastaccess/data/persistence/db/FastHubDatabase.kt @@ -10,7 +10,7 @@ import com.fastaccess.data.persistence.models.* * Created by Kosh on 11.05.18. */ -const val VERSION = 29 +const val VERSION = 30 const val DATABASE_NAME = "FastHub-Room-DB" @Database( diff --git a/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt b/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt index 0f0c9b2e..37754a7f 100644 --- a/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt +++ b/data/src/main/java/com/fastaccess/data/persistence/models/PullRequestModel.kt @@ -50,7 +50,9 @@ data class PullRequestModel( @SerializedName("url") var url: String? = null, @SerializedName("labels") var labels: List? = null, @SerializedName("milestone") @Embedded(prefix = "milestone_") var milestone: MilestoneModel? = null, - @SerializedName("assignees") var assignees: List? = null + @SerializedName("assignees") var assignees: List? = null, + @SerializedName("headRefName") var headRefName: String? = null, + @SerializedName("baseRefName") var baseRefName: String? = null ) { companion object { const val TABLE_NAME = "pullrequest_table" diff --git a/data/src/main/java/com/fastaccess/data/repository/PullRequestRepositoryProvider.kt b/data/src/main/java/com/fastaccess/data/repository/PullRequestRepositoryProvider.kt index e7365d7e..da8f666b 100644 --- a/data/src/main/java/com/fastaccess/data/repository/PullRequestRepositoryProvider.kt +++ b/data/src/main/java/com/fastaccess/data/repository/PullRequestRepositoryProvider.kt @@ -6,7 +6,6 @@ import com.fastaccess.data.persistence.dao.PullRequestDao import com.fastaccess.data.persistence.models.PullRequestModel import io.reactivex.Maybe import javax.inject.Inject -import javax.inject.Singleton /** * Created by Kosh on 27.01.19. @@ -15,7 +14,7 @@ class PullRequestRepositoryProvider @Inject constructor( private val dao: PullRequestDao ) : PullRequestRepository { - override fun upsert(pr: PullRequestModel) = dao.update(pr) + override fun upsert(pr: PullRequestModel) = dao.upsert(pr) override fun getPullRequests( repo: String, state: String