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 d09ce0c4..ee21c733 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 @@ -80,6 +80,4 @@ abstract class FragmentBindingModule { @PerFragment @ContributesAndroidInjector(modules = [IssueModule::class]) abstract fun provideIssueFragment(): IssueFragment @PerFragment @ContributesAndroidInjector(modules = [EditIssuePrModule::class]) abstract fun provideEditIssuePrFragment(): EditIssuePrFragment @PerFragment @ContributesAndroidInjector(modules = [PullRequestModule::class]) abstract fun providePullRequestFragment(): PullRequestFragment - @PerFragment @ContributesAndroidInjector(modules = [ReviewsModule::class]) abstract fun provideListReviewsFragment(): ReviewsFragment - } \ No newline at end of file diff --git a/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt index 788a9346..c7ba200f 100644 --- a/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt +++ b/app/src/main/java/com/fastaccess/github/ui/adapter/viewholder/ReviewViewHolder.kt @@ -70,7 +70,7 @@ class ReviewViewHolder( association.text = if (CommentAuthorAssociation.NONE == model.authorAssociation) { if (!showReview) { SpannableBuilder.builder() - .bold(_review.state?.replace("_", "")?.toLowerCase()) + .bold(_review.state?.replace("_", " ")?.toLowerCase()) .append(", ") .append(model.createdAt?.timeAgo()) } else { @@ -131,12 +131,12 @@ class ReviewViewHolder( reviewAuthor.text = model.author?.login ?: "" reviewAssociation.text = if (CommentAuthorAssociation.NONE.value == model.authorAssociation) { SpannableBuilder.builder() - .bold(model.state?.replace("_", "")?.toLowerCase()) + .bold(model.state?.replace("_", " ")?.toLowerCase()) .append(", ") .append(model.createdAt?.timeAgo()) } else { SpannableBuilder.builder() - .bold(model.state?.replace("_", "")?.toLowerCase()) + .bold(model.state?.replace("_", " ")?.toLowerCase()) .append(", ") .append(model.authorAssociation?.toLowerCase()?.replace("_", "") ?: "") .append(", ") diff --git a/editor/src/main/java/com/fastaccess/github/editor/presenter/MentionsPresenter.kt b/editor/src/main/java/com/fastaccess/github/editor/presenter/MentionsPresenter.kt index 43727750..8f37c71c 100644 --- a/editor/src/main/java/com/fastaccess/github/editor/presenter/MentionsPresenter.kt +++ b/editor/src/main/java/com/fastaccess/github/editor/presenter/MentionsPresenter.kt @@ -22,10 +22,10 @@ import javax.inject.Inject * Created by Kosh on 2019-07-18. */ class MentionsPresenter @Inject constructor( - c: Context, + context: Context, private val searchUsersUseCase: FilterSearchUsersUseCase, schedulerProvider: SchedulerProvider -) : RecyclerViewPresenter(c) { +) : RecyclerViewPresenter(context) { private var disposable: Disposable? = null private val localList = ArrayList() diff --git a/reviews/src/main/java/com/fastaccess/fasthub/reviews/PullRequestReviewsActivity.kt b/reviews/src/main/java/com/fastaccess/fasthub/reviews/PullRequestReviewsActivity.kt index 571bcb92..61c3ef2a 100644 --- a/reviews/src/main/java/com/fastaccess/fasthub/reviews/PullRequestReviewsActivity.kt +++ b/reviews/src/main/java/com/fastaccess/fasthub/reviews/PullRequestReviewsActivity.kt @@ -1,25 +1,27 @@ package com.fastaccess.fasthub.reviews import android.os.Bundle -import androidx.fragment.app.Fragment import com.fastaccess.github.base.BaseActivity import com.fastaccess.github.base.deeplink.AppDeepLink import com.fastaccess.github.base.utils.IN_APP_LINK import com.fastaccess.github.extensions.replace @AppDeepLink( - "repo/{login}/{repo}/pull/{number}/review/{id}", - "repo/{login}/{repo}/pull/{number}/reviews" + "/repo/{login}/{repo}/pull/{number}/review/{id}", + "/repo/{login}/{repo}/pull/{number}/reviews" ) class PullRequestReviewsActivity : BaseActivity() { private val id by lazy { intent.getStringExtra("id") } + private val login by lazy { intent.getStringExtra("login") ?: throw NullPointerException("no login") } + private val repo by lazy { intent.getStringExtra("repo") ?: throw NullPointerException("no repo") } + private val number by lazy { intent.getStringExtra("number")?.toIntOrNull() ?: throw NullPointerException("no number") } override fun layoutRes(): Int = R.layout.fragment_activity_layout override fun onActivityCreatedWithUser(savedInstanceState: Bundle?) { if (savedInstanceState == null) { - replace(R.id.container, Fragment()) + replace(R.id.container, ReviewsFragment.newInstance(login, repo, number, id)) } } diff --git a/reviews/src/main/java/com/fastaccess/fasthub/reviews/ReviewsFragment.kt b/reviews/src/main/java/com/fastaccess/fasthub/reviews/ReviewsFragment.kt index 61c6ffd7..89d9f9e5 100644 --- a/reviews/src/main/java/com/fastaccess/fasthub/reviews/ReviewsFragment.kt +++ b/reviews/src/main/java/com/fastaccess/fasthub/reviews/ReviewsFragment.kt @@ -1,7 +1,9 @@ package com.fastaccess.fasthub.reviews import android.os.Bundle +import android.text.Editable import android.view.View +import android.widget.EditText import androidx.core.os.bundleOf import androidx.lifecycle.ViewModelProvider import com.fastaccess.data.model.CommentModel @@ -16,9 +18,14 @@ import com.fastaccess.github.base.utils.* import com.fastaccess.github.base.viewmodel.ViewModelProviders import com.fastaccess.github.editor.comment.CommentActivity import com.fastaccess.github.editor.presenter.MentionsPresenter +import com.fastaccess.github.extensions.getDrawableCompat import com.fastaccess.github.extensions.isTrue import com.fastaccess.github.extensions.observeNotNull import com.fastaccess.github.extensions.routeForResult +import com.fastaccess.markdown.MarkdownProvider +import com.otaliastudios.autocomplete.Autocomplete +import com.otaliastudios.autocomplete.AutocompleteCallback +import com.otaliastudios.autocomplete.CharPolicy import io.noties.markwon.Markwon import javax.inject.Inject @@ -43,6 +50,7 @@ class ReviewsFragment : BaseFragment() { override fun layoutRes(): Int = R.layout.reviews_fragment_layout override fun onFragmentCreatedWithUser(view: View, savedInstanceState: Bundle?) { + setupToolbar(R.string.reviews) recyclerView.adapter = adapter recyclerView.setEmptyView(emptyLayout) fastScroller.attachRecyclerView(recyclerView) @@ -60,7 +68,7 @@ class ReviewsFragment : BaseFragment() { } recyclerView.addOnLoadMore { isConnected().isTrue { viewModel.load(login, repo, number) } } - + setupEditText() observeChanges() } @@ -92,6 +100,32 @@ class ReviewsFragment : BaseFragment() { } } + private fun setupEditText() { + val commentText = view?.findViewById(R.id.commentText) ?: return + Autocomplete.on(commentText) + .with(CharPolicy('@')) + .with(mentionsPresenter) + .with(requireContext().getDrawableCompat(R.drawable.popup_window_background)) + .with(object : AutocompleteCallback { + override fun onPopupItemClicked( + editable: Editable?, + item: String? + ): Boolean = MarkdownProvider.replaceMention(editable, item) + + override fun onPopupVisibilityChanged(shown: Boolean) {} + }) + .build() + view?.findViewById(R.id.sendComment)?.setOnClickListener { + val comment = commentText.text?.toString() + if (!comment.isNullOrEmpty()) { + viewModel.createComment(login, repo, number, comment) + } + } + view?.findViewById(R.id.toggleFullScreen)?.setOnClickListener { + routeForResult(EDITOR_DEEP_LINK, COMMENT_REQUEST_CODE, bundleOf(EXTRA to commentText.text?.toString())) + } + } + companion object { const val COMMENT_REQUEST_CODE = 1001 const val EDIT_COMMENT_REQUEST_CODE = 1003 diff --git a/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewCommentViewHolder.kt b/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewCommentViewHolder.kt index 0f17fd6b..42bbe23f 100644 --- a/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewCommentViewHolder.kt +++ b/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewCommentViewHolder.kt @@ -34,17 +34,17 @@ class ReviewCommentViewHolder( @SuppressLint("DefaultLocale") override fun bind(item: CommentModel) { itemView.apply { - fileName.text = item.path if (!item.diffHunk.isNullOrEmpty()) { + fileName.text = item.path diffHunk.text = DiffLineSpan.getSpannable( item.diffHunk, context.getColorAttr(R.attr.patch_addition), context.getColorAttr(R.attr.patch_deletion), context.getColorAttr(R.attr.patch_ref), truncate = true ) - diffHunk.isVisible = true + reviewCommentLayout.isVisible = true } else { - diffHunk.isVisible = false + reviewCommentLayout.isVisible = false } userIcon.loadAvatar(item.author?.avatarUrl, item.author?.url ?: "") author.text = item.author?.login ?: "" diff --git a/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewViewHolder.kt b/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewViewHolder.kt index 6de5f6ca..c8e55f0f 100644 --- a/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewViewHolder.kt +++ b/reviews/src/main/java/com/fastaccess/fasthub/reviews/adapter/ReviewViewHolder.kt @@ -27,50 +27,58 @@ class ReviewViewHolder( @SuppressLint("DefaultLocale") override fun bind(item: ReviewModel) { itemView.apply { + val hasBody = !item.body.isNullOrEmpty() + descriptionLayout.isVisible = hasBody + reviewMenu.isVisible = hasBody reviewUserIcon.loadAvatar(item.author?.avatarUrl, item.author?.url ?: "") reviewAuthor.text = item.author?.login ?: "" reviewAssociation.text = if (CommentAuthorAssociation.NONE.value == item.authorAssociation) { SpannableBuilder.builder() - .bold(item.state?.replace("_", "")?.toLowerCase()) + .bold(item.state?.replace("_", " ")?.toLowerCase()) .append(", ") .append(item.createdAt?.timeAgo()) } else { SpannableBuilder.builder() - .bold(item.state?.replace("_", "")?.toLowerCase()) + .bold(item.state?.replace("_", " ")?.toLowerCase()) .append(", ") .append(item.authorAssociation?.toLowerCase()?.replace("_", "") ?: "") .append(", ") .append(item.createdAt?.timeAgo()) } - reviewDescription.post { - reviewDescription.setSpannableFactory(NoCopySpannableFactory.getInstance()) - val bodyMd = item.body - markwon.setMarkdown(reviewDescription, if (!bodyMd.isNullOrEmpty()) bodyMd else resources.getString(R.string.no_description_provided)) - } - - reviewDescription.setOnTouchListener { v, event -> - if (event.action == MotionEvent.ACTION_UP && !reviewDescription.hasSelection()) { - itemView.callOnClick() + if (hasBody) { + reviewDescription.post { + reviewDescription.setSpannableFactory(NoCopySpannableFactory.getInstance()) + val bodyMd = item.body + markwon.setMarkdown( + reviewDescription, + if (!bodyMd.isNullOrEmpty()) bodyMd else resources.getString(R.string.no_description_provided) + ) } - return@setOnTouchListener false - } - val canAlter = item.viewerCanUpdate == true || item.viewerCanDelete == true - reviewMenu.isVisible = canAlter - if (canAlter) { - reviewMenu.popMenu(R.menu.comment_menu, { menu -> - menu.findItem(R.id.edit)?.isVisible = item.viewerCanUpdate == true - menu.findItem(R.id.delete)?.isVisible = false - }) { itemId -> - if (itemId == R.id.edit) { - editCommentListener.invoke(adapterPosition) + reviewDescription.setOnTouchListener { v, event -> + if (event.action == MotionEvent.ACTION_UP && !reviewDescription.hasSelection()) { + itemView.callOnClick() + } + return@setOnTouchListener false + } + + val canAlter = item.viewerCanUpdate == true || item.viewerCanDelete == true + reviewMenu.isVisible = canAlter + if (canAlter) { + reviewMenu.popMenu(R.menu.comment_menu, { menu -> + menu.findItem(R.id.edit)?.isVisible = item.viewerCanUpdate == true + menu.findItem(R.id.delete)?.isVisible = false + }) { itemId -> + if (itemId == R.id.edit) { + editCommentListener.invoke(adapterPosition) + } } } - } - reviewAdaptiveEmoticon.init(requireNotNull(item.id), item.reactionGroups) { - callback.invoke(adapterPosition) + reviewAdaptiveEmoticon.init(requireNotNull(item.id), item.reactionGroups) { + callback.invoke(adapterPosition) + } } } } diff --git a/reviews/src/main/java/com/fastaccess/fasthub/reviews/usecase/GetReviewsUseCase.kt b/reviews/src/main/java/com/fastaccess/fasthub/reviews/usecase/GetReviewsUseCase.kt index b740c22e..4f01273e 100644 --- a/reviews/src/main/java/com/fastaccess/fasthub/reviews/usecase/GetReviewsUseCase.kt +++ b/reviews/src/main/java/com/fastaccess/fasthub/reviews/usecase/GetReviewsUseCase.kt @@ -41,13 +41,9 @@ class GetReviewsUseCase @Inject constructor( ) val timelineModel = arrayListOf() - val comments = response.nodes?.flatMap { it.comments.nodes?.asIterable() ?: emptyList() } ?: emptyList() - val reviews = response.nodes?.flatMap { it.comments.nodes?.asIterable() ?: emptyList() } - ?.distinctBy { it.pullRequestReview?.id } - ?.mapNotNull { it.pullRequestReview } - ?.sortedBy { it.createdAt } - ?.map { node -> - ReviewModel( + response.nodes?.forEach { + it.comments.nodes?.firstOrNull()?.pullRequestReview?.let { node -> + timelineModel.add(TimelineModel(review = ReviewModel( node.id, node.databaseId, ShortUserModel( @@ -67,32 +63,39 @@ class GetReviewsUseCase @Inject constructor( node.isViewerDidAuthor, false, node.reactionGroups?.map { it.fragments.reactions.toReactionGroup() } - ) - } ?: arrayListOf() - - reviews.forEach { review -> - timelineModel.add(TimelineModel(review = review)) - timelineModel.addAll(comments.filter { it.pullRequestReview?.id == review.id } - .sortedBy { it.createdAt } - .map { + ))) + } + timelineModel.addAll(it.comments.nodes + ?.mapIndexedNotNull { index, node1 -> TimelineModel( comment = CommentModel( - it.id, - it.databaseId, + node1.id, + node1.databaseId, ShortUserModel( - it.author?.login, - it.author?.login, - it.author?.url?.toString(), - avatarUrl = it.author?.avatarUrl?.toString() + node1.author?.login, + node1.author?.login, + node1.author?.url?.toString(), + avatarUrl = node1.author?.avatarUrl?.toString() ), - it.body, CommentAuthorAssociation.fromName(it.authorAssociation.rawValue()), - it.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, - it.updatedAt, it.updatedAt, it.isViewerCanReact, it.isViewerCanDelete, - it.isViewerCanUpdate, it.isViewerDidAuthor, false, - it.path, it.originalPosition, it.isOutdated, it.diffHunk + node1.body, + CommentAuthorAssociation.fromName(node1.authorAssociation.rawValue()), + node1.reactionGroups?.map { it.fragments.reactions.toReactionGroup() }, + node1.updatedAt, + node1.updatedAt, + node1.isViewerCanReact, + node1.isViewerCanDelete, + node1.isViewerCanUpdate, + node1.isViewerDidAuthor, + false, + if (index == 0) node1.path else null, + if (index == 0) node1.originalPosition else null, + node1.isOutdated, + if (index == 0) node1.diffHunk else + null ) ) - }) + } ?: arrayListOf() + ) } return@map Pair(pageInfo, timelineModel) } diff --git a/reviews/src/main/res/layout/review_comment_row_item.xml b/reviews/src/main/res/layout/review_comment_row_item.xml index dbf23315..9910abda 100644 --- a/reviews/src/main/res/layout/review_comment_row_item.xml +++ b/reviews/src/main/res/layout/review_comment_row_item.xml @@ -5,10 +5,6 @@ style="@style/CardViewStyle" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacing_xs_large" - android:layout_marginTop="@dimen/spacing_micro" - android:layout_marginEnd="@dimen/spacing_xs_large" - android:layout_marginBottom="@dimen/spacing_micro" android:foreground="?selectableItemBackground"> - + + + + + android:orientation="vertical"> + android:layout_marginBottom="@dimen/spacing_normal" + android:orientation="horizontal"> + + - - - - - - - - - - - - - - - + android:layout_weight="1" + android:orientation="vertical"> - + + + + + + + android:layout_gravity="center" + android:background="?selectableItemBackgroundBorderless" + android:src="@drawable/ic_overflow" /> - + - + + + + diff --git a/reviews/src/main/res/layout/review_row_item.xml b/reviews/src/main/res/layout/review_row_item.xml index 1ca7d8b3..1ff6dfec 100644 --- a/reviews/src/main/res/layout/review_row_item.xml +++ b/reviews/src/main/res/layout/review_row_item.xml @@ -6,9 +6,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/spacing_xs_large" - android:layout_marginTop="@dimen/spacing_micro" + android:layout_marginTop="@dimen/spacing_normal" android:layout_marginEnd="@dimen/spacing_xs_large" - android:layout_marginBottom="@dimen/spacing_micro" + android:layout_marginBottom="@dimen/spacing_normal" android:foreground="?selectableItemBackground"> - - - - - + android:orientation="vertical"> + + + + +