mirror of
https://github.com/M4TH1EU/DJI-FCC-HACK.git
synced 2025-07-05 11:03:19 +00:00
fixed serial connection, improved ui, improve error handling, ready for release
This commit is contained in:
parent
0942019869
commit
27204471c7
@ -23,16 +23,15 @@
|
|||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.Djiffchack">
|
android:theme="@style/Theme.Djiffchack">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<!-- <intent-filter>-->
|
<intent-filter>
|
||||||
<!-- <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>-->
|
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
|
||||||
<!-- </intent-filter>-->
|
</intent-filter>
|
||||||
<!-- <meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter"/>-->
|
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter"/>
|
||||||
</activity>
|
</activity>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
package ch.mathieubroillet.djiffchack
|
package ch.mathieubroillet.djiffchack
|
||||||
|
|
||||||
class Constants {
|
|
||||||
companion object {
|
object Constants {
|
||||||
// const val INTENT_ACTION_GRANT_USB_ACCESSORY = "ch.mathieubroillet.djiffchack.USB_ACCESSORY_PERMISSION"
|
private const val PACKAGE = "ch.mathieubroillet.djiffchack"
|
||||||
// const val INTENT_ACTION_GRANT_USB_DEVICE = "ch.mathieubroillet.djiffchack.USB_DEVICE_PERMISSION"
|
const val INTENT_ACTION_GRANT_USB_PERMISSION = "$PACKAGE.USB_PERMISSION"
|
||||||
const val INTENT_ACTION_GRANT_USB_PERMISSION = "ch.mathieubroillet.djiffchack.USB_PERMISSION"
|
|
||||||
}
|
// The "magic bytes" that enable FCC mode
|
||||||
|
// The credits goes to @galbb from https://mavicpilots.com/threads/mavic-air-2-switch-to-fcc-mode-using-an-android-app.115027/
|
||||||
|
val BYTES_1 = byteArrayOf(85, 13, 4, 33, 42, 31, 0, 0, 0, 0, 1, -122, 32)
|
||||||
|
val BYTES_2 = byteArrayOf(85, 24, 4, 32, 2, 9, 0, 0, 64, 9, 39, 0, 2, 72, 0, -1, -1, 2, 0, 0, 0, 0, -127, 31)
|
||||||
|
|
||||||
|
const val GITHUB_URL = "https://github.com/M4TH1EU/DJI-FCC-HACK"
|
||||||
}
|
}
|
@ -6,7 +6,6 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.IntentFilter
|
import android.content.IntentFilter
|
||||||
import android.hardware.usb.UsbDevice
|
import android.hardware.usb.UsbDevice
|
||||||
import android.hardware.usb.UsbDeviceConnection
|
|
||||||
import android.hardware.usb.UsbManager
|
import android.hardware.usb.UsbManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -25,12 +24,13 @@ import androidx.compose.foundation.layout.height
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Build
|
import androidx.compose.material.icons.filled.Build
|
||||||
import androidx.compose.material.icons.filled.CheckCircle
|
import androidx.compose.material.icons.filled.CheckCircle
|
||||||
import androidx.compose.material.icons.filled.Clear
|
import androidx.compose.material.icons.filled.Clear
|
||||||
import androidx.compose.material.icons.filled.MoreVert
|
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
@ -49,7 +49,10 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalUriHandler
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
@ -66,7 +69,7 @@ import kotlinx.coroutines.launch
|
|||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var usbManager: UsbManager
|
private lateinit var usbManager: UsbManager
|
||||||
private var usbConnection by mutableStateOf(null as UsbDeviceConnection?)
|
private var usbConnected by mutableStateOf(false)
|
||||||
private var isPatching by mutableStateOf(false)
|
private var isPatching by mutableStateOf(false)
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -81,10 +84,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
addAction(Constants.INTENT_ACTION_GRANT_USB_PERMISSION)
|
addAction(Constants.INTENT_ACTION_GRANT_USB_PERMISSION)
|
||||||
}
|
}
|
||||||
ContextCompat.registerReceiver(
|
ContextCompat.registerReceiver(
|
||||||
this,
|
this, usbReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED
|
||||||
usbReceiver,
|
|
||||||
filter,
|
|
||||||
ContextCompat.RECEIVER_NOT_EXPORTED
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initial check for USB connection
|
// Initial check for USB connection
|
||||||
@ -92,7 +92,7 @@ class MainActivity : ComponentActivity() {
|
|||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
DJI_FCC_HACK_Theme {
|
DJI_FCC_HACK_Theme {
|
||||||
MainScreen(usbConnection != null, ::refreshUsbConnection, ::sendPatch, isPatching)
|
MainScreen(usbConnected, ::refreshUsbConnection, ::sendPatch, isPatching)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,66 +112,69 @@ class MainActivity : ComponentActivity() {
|
|||||||
// Check to be sure the device is the initialized DJI Remote (and not another USB device)
|
// Check to be sure the device is the initialized DJI Remote (and not another USB device)
|
||||||
if (device.productId != 4128) {
|
if (device.productId != 4128) {
|
||||||
Log.d("USB_CONNECTION", "Device not supported ${device.productId}")
|
Log.d("USB_CONNECTION", "Device not supported ${device.productId}")
|
||||||
|
usbConnected = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
usbConnection = usbManager.openDevice(device)
|
if (usbManager.openDevice(device) == null) {
|
||||||
|
|
||||||
|
|
||||||
if (usbConnection == null) {
|
|
||||||
Log.d("USB_CONNECTION", "Requesting USB Permission")
|
Log.d("USB_CONNECTION", "Requesting USB Permission")
|
||||||
requestUsbPermission(device)
|
requestUsbPermission(device)
|
||||||
|
} else {
|
||||||
|
usbConnected = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
usbConnection = null
|
usbConnected = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles sending the patch via USB communication
|
* Sends the FCC patch to the DJI remote via USB
|
||||||
*/
|
*/
|
||||||
private fun sendPatch() {
|
private fun sendPatch(): Boolean {
|
||||||
isPatching = true
|
// At this point, we assume the USB device is connected and we have permission to access it
|
||||||
|
if (!usbConnected) {
|
||||||
if (usbConnection == null) {
|
|
||||||
Toast.makeText(this, "No USB device connected!", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "No USB device connected!", Toast.LENGTH_SHORT).show()
|
||||||
isPatching = false
|
return false
|
||||||
return
|
}
|
||||||
|
|
||||||
|
val probeTable = ProbeTable().apply {
|
||||||
|
addProduct(11427, 4128, CdcAcmSerialDriver::class.java)
|
||||||
|
|
||||||
|
// TODO: not sure which device this is, might be the DJI remote before it's connected
|
||||||
|
// to drone it seems to use a different device once connected to the drone
|
||||||
|
// addProduct(5840, 2174, CdcAcmSerialDriver::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve the custom device (DJI remote) with the correct driver from the probe table above
|
||||||
|
val driver = UsbSerialProber(probeTable).probeDevice(usbManager.deviceList.values.first())
|
||||||
|
val deviceConnection = usbManager.openDevice(driver.device)
|
||||||
|
if (deviceConnection == null) {
|
||||||
|
Log.e("USB_PATCH", "Error opening USB device")
|
||||||
|
Toast.makeText(this, "Error opening USB device", Toast.LENGTH_SHORT).show()
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
for (device in usbManager.deviceList.values) {
|
|
||||||
try {
|
try {
|
||||||
val probeTable = ProbeTable()
|
val deviceSerialPort = driver.ports.firstOrNull()
|
||||||
probeTable.addProduct(11427, 4128, CdcAcmSerialDriver::class.java)
|
if (deviceSerialPort == null) {
|
||||||
probeTable.addProduct(5840, 2174, CdcAcmSerialDriver::class.java)
|
Log.e("USB_PATCH", "Error opening USB port")
|
||||||
|
Toast.makeText(this, "Error opening USB port", Toast.LENGTH_SHORT).show()
|
||||||
val usbSerialProber = UsbSerialProber(probeTable)
|
return false
|
||||||
val usbSerialPort = usbSerialProber.probeDevice(device).ports.firstOrNull()
|
|
||||||
|
|
||||||
if (usbSerialPort == null) {
|
|
||||||
Toast.makeText(this, "No serial port found", Toast.LENGTH_SHORT).show()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
usbSerialPort.open(usbConnection)
|
deviceSerialPort.open(deviceConnection)
|
||||||
usbSerialPort.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE)
|
deviceSerialPort.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE)
|
||||||
usbSerialPort.write(
|
deviceSerialPort.write(Constants.BYTES_1, 1000)
|
||||||
byteArrayOf(85, 13, 4, 33, 42, 31, 0, 0, 0, 0, 1, -122, 32),
|
deviceSerialPort.write(Constants.BYTES_2, 1000)
|
||||||
1000
|
|
||||||
)
|
Toast.makeText(this, "Patched successfully", Toast.LENGTH_LONG).show()
|
||||||
usbSerialPort.write(
|
|
||||||
byteArrayOf(
|
|
||||||
85, 24, 4, 32, 2, 9, 0, 0, 64, 9, 39, 0, 2, 72, 0, -1, -1, 2, 0, 0, 0, 0,
|
|
||||||
-127, 31
|
|
||||||
), 1000
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("USB_PATCH", "Error sending patch: ${e.message}")
|
e.printStackTrace()
|
||||||
Toast.makeText(this, "Patch failed: ${e.message}", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isPatching = false
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -221,32 +224,26 @@ class MainActivity : ComponentActivity() {
|
|||||||
fun MainScreen(
|
fun MainScreen(
|
||||||
usbConnected: Boolean,
|
usbConnected: Boolean,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onSendPatch: () -> Unit,
|
onSendPatch: () -> Boolean,
|
||||||
isPatching: Boolean = false
|
isPatching: Boolean = false
|
||||||
) {
|
) {
|
||||||
var buttonText by remember { mutableStateOf("Send FCC Patch") }
|
var buttonText by remember { mutableStateOf("Send FCC Patch") }
|
||||||
var buttonEnabled by remember { mutableStateOf(true) }
|
var buttonEnabled by remember { mutableStateOf(true) }
|
||||||
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(topBar = {
|
||||||
topBar = {
|
TopAppBar(title = { Text("DJI FCC Hack") }, actions = {
|
||||||
TopAppBar(
|
|
||||||
title = { Text("DJI FCC Hack") },
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = onRefresh, enabled = !isPatching) {
|
IconButton(onClick = onRefresh, enabled = !isPatching) {
|
||||||
Icon(Icons.Default.Refresh, contentDescription = "Refresh USB Connection")
|
Icon(Icons.Default.Refresh, contentDescription = "Refresh USB Connection")
|
||||||
}
|
}
|
||||||
IconButton(onClick = { /* Open Settings */ }) {
|
})
|
||||||
Icon(Icons.Default.MoreVert, contentDescription = "More Options")
|
}) { innerPadding ->
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
) { innerPadding ->
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(innerPadding)
|
.padding(innerPadding)
|
||||||
.padding(16.dp),
|
.padding(16.dp)
|
||||||
|
.verticalScroll(rememberScrollState()), // Make the entire screen scrollable
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
@ -257,6 +254,61 @@ fun MainScreen(
|
|||||||
modifier = Modifier.size(75.dp),
|
modifier = Modifier.size(75.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Disclaimer Section
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer)
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "Disclaimer",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
text = "This app is provided as-is and is not affiliated with DJI. Use at your own risk.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructions Section
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "Instructions",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Column {
|
||||||
|
listOf(
|
||||||
|
"Turn on the drone and remote and wait a few seconds for them to connect.",
|
||||||
|
"Connect your phone to the bottom USB port of the remote.",
|
||||||
|
"Click on 'Send FCC Patch'.",
|
||||||
|
"Disconnect your phone from the bottom USB port of the remote and connect it to the top USB port."
|
||||||
|
).forEachIndexed { index, instruction ->
|
||||||
|
Text(
|
||||||
|
text = "${index + 1}. $instruction",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
modifier = Modifier.padding(vertical = 4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Note: You have to repeat the process every time you turn on the remote and/or drone.",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// USB Connection Status
|
// USB Connection Status
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@ -285,9 +337,9 @@ fun MainScreen(
|
|||||||
// Send Patch Button
|
// Send Patch Button
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
buttonText = "Successfully patched"
|
|
||||||
buttonEnabled = false
|
buttonEnabled = false
|
||||||
onSendPatch()
|
val result = onSendPatch()
|
||||||
|
buttonText = if (result) "Successfully patched" else "Error patching"
|
||||||
|
|
||||||
CoroutineScope(Dispatchers.Main).launch {
|
CoroutineScope(Dispatchers.Main).launch {
|
||||||
delay(5000)
|
delay(5000)
|
||||||
@ -306,44 +358,38 @@ fun MainScreen(
|
|||||||
Text(buttonText)
|
Text(buttonText)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instructions Section
|
// Links
|
||||||
Card(
|
Row {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
IconButton(onClick = { uriHandler.openUri(Constants.GITHUB_URL) }) {
|
||||||
shape = RoundedCornerShape(16.dp)
|
Image(
|
||||||
) {
|
painter = painterResource(id = isSystemInDarkTheme().let { if (it) R.drawable.github_light else R.drawable.github_dark }),
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
contentDescription = "GitHub",
|
||||||
Text(
|
modifier = Modifier.size(24.dp),
|
||||||
text = "Instructions",
|
|
||||||
style = MaterialTheme.typography.titleMedium
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(
|
|
||||||
text = "1. Connect your phone to the bottom port of the RC remote.\n" +
|
|
||||||
"2. Tap 'Send FCC Patch'.\n" +
|
|
||||||
"3. Disconnect from the bottom port and connect to the top port.",
|
|
||||||
style = MaterialTheme.typography.bodyMedium
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disclaimer Section
|
// Footer
|
||||||
Card(
|
Row {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer)
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
|
||||||
Text(
|
Text(
|
||||||
text = "Disclaimer",
|
text = "Made with ❤️ by ",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = "This app is provided as-is and is not affiliated with DJI. Use at your own risk.",
|
text = "Mathieu Broillet",
|
||||||
style = MaterialTheme.typography.bodyMedium
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
Text(
|
||||||
|
text = "based on the work of @galbb on MavicPilots",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.tertiary,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -353,6 +399,6 @@ fun MainScreen(
|
|||||||
@Composable
|
@Composable
|
||||||
fun PreviewMainScreen() {
|
fun PreviewMainScreen() {
|
||||||
DJI_FCC_HACK_Theme {
|
DJI_FCC_HACK_Theme {
|
||||||
MainScreen(usbConnected = true, onRefresh = {}, onSendPatch = {}, isPatching = false)
|
MainScreen(usbConnected = true, onRefresh = {}, onSendPatch = { false }, isPatching = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
1
app/src/main/res/drawable/github_dark.xml
Normal file
1
app/src/main/res/drawable/github_dark.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- drawable/github.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#000000" android:pathData="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" /></vector>
|
1
app/src/main/res/drawable/github_light.xml
Normal file
1
app/src/main/res/drawable/github_light.xml
Normal file
@ -0,0 +1 @@
|
|||||||
|
<!-- drawable/github.xml --><vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:width="24dp" android:viewportWidth="24" android:viewportHeight="24"><path android:fillColor="#ffffff" android:pathData="M12,2A10,10 0 0,0 2,12C2,16.42 4.87,20.17 8.84,21.5C9.34,21.58 9.5,21.27 9.5,21C9.5,20.77 9.5,20.14 9.5,19.31C6.73,19.91 6.14,17.97 6.14,17.97C5.68,16.81 5.03,16.5 5.03,16.5C4.12,15.88 5.1,15.9 5.1,15.9C6.1,15.97 6.63,16.93 6.63,16.93C7.5,18.45 8.97,18 9.54,17.76C9.63,17.11 9.89,16.67 10.17,16.42C7.95,16.17 5.62,15.31 5.62,11.5C5.62,10.39 6,9.5 6.65,8.79C6.55,8.54 6.2,7.5 6.75,6.15C6.75,6.15 7.59,5.88 9.5,7.17C10.29,6.95 11.15,6.84 12,6.84C12.85,6.84 13.71,6.95 14.5,7.17C16.41,5.88 17.25,6.15 17.25,6.15C17.8,7.5 17.45,8.54 17.35,8.79C18,9.5 18.38,10.39 18.38,11.5C18.38,15.32 16.04,16.16 13.81,16.41C14.17,16.72 14.5,17.33 14.5,18.26C14.5,19.6 14.5,20.68 14.5,21C14.5,21.27 14.66,21.59 15.17,21.5C19.14,20.16 22,16.42 22,12A10,10 0 0,0 12,2Z" /></vector>
|
Loading…
x
Reference in New Issue
Block a user