* Create app-samples-CI.yml

* Clean up SourceEditor

* Clean up PhotoEditor

* Clean up Widget and add to CI build
This commit is contained in:
Kristen Halper 2020-07-30 13:50:07 -04:00 коммит произвёл GitHub
Родитель 7fd01d5c91
Коммит df56bd9356
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
20 изменённых файлов: 212 добавлений и 141 удалений

112
.github/workflows/app-samples-CI.yml поставляемый Normal file
Просмотреть файл

@ -0,0 +1,112 @@
# This workflow will build a Java project with Gradle
# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle
name: App samples CI
on:
push:
branches: [ main, hero_notes, add-ci ]
pull_request:
branches: [ main, hero_notes ]
workflow_dispatch:
inputs:
name:
description: 'manual build trigger'
home:
description: 'location'
required: false
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK 1.8
uses: actions/setup-java@v1
with:
java-version: 1.8
- name: Cache SourceEditor Gradle packages
uses: actions/cache@v2
with:
path: SourceEditor/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Grant execute permission for SourceEditor gradlew
run: chmod +x SourceEditor/gradlew
- name: clean SourceEditor
run: |
cd SourceEditor
./gradlew clean --info
- name: assemble debug SourceEditor
run: |
cd SourceEditor
./gradlew assembleDebug
- name: unit tests SourceEditor
run: |
cd SourceEditor
./gradlew testDebugUnitTest
- name: lint SourceEditor
run: |
cd SourceEditor
./gradlew lintDebug
- name: ktlint SourceEditor
run: |
cd SourceEditor
./gradlew ktlint
- name: Cache PhotoEditor Gradle packages
uses: actions/cache@v2
with:
path: PhotoEditor/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Grant execute permission for PhotoEditor gradlew
run: chmod +x PhotoEditor/gradlew
- name: clean PhotoEditor
run: |
cd PhotoEditor
./gradlew clean
- name: assemble debug PhotoEditor
run: |
cd PhotoEditor
./gradlew assembleDebug
- name: unit tests PhotoEditor
run: |
cd PhotoEditor
./gradlew testDebugUnitTest
- name: lint PhotoEditor
run: |
cd PhotoEditor
./gradlew lintDebug
- name: ktlint PhotoEditor
run: |
cd PhotoEditor
./gradlew ktlint
- name: Cache Widget Gradle packages
uses: actions/cache@v2
with:
path: Widget/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- name: Grant execute permission for Widget gradlew
run: chmod +x Widget/gradlew
- name: clean Widget
run: |
cd Widget
./gradlew clean
- name: assemble debug Widget
run: |
cd Widget
./gradlew assembleDebug
- name: unit tests Widget
run: |
cd Widget
./gradlew testDebugUnitTest
- name: lint Widget
run: |
cd Widget
./gradlew lintDebug
- name: ktlint Widget
run: |
cd Widget
./gradlew ktlint

Просмотреть файл

