display commit patch

This commit is contained in:
k0shk0sh 2019-09-21 12:46:51 +02:00
parent e5a2b67361
commit dc0a2263c4
13 changed files with 269 additions and 430 deletions

View File

@ -0,0 +1,19 @@
package com.fastaccess.data.model
data class CommitLinesModel(
var text: String? = null,
var color: Int = 0,
var leftLineNo: Int = 0,
var rightLineNo: Int = 0,
var noNewLine: Boolean = false,
var position: Int = 0,
var hasCommentedOn: Boolean = false
) {
companion object {
const val TRANSPARENT = 0
const val ADDITION = 1
const val DELETION = 2
const val PATCH = 3
}
}

View File

@ -50,6 +50,7 @@ dependencies {
implementation project(':resources')
implementation project(':extensions')
implementation project(':dagger')
implementation project(':markdown')
implementation dependency.kotlin
implementation dependency.supportLibraries
implementation dependency.extrasLibraries

View File

@ -1,10 +0,0 @@
function loadDiff(diff) {
var diffHtml = Diff2Html.getPrettyHtml(diff, {
inputFormat: 'diff',
showFiles: true,
matching: 'none',
outputFormat: 'line-by-line'
});
console.log(diffHtml);
document.getElementById("diff").innerHTML = diffHtml;
}

View File

@ -1,365 +0,0 @@
/*
*
* Diff to HTML (diff2html.css)
* Author: rtfpessoa
*
*/
.d2h-wrapper {
text-align: left;
}
.d2h-file-header {
height: 35px;
padding: 5px 10px;
border-bottom: 1px solid #d8d8d8;
background-color: #f7f7f7;
}
.d2h-file-stats {
display: flex;
margin-left: auto;
font-size: 14px;
}
.d2h-lines-added {
text-align: right;
border: 1px solid #b4e2b4;
border-radius: 5px 0 0 5px;
color: #399839;
padding: 2px;
vertical-align: middle;
}
.d2h-lines-deleted {
text-align: left;
border: 1px solid #e9aeae;
border-radius: 0 5px 5px 0;
color: #c33;
padding: 2px;
vertical-align: middle;
margin-left: 1px;
}
.d2h-file-name-wrapper {
display: flex;
align-items: center;
width: 100%;
font-family: "Source Sans Pro", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 15px;
}
.d2h-file-name {
white-space: nowrap;
text-overflow: ellipsis;
overflow-x: hidden;
}
.d2h-file-wrapper {
border: 1px solid #ddd;
border-radius: 3px;
margin-bottom: 1em;
}
.d2h-diff-table {
width: 100%;
border-collapse: collapse;
font-family: "Menlo", "Consolas", monospace;
font-size: 13px;
}
.d2h-files-diff {
display: block;
width: 100%;
height: 100%;
}
.d2h-file-diff {
overflow-y: hidden;
}
.d2h-file-side-diff {
display: inline-block;
overflow-x: scroll;
overflow-y: hidden;
width: 50%;
margin-right: -4px;
margin-bottom: -8px;
}
.d2h-code-line {
display: inline-block;
white-space: nowrap;
/* Compensate for the absolute positioning of the line numbers */
padding: 0 8em;
}
.d2h-code-side-line {
display: inline-block;
white-space: nowrap;
/* Compensate for the absolute positioning of the line numbers */
padding: 0 4.5em;
}
.d2h-code-line del,
.d2h-code-side-line del {
display: inline-block;
margin-top: -1px;
text-decoration: none;
background-color: #ffb6ba;
border-radius: 0.2em;
}
.d2h-code-line ins,
.d2h-code-side-line ins {
display: inline-block;
margin-top: -1px;
text-decoration: none;
background-color: #97f295;
border-radius: 0.2em;
text-align: left;
}
.d2h-code-line-prefix {
display: inline;
background: none;
padding: 0;
word-wrap: normal;
white-space: pre;
}
.d2h-code-line-ctn {
display: inline;
background: none;
padding: 0;
word-wrap: normal;
white-space: pre;
}
.line-num1 {
box-sizing: border-box;
float: left;
width: 3.5em;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.line-num2 {
box-sizing: border-box;
float: right;
width: 3.5em;
overflow: hidden;
text-overflow: ellipsis;
padding: 0 0.5em 0 0.5em;
}
.d2h-code-linenumber {
box-sizing: border-box;
width: 7.5em;
/* Keep the numbers fixed on line contents scroll */
position: absolute;
display: inline-block;
background-color: #fff;
color: rgba(0, 0, 0, 0.3);
text-align: right;
border: solid #eeeeee;
border-width: 0 1px 0 1px;
cursor: pointer;
}
.d2h-code-linenumber:after {
content: '\200b';
}
.d2h-code-side-linenumber {
/* Keep the numbers fixed on line contents scroll */
position: absolute;
display: inline-block;
box-sizing: border-box;
width: 4em;
background-color: #fff;
color: rgba(0, 0, 0, 0.3);
text-align: right;
border: solid #eeeeee;
border-width: 0 1px 0 1px;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
}
.d2h-code-side-linenumber:after {
content: '\200b';
}
.d2h-code-side-emptyplaceholder,
.d2h-emptyplaceholder {
background-color: #f1f1f1;
border-color: #e1e1e1;
}
/*
* Changes Highlight
*/
.d2h-del {
background-color: #fee8e9;
border-color: #e9aeae;
}
.d2h-ins {
background-color: #dfd;
border-color: #b4e2b4;
}
.d2h-info {
background-color: #f8fafd;
color: rgba(0, 0, 0, 0.3);
border-color: #d5e4f2;
}
.d2h-file-diff .d2h-del.d2h-change {
background-color: #fdf2d0;
}
.d2h-file-diff .d2h-ins.d2h-change {
background-color: #ded;
}
/*
* File Summary List
*/
.d2h-file-list-wrapper {
margin-bottom: 10px;
}
.d2h-file-list-wrapper a {
text-decoration: none;
color: #3572b0;
}
.d2h-file-list-wrapper a:visited {
color: #3572b0;
}
.d2h-file-list-header {
text-align: left;
}
.d2h-file-list-title {
font-weight: bold;
}
.d2h-file-list-line {
display: flex;
text-align: left;
}
.d2h-file-list {
display: block;
list-style: none;
padding: 0;
margin: 0;
}
.d2h-file-list > li {
border-bottom: #ddd solid 1px;
padding: 5px 10px;
margin: 0;
}
.d2h-file-list > li:last-child {
border-bottom: none;
}
.d2h-file-switch {
display: none;
font-size: 10px;
cursor: pointer;
}
.d2h-icon {
vertical-align: middle;
margin-right: 10px;
fill: currentColor;
}
.d2h-deleted {
color: #c33;
}
.d2h-added {
color: #399839;
}
.d2h-changed {
color: #d0b44c;
}
.d2h-moved {
color: #3572b0;
}
.d2h-tag {
display: flex;
font-size: 10px;
margin-left: 5px;
padding: 0 2px;
background-color: #fff;
}
.d2h-deleted-tag {
border: #c33 1px solid;
}
.d2h-added-tag {
border: #399839 1px solid;
}
.d2h-changed-tag {
border: #d0b44c 1px solid;
}
.d2h-moved-tag {
border: #3572b0 1px solid;
}
/*
* Selection util.
*/
.selecting-left .d2h-code-line,
.selecting-left .d2h-code-line *,
.selecting-right td.d2h-code-linenumber,
.selecting-right td.d2h-code-linenumber *,
.selecting-left .d2h-code-side-line,
.selecting-left .d2h-code-side-line *,
.selecting-right td.d2h-code-side-linenumber,
.selecting-right td.d2h-code-side-linenumber * {
-webkit-touch-callout: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.selecting-left .d2h-code-line::-moz-selection,
.selecting-left .d2h-code-line *::-moz-selection,
.selecting-right td.d2h-code-linenumber::-moz-selection,
.selecting-left .d2h-code-side-line::-moz-selection,
.selecting-left .d2h-code-side-line *::-moz-selection,
.selecting-right td.d2h-code-side-linenumber::-moz-selection,
.selecting-right td.d2h-code-side-linenumber *::-moz-selection {
background: transparent;
}
.selecting-left .d2h-code-line::selection,
.selecting-left .d2h-code-line *::selection,
.selecting-right td.d2h-code-linenumber::selection,
.selecting-left .d2h-code-side-line::selection,
.selecting-left .d2h-code-side-line *::selection,
.selecting-right td.d2h-code-side-linenumber::selection,
.selecting-right td.d2h-code-side-linenumber *::selection {
background: transparent;
}

File diff suppressed because one or more lines are too long

View File

@ -1,13 +0,0 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no"/>
<link rel="stylesheet" type="text/css" href="diff2html.css"/>
<script src="diff2html.min.js"></script>
<script src="diff.js"></script>
</head>
<body id="diff">
</body>
</html>

View File

@ -0,0 +1,74 @@
package com.fastaccess.fasthub.diff
import com.fastaccess.data.model.CommitLinesModel
import com.fastaccess.data.model.CommitLinesModel.Companion.ADDITION
import com.fastaccess.data.model.CommitLinesModel.Companion.DELETION
import com.fastaccess.data.model.CommitLinesModel.Companion.PATCH
import com.fastaccess.data.model.CommitLinesModel.Companion.TRANSPARENT
import java.util.*
import java.util.regex.Pattern
import kotlin.math.abs
object CommitLineBuilder {
private val HUNK_TITLE = Pattern.compile("^.*-([0-9]+)(?:,([0-9]+))? \\+([0-9]+)(?:,([0-9]+))?.*$")
fun buildLines(patch: String?): List<CommitLinesModel> {
val models = ArrayList<CommitLinesModel>()
if (!patch.isNullOrEmpty()) {
val split = patch.split("\\r?\\n|\\r".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
if (split.size > 1) {
var leftLineNo = -1
var rightLineNo = -1
var position = 0
for (text in split) {
var _text: String = text
val firstChar = _text[0]
var addLeft = false
var addRight = false
var color = TRANSPARENT
if (_text.startsWith("@@")) {
color = PATCH
val matcher = HUNK_TITLE.matcher(_text.trim { it <= ' ' })
if (matcher.matches()) {
try {
leftLineNo = abs(matcher.group(1)?.toIntOrNull() ?: 0) - 1
rightLineNo = (matcher.group(3)?.toIntOrNull() ?: 0) - 1
} catch (e: NumberFormatException) {
e.printStackTrace()
}
}
} else if (firstChar == '+') {
position++
color = ADDITION
++rightLineNo
addRight = true
addLeft = false
} else if (firstChar == '-') {
position++
color = DELETION
++leftLineNo
addRight = false
addLeft = true
} else {
position++
addLeft = true
addRight = true
++rightLineNo
++leftLineNo
}
val index = _text.indexOf("\\ No newline at end of file")
if (index != -1) {
_text = _text.replace("\\ No newline at end of file", "")
}
models.add(
CommitLinesModel(
_text.replace("\t", ""), color, if (_text.startsWith("@@") || !addLeft) -1 else leftLineNo,
if (_text.startsWith("@@") || !addRight) -1 else rightLineNo, index != -1, position, false
)
)
}
}
}
return models
}
}

View File

@ -5,13 +5,28 @@ import android.content.Intent
import android.os.Bundle
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import com.fastaccess.data.model.CommitLinesModel
import com.fastaccess.data.repository.SchedulerProvider
import com.fastaccess.fasthub.dagger.annotations.ForActivity
import com.fastaccess.fasthub.diff.adapter.CommitLinesAdapter
import com.fastaccess.github.base.BaseActivity
import com.fastaccess.github.base.utils.EXTRA
import io.reactivex.Observable
import io.reactivex.disposables.Disposable
import kotlinx.android.synthetic.main.diff_patch_viewer_layout.*
import javax.inject.Inject
class DiffViewerActivity : BaseActivity() {
@Inject lateinit var schedulerProvider: SchedulerProvider
private var disposable: Disposable? = null
private val adapter by lazy {
CommitLinesAdapter {
//TODO
}
}
override fun layoutRes(): Int = R.layout.diff_patch_viewer_layout
override fun onActivityCreatedWithUser(savedInstanceState: Bundle?) {
@ -22,11 +37,30 @@ class DiffViewerActivity : BaseActivity() {
setNavigationIcon(R.drawable.ic_clear)
setSupportActionBar(this)
}
intent?.getStringExtra(EXTRA)?.let {
webview.loadDiff(it)
diffRecyclerView.adapter = adapter
intent?.getStringExtra(EXTRA)?.let { patch ->
val observable = Observable.fromPublisher<List<CommitLinesModel>> { s ->
runCatching { CommitLineBuilder.buildLines(patch) }
.onSuccess { s.onNext(it) }
.onFailure { s.onError(it) }
s.onComplete()
}
disposable = observable.subscribeOn(schedulerProvider.ioThread())
.observeOn(schedulerProvider.uiThread())
.subscribe({
adapter.submitList(it)
}, {
it.printStackTrace()
finish()
})
} ?: run { finish() }
}
override fun onDestroy() {
disposable?.dispose()
super.onDestroy()
}
companion object {
fun startActivity(@ForActivity context: Context, patch: String) {
context.startActivity(Intent(context, DiffViewerActivity::class.java).apply {

View File

@ -1,35 +0,0 @@
package com.fastaccess.fasthub.diff
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.webkit.WebChromeClient
import android.webkit.WebView
import androidx.webkit.WebViewClientCompat
import timber.log.Timber
class DiffWebView : WebView {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
@SuppressLint("SetJavaScriptEnabled")
fun loadDiff(diff: String) {
settings.apply {
javaScriptEnabled = true
defaultTextEncodingName = "utf-8"
webChromeClient = WebChromeClient()
}
post {
webViewClient = object : WebViewClientCompat() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
Timber.e("here!")
loadUrl("javascript:loadDiff('$diff')")
}
}
loadUrl("file:///android_asset/index.html")
}
}
}

View File

@ -0,0 +1,59 @@
package com.fastaccess.fasthub.diff.adapter
import android.graphics.Color
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import com.fastaccess.data.model.CommitLinesModel
import com.fastaccess.fasthub.diff.R
import com.fastaccess.github.base.adapter.BaseViewHolder
import com.fastaccess.github.extensions.getColorAttr
import com.fastaccess.github.extensions.getDrawableCompat
import com.fastaccess.markdown.widget.SpannableBuilder
import kotlinx.android.synthetic.main.commit_line_row_item.view.*
class CommitLinesAdapter(
private val callback: (CommitLinesModel) -> Unit
) : ListAdapter<CommitLinesModel, CommitLineViewHolder>(object : DiffUtil.ItemCallback<CommitLinesModel?>() {
override fun areItemsTheSame(oldItem: CommitLinesModel, newItem: CommitLinesModel): Boolean = oldItem.position == newItem.position
override fun areContentsTheSame(oldItem: CommitLinesModel, newItem: CommitLinesModel): Boolean = oldItem == newItem
}) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = CommitLineViewHolder(parent).apply {
itemView.setOnClickListener { getItem(adapterPosition)?.let(callback) }
}
override fun onBindViewHolder(holder: CommitLineViewHolder, position: Int) = holder.bind(getItem(position))
}
class CommitLineViewHolder(parent: ViewGroup) : BaseViewHolder<CommitLinesModel>(
LayoutInflater.from(parent.context).inflate(R.layout.commit_line_row_item, parent, false)
) {
override fun bind(item: CommitLinesModel) {
itemView.apply {
leftLinNo.text = if (item.leftLineNo > 0) "${item.leftLineNo}" else " "
rightLinNo.text = if (item.rightLineNo > 0) "${item.rightLineNo}" else " "
hasComment.isVisible = item.hasCommentedOn
when (item.color) {
CommitLinesModel.ADDITION -> textView.setBackgroundColor(context.getColorAttr(R.attr.patch_addition))
CommitLinesModel.DELETION -> textView.setBackgroundColor(context.getColorAttr(R.attr.patch_deletion))
CommitLinesModel.PATCH -> {
leftLinNo.visibility = View.GONE
rightLinNo.visibility = View.GONE
textView.setBackgroundColor(context.getColorAttr(R.attr.patch_ref))
}
else -> textView.setBackgroundColor(Color.TRANSPARENT)
}
if (item.noNewLine) {
textView.text = SpannableBuilder.builder().append(item.text).append(" ")
.append(context.getDrawableCompat(R.drawable.ic_newline))
} else {
textView.setText(item.text)
}
}
}
}

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?selectableItemBackground"
android:orientation="vertical"
android:paddingTop="@dimen/spacing_micro"
android:paddingBottom="@dimen/spacing_micro">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/leftLinNo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:fontFamily="monospace"
android:textColor="?android:textColorSecondary"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="1" />
<View
android:layout_width="@dimen/spacing_normal"
android:layout_height="wrap_content" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/rightLinNo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top"
android:fontFamily="monospace"
android:textColor="?android:textColorSecondary"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
tools:text="1" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacing_micro"
android:fontFamily="monospace"
android:scrollbars="horizontal"
android:scrollHorizontally="true"
android:textAppearance="@style/TextAppearance.AppCompat.Caption"
android:textColor="?android:textColorPrimary"
tools:text="Per guest prepare a handfull teaspoons of soy sauce with squeezed sausages for dessert." />
</LinearLayout>
<View
android:id="@+id/hasComment"
android:layout_width="match_parent"
android:layout_height="0.5dp"
android:layout_marginTop="@dimen/spacing_micro"
android:layout_marginBottom="@dimen/spacing_micro"
android:background="?colorAccent"
android:visibility="gone" />
</LinearLayout>

View File

@ -1,12 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/appbar_center_title_layout" />
<ProgressBar
android:id="@+id/progressBar"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
@ -15,9 +16,14 @@
android:indeterminate="true"
android:visibility="gone" />
<com.fastaccess.fasthub.diff.DiffWebView
android:id="@+id/webview"
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/diffRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="wrap_content"
android:paddingStart="@dimen/spacing_xs_large"
android:paddingEnd="@dimen/spacing_xs_large"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="40"
tools:listitem="@layout/commit_line_row_item" />
</LinearLayout>

View File

@ -3,6 +3,7 @@ package com.fastaccess.domain.services
import com.fastaccess.domain.response.CommentResponse
import com.fastaccess.domain.response.CommitFilesResponseModel
import com.fastaccess.domain.response.body.CommentRequestModel
import io.reactivex.Maybe
import io.reactivex.Observable
import retrofit2.Response
import retrofit2.http.*
@ -15,6 +16,13 @@ interface CommitService {
@Path("sha") ref: String
): Observable<CommitFilesResponseModel>
@Headers("Accept: application/vnd.github.v3.diff")
@GET("repos/{owner}/{repo}/commits/{sha}") fun getCommitDiff(
@Path("owner") owner: String,
@Path("repo") repo: String,
@Path("sha") ref: String
): Maybe<String>
@POST("repos/{owner}/{repo}/commits/{sha}/comments") fun postCommitComment(
@Path("owner") owner: String,
@Path("repo") repo: String,