@ -19,11 +19,11 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.microsoft.device.dualscreen.layout.ScreenHelper
import org.hamcrest.CoreMatchers.`is` as iz
import org.hamcrest.CoreMatchers.not
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.hamcrest.CoreMatchers.`is` as iz
/**
* Instrumented test, which will execute on an Android device.
@ -164,15 +164,15 @@ class PhotoEditorUITest {
val device: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
// Swipe constants
const val leftX: Int = 675 // middle of left screen
const val rightX: Int = 2109 // middle of right screen
const val middleX: Int = 1350 // hinge area
const val bottomY: Int = 1780 // bottom of screen
const val middleY: Int = 900 // middle of screen
const val spanSteps: Int = 400 // spanning swipe
const val unspanSteps: Int = 200 // unspanning swipe
const val switchSteps: Int = 100 // swipe to switch from one screen to the other
const val closeSteps: Int = 50 // swipe to close app
const val leftX: Int = 675 // middle of left screen
const val rightX: Int = 2109 // middle of right screen
const val middleX: Int = 1350 // hinge area
const val bottomY: Int = 1780 // bottom of screen
const val middleY: Int = 900 // middle of screen
const val spanSteps: Int = 400 // spanning swipe
const val unspanSteps: Int = 200 // unspanning swipe
const val switchSteps: Int = 100 // swipe to switch from one screen to the other
const val closeSteps: Int = 50 // swipe to close app
}
private fun spanFromLeft() {

Просмотреть файл

@ -1,3 +1,5 @@
![App samples CI](https://github.com/microsoft/surface-duo-app-samples/workflows/App%20samples%20CI/badge.svg)
# Surface Duo app samples
This repo contains sample Android applications for Microsoft Surface Duo.

Просмотреть файл

@ -7,7 +7,8 @@
package com.microsoft.device.display.samples.sourceeditor
import androidx.test.espresso.Espresso.onView
import androidx.test.espresso.action.ViewActions.*
import androidx.test.espresso.action.ViewActions.click
import androidx.test.espresso.action.ViewActions.replaceText
import androidx.test.espresso.matcher.ViewMatchers.withId
import androidx.test.espresso.web.assertion.WebViewAssertions.webMatches
import androidx.test.espresso.web.sugar.Web.onWebView
@ -52,7 +53,7 @@ class InteractiveTest {
@Test
fun textPreservedOnSpanTest() {
onView(withId(R.id.textinput_code)).perform(replaceText("<h1>" + testString + "</h1>"))
onView(withId(R.id.textinput_code)).perform(replaceText("<h1>$testString</h1>"))
spanFromLeft()
assert(isSpanned())
onWebView()
@ -89,24 +90,31 @@ class InteractiveTest {
const val testString = "Testing in a different browser"
}
private fun spanFromLeft() {
device.swipe(leftX, bottomY, leftMiddleX, middleY, spanSteps)
}
private fun unspanToLeft() {
device.swipe(rightX, bottomY, leftX, middleY, spanSteps)
}
private fun spanFromRight() {
device.swipe(rightX, bottomY, rightMiddleX, middleY, spanSteps)
}
private fun unspanToRight() {
device.swipe(leftX, bottomY, rightX, middleY, spanSteps)
}
private fun switchToLeft() {
device.swipe(rightX, bottomY, leftX, middleY, switchSteps)
}
private fun switchToRight() {
device.swipe(leftX, bottomY, rightX, middleY, switchSteps)
}
private fun isSpanned(): Boolean {
return ScreenHelper.isDualMode(activityRule.activity)
}

Просмотреть файл

@ -44,9 +44,9 @@ class CodeFragment : Fragment() {
private lateinit var scrollVM: ScrollViewModel
private lateinit var webVM: WebViewModel
private var scrollingBuffer : Int = Defines.DEFAULT_BUFFER_SIZE
private var scrollRange : Int = Defines.DEFAULT_RANGE
private var rangeFound : Boolean = false
private var scrollingBuffer: Int = Defines.DEFAULT_BUFFER_SIZE
private var scrollRange: Int = Defines.DEFAULT_RANGE
private var rangeFound: Boolean = false
override fun onCreateView(
inflater: LayoutInflater,
@ -83,7 +83,7 @@ class CodeFragment : Fragment() {
}
// read from a base file in the assets folder
private fun readFile(file : String, context : Context?) : String {
private fun readFile(file: String, context: Context?): String {
return BufferedReader(InputStreamReader(context?.assets?.open(file))).useLines { lines ->
val results = StringBuilder()
lines.forEach { results.append(it + System.getProperty("line.separator")) }
@ -92,7 +92,7 @@ class CodeFragment : Fragment() {
}
// mirror scrolling logic
private fun handleScrolling (observing: Boolean, int: Int) {
private fun handleScrolling(observing: Boolean, int: Int) {
if (!rangeFound) {
calibrateScrollView()
} else {
@ -138,12 +138,12 @@ class CodeFragment : Fragment() {
rangeFound = false
// set event and data listeners
scrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
scrollView.setOnScrollChangeListener { _, _, scrollY, _, _ ->
handleScrolling(false, scrollY)
}
scrollVM.getScroll().observe(requireActivity(), Observer { state ->
if (!state.scrollKey.equals(Defines.CODE_KEY)) {
if (state.scrollKey != Defines.CODE_KEY) {
handleScrolling(true, state.scrollPercentage)
}
})
@ -152,12 +152,12 @@ class CodeFragment : Fragment() {
// method that triggers transition to preview fragment
private fun startPreviewFragment() {
parentFragmentManager.beginTransaction()
.replace(
R.id.single_screen_container_id,
PreviewFragment(),
null
).addToBackStack(null)
.commit()
.replace(
R.id.single_screen_container_id,
PreviewFragment(),
null
).addToBackStack(null)
.commit()
}
// listener for changes to text in code editor
@ -177,7 +177,7 @@ class CodeFragment : Fragment() {
// get bounds of scroll window
private fun calibrateScrollView() {
if (scrollView.scrollY > Defines.MIN_RANGE_THRESHOLD) {
scrollRange = scrollView.scrollY // successfully calibrated
scrollRange = scrollView.scrollY // successfully calibrated
rangeFound = true
} else {
scrollView.fullScroll(View.FOCUS_DOWN)
@ -208,13 +208,13 @@ class CodeFragment : Fragment() {
val handler = DragHandler(requireActivity(), webVM, requireActivity().contentResolver)
// Main target will trigger when textField has content
textField.setOnDragListener { v, event ->
handler.onDrag(v, event)
textField.setOnDragListener { _, event ->
handler.onDrag(event)
}
// Sub target will trigger when textField is empty
codeLayout.setOnDragListener { v, event ->
handler.onDrag(v, event)
codeLayout.setOnDragListener { _, event ->
handler.onDrag(event)
}
}
}

Просмотреть файл

@ -23,7 +23,7 @@ class MainActivity : AppCompatActivity() {
private lateinit var fileHandler: FileHandler
private lateinit var webVM: WebViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@ -48,20 +48,17 @@ class MainActivity : AppCompatActivity() {
}
}
override fun onActivityResult(
requestCode: Int, resultCode: Int, resultData: Intent?) {
override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
super.onActivityResult(requestCode, resultCode, resultData)
// request to save a file has been made, add data to newly created file
if (requestCode == fileHandler.CREATE_FILE
&& resultCode == Activity.RESULT_OK) {
if (requestCode == FileHandler.CREATE_FILE && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
fileHandler.alterDocument(uri)
}
}
// request to load file contents has been made, process the file's contents
else if (requestCode == fileHandler.PICK_TXT_FILE
&& resultCode == Activity.RESULT_OK) {
else if (requestCode == FileHandler.PICK_TXT_FILE && resultCode == Activity.RESULT_OK) {
resultData?.data?.also { uri ->
fileHandler.processFileData(uri)
}

Просмотреть файл

@ -38,14 +38,14 @@ class PreviewFragment : Fragment() {
private lateinit var scrollVM: ScrollViewModel
private lateinit var webVM: WebViewModel
private var scrollingBuffer : Int = Defines.DEFAULT_BUFFER_SIZE
private var scrollRange : Int = Defines.DEFAULT_RANGE
private var rangeFound : Boolean = false
private var scrollingBuffer: Int = Defines.DEFAULT_BUFFER_SIZE
private var scrollRange: Int = Defines.DEFAULT_RANGE
private var rangeFound: Boolean = false
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_preview, container, false)
@ -54,12 +54,12 @@ class PreviewFragment : Fragment() {
webView.settings.javaScriptEnabled = true
webView.webChromeClient = WebChromeClient()
activity?.let { activity ->
activity?.let {
// initialize ViewModels (find existing or create a new one)
scrollVM = ViewModelProvider(requireActivity()).get(ScrollViewModel::class.java)
webVM = ViewModelProvider(requireActivity()).get(WebViewModel::class.java)
val str : String? = (webVM.getText().value)
val str: String? = (webVM.getText().value)
webView.loadData(str, Defines.HTML_TYPE, Defines.ENCODING)
handleSpannedModeSelection(view, webView)
@ -69,11 +69,10 @@ class PreviewFragment : Fragment() {
}
// mirror scrolling logic
private fun handleScrolling (observing: Boolean, int: Int) {
private fun handleScrolling(observing: Boolean, int: Int) {
if (!rangeFound) {
calibrateScrollView()
}
else {
} else {
// code window scrolled, auto scroll to match editor
if (observing) {
autoScroll(int)
@ -95,9 +94,8 @@ class PreviewFragment : Fragment() {
})
if (ScreenHelper.isDualMode(activity)) {
initializeDualScreen(view, webView)
}
else {
initializeDualScreen(view)
} else {
initializeSingleScreen()
}
}
@ -114,7 +112,7 @@ class PreviewFragment : Fragment() {
}
// spanned selection helper
private fun initializeDualScreen(view: View, webView: WebView) {
private fun initializeDualScreen(view: View) {
scrollingBuffer = Defines.DEFAULT_BUFFER_SIZE
scrollRange = Defines.DEFAULT_RANGE
rangeFound = false
@ -123,12 +121,12 @@ class PreviewFragment : Fragment() {
// set event and data listeners
scrollView = view.findViewById(R.id.scrollview_preview)
scrollView.setOnScrollChangeListener { v, scrollX, scrollY, oldScrollX, oldScrollY ->
scrollView.setOnScrollChangeListener { _, _, scrollY, _, _ ->
handleScrolling(false, scrollY)
}
scrollVM.getScroll().observe(requireActivity(), Observer { state ->
if (!state.scrollKey.equals(Defines.PREVIEW_KEY)) {
if (state.scrollKey != Defines.PREVIEW_KEY) {
handleScrolling(true, state.scrollPercentage)
}
})
@ -137,18 +135,18 @@ class PreviewFragment : Fragment() {
// method that triggers transition to code fragment
private fun startCodeFragment() {
parentFragmentManager.beginTransaction()
.replace(
R.id.single_screen_container_id,
CodeFragment(),
null
).addToBackStack(null)
.commit()
.replace(
R.id.single_screen_container_id,
CodeFragment(),
null
).addToBackStack(null)
.commit()
}
// get bounds of scroll window
private fun calibrateScrollView() {
if (scrollView.scrollY > Defines.MIN_RANGE_THRESHOLD) {
scrollRange = scrollView.scrollY // successfully calibrated
scrollRange = scrollView.scrollY // successfully calibrated
rangeFound = true
} else {
scrollView.fullScroll(View.FOCUS_DOWN)
@ -179,8 +177,8 @@ class PreviewFragment : Fragment() {
val handler = DragHandler(requireActivity(), webVM, requireActivity().contentResolver)
val target = webView
target.setOnDragListener { v, event ->
handler.onDrag(v, event)
target.setOnDragListener { _, event ->
handler.onDrag(event)
}
}
}

Просмотреть файл

@ -10,21 +10,18 @@ package com.microsoft.device.display.samples.sourceeditor.includes
import android.app.Activity
import android.content.ContentResolver
import android.view.DragEvent
import android.view.View
import com.microsoft.device.display.samples.sourceeditor.viewmodel.WebViewModel
/* Class used to update app contents when an appropriate drag event occurs */
class DragHandler (val activity: Activity,
val webVM: WebViewModel,
val contentResolver: ContentResolver) {
class DragHandler(activity: Activity, webVM: WebViewModel, contentResolver: ContentResolver) {
val fileHandler = FileHandler(activity, webVM, contentResolver)
private val fileHandler = FileHandler(activity, webVM, contentResolver)
// drag occurred, decide if event is relevant to app
fun onDrag(v: View, event: DragEvent): Boolean {
fun onDrag(event: DragEvent): Boolean {
val action = event.action
val isText = event.clipDescription?.getMimeType(0)
.toString().startsWith(Defines.TEXT_PREFIX)
.toString().startsWith(Defines.TEXT_PREFIX)
return when (action) {
DragEvent.ACTION_DRAG_STARTED -> isText
@ -35,7 +32,7 @@ class DragHandler (val activity: Activity,
}
DragEvent.ACTION_DRAG_ENTERED, DragEvent.ACTION_DRAG_LOCATION,
DragEvent.ACTION_DRAG_ENDED, DragEvent.ACTION_DRAG_EXITED ->
DragEvent.ACTION_DRAG_ENDED, DragEvent.ACTION_DRAG_EXITED ->
// Ignore events
return true

Просмотреть файл

@ -14,17 +14,25 @@ import android.net.Uri
import android.provider.DocumentsContract
import androidx.core.app.ActivityCompat.startActivityForResult
import com.microsoft.device.display.samples.sourceeditor.viewmodel.WebViewModel
import java.io.*
import java.io.BufferedReader
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.InputStreamReader
import java.io.IOException
import java.nio.charset.Charset
/* Class used to make file read/write requests */
class FileHandler (val activity: Activity,
val webVM: WebViewModel,
val contentResolver: ContentResolver) {
class FileHandler(
private val activity: Activity,
private val webVM: WebViewModel,
private val contentResolver: ContentResolver
) {
// intent request codes
val CREATE_FILE = 1
val PICK_TXT_FILE = 2
companion object {
// intent request codes
const val CREATE_FILE = 1
const val PICK_TXT_FILE = 2
}
// create a window prompting user to save a new file
// defaults to public Downloads folder if uri is empty
@ -72,11 +80,11 @@ class FileHandler (val activity: Activity,
fun alterDocument(uri: Uri) {
try {
contentResolver.openFileDescriptor(uri, "w")?.use {
FileOutputStream(it.fileDescriptor).use {
FileOutputStream(it.fileDescriptor).use { stream ->
val charset: Charset = Charsets.UTF_8
it.write(
webVM.getText().value.toString()
.toByteArray(charset)
stream.write(
webVM.getText().value.toString()
.toByteArray(charset)
)
}
}
@ -95,7 +103,7 @@ class FileHandler (val activity: Activity,
var initHeader = true
val lines = str.split("<")
lines.forEach{
lines.forEach {
if (initHeader) {
builder.append(it)
initHeader = false

Просмотреть файл

@ -8,6 +8,6 @@
package com.microsoft.device.display.samples.sourceeditor.viewmodel
/* Class used to define a key, value pair for scrolling data */
data class ScrollState (val scrollKey: String, val scrollPercentage: Int) {
data class ScrollState(val scrollKey: String, val scrollPercentage: Int) {
// empty body
}

Просмотреть файл

@ -23,4 +23,3 @@ class WebViewModel : ViewModel() {
return webFormState
}
}

Просмотреть файл

@ -1,40 +0,0 @@
<!--
~ Copyright (c) Microsoft Corporation. All rights reserved.
~ Licensed under the MIT License.
~
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillType="evenOdd"
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
android:strokeWidth="1"
android:strokeColor="#00000000">
<aapt:attr name="android:fillColor">
<gradient
android:endX="78.5885"
android:endY="90.9159"
android:startX="48.7653"
android:startY="61.0927"
android:type="linear">
<item
android:color="#44000000"
android:offset="0.0" />
<item
android:color="#00000000"
android:offset="1.0" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#FFFFFF"
android:fillType="nonZero"
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
android:strokeWidth="1"
android:strokeColor="#00000000" />
</vector>

Просмотреть файл

@ -10,7 +10,6 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
android:orientation="vertical">
<androidx.appcompat.widget.Toolbar
@ -39,7 +38,7 @@
android:id="@+id/btn_save"
android:layout_width="56dp"
android:layout_height="56dp"
android:paddingRight="15dp"
android:paddingEnd="15dp"
app:layout_constraintBottom_toBottomOf="@+id/list_toolbar"
app:layout_constraintEnd_toStartOf="@+id/btn_file"
app:layout_constraintTop_toTopOf="@+id/list_toolbar"

Просмотреть файл

@ -1,16 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) Microsoft Corporation. All rights reserved.
~ Licensed under the MIT License.
~
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@color/white">
android:background="@color/white"
android:orientation="vertical">
<LinearLayout
android:id="@+id/button_toolbar"

Просмотреть файл

@ -6,5 +6,4 @@
-->
<resources>
<dimen name="text_size">28sp</dimen>
</resources>

Просмотреть файл

@ -12,8 +12,4 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="DarkerGrayTheme" parent="AppTheme">
<item name="android:background">@android:color/darker_gray</item>
</style>
</resources>

Двоичные данные
SourceEditor/gradle/wrapper/gradle-wrapper.jar поставляемый Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -61,8 +61,6 @@ class WidgetApp : AppWidgetProvider() {
getPendingSelfIntent(context, ACTION_UPDATE)
)
val number = 10
// Adding settings button logic for every widget instance
views.setOnClickPendingIntent(
R.id.widgetSettingsButton,

Просмотреть файл

@ -34,7 +34,7 @@
android:textSize="14sp"
android:gravity="center_vertical"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/widgetLogo"
android:layout_toEndOf="@+id/widgetLogo"
android:textAllCaps="true" />
<ImageView

Двоичные данные
Widget/gradle/wrapper/gradle-wrapper.jar поставляемый Normal file

Двоичный файл не отображается.