updated for android 16 fixing some detection issues
This commit is contained in:
parent
f3e01a116c
commit
e7c34d0de5
98
.gitignore
vendored
98
.gitignore
vendored
@ -1,49 +1,49 @@
|
|||||||
*.iml
|
*.iml
|
||||||
.gradle
|
.gradle
|
||||||
/local.properties
|
/local.properties
|
||||||
/.idea/caches
|
/.idea/caches
|
||||||
/.idea/libraries
|
/.idea/libraries
|
||||||
/.idea/modules.xml
|
/.idea/modules.xml
|
||||||
/.idea/workspace.xml
|
/.idea/workspace.xml
|
||||||
/.idea/navEditor.xml
|
/.idea/navEditor.xml
|
||||||
/.idea/assetWizardSettings.xml
|
/.idea/assetWizardSettings.xml
|
||||||
.DS_Store
|
.DS_Store
|
||||||
/build
|
/build
|
||||||
/captures
|
/captures
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
.cxx
|
||||||
local.properties
|
local.properties
|
||||||
|
|
||||||
# Gradle files
|
# Gradle files
|
||||||
.gradle/
|
.gradle/
|
||||||
build/
|
build/
|
||||||
|
|
||||||
# Local configuration file (sdk path, etc)
|
# Local configuration file (sdk path, etc)
|
||||||
local.properties
|
local.properties
|
||||||
|
|
||||||
# Log/OS Files
|
# Log/OS Files
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
# Android Studio generated files and folders
|
# Android Studio generated files and folders
|
||||||
captures/
|
captures/
|
||||||
.externalNativeBuild/
|
.externalNativeBuild/
|
||||||
.cxx/
|
.cxx/
|
||||||
*.apk
|
*.apk
|
||||||
output.json
|
output.json
|
||||||
|
|
||||||
# IntelliJ
|
# IntelliJ
|
||||||
*.iml
|
*.iml
|
||||||
.idea/
|
.idea/
|
||||||
misc.xml
|
misc.xml
|
||||||
deploymentTargetDropDown.xml
|
deploymentTargetDropDown.xml
|
||||||
render.experimental.xml
|
render.experimental.xml
|
||||||
|
|
||||||
# Keystore files
|
# Keystore files
|
||||||
*.jks
|
*.jks
|
||||||
*.keystore
|
*.keystore
|
||||||
|
|
||||||
# Google Services (e.g. APIs or Firebase)
|
# Google Services (e.g. APIs or Firebase)
|
||||||
google-services.json
|
google-services.json
|
||||||
|
|
||||||
# Android Profiling
|
# Android Profiling
|
||||||
*.hprof
|
*.hprof
|
||||||
|
|||||||
252
README.md
252
README.md
@ -1,126 +1,126 @@
|
|||||||
# DJI-FCC-HACK
|
# DJI-FCC-HACK
|
||||||
|
|
||||||
A simple Android app that forces DJI N1 remotes to FCC
|
A simple Android app that forces DJI N1 remotes to FCC
|
||||||
|
|
||||||
<img src=".github/light.webp" alt="app" width="200"/><img src=".github/dark.webp" alt="app" width="200"/>
|
<img src=".github/light.webp" alt="app" width="200"/><img src=".github/dark.webp" alt="app" width="200"/>
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> This only works for drones with DJI N1 remotes. If you have a different remote, this app will not work for you.
|
> This only works for drones with DJI N1 remotes. If you have a different remote, this app will not work for you.
|
||||||
|
|
||||||
## How to use
|
## How to use
|
||||||
|
|
||||||
Download the latest release from the [releases page](https://github.com/M4TH1EU/DJI-FCC-HACK/releases) and install it on your Android device.
|
Download the latest release from the [releases page](https://github.com/M4TH1EU/DJI-FCC-HACK/releases) and install it on your Android device.
|
||||||
|
|
||||||
> [!IMPORTANT]
|
> [!IMPORTANT]
|
||||||
> You need to repeat the following steps every time you turn on the drone and/or remote.
|
> You need to repeat the following steps every time you turn on the drone and/or remote.
|
||||||
|
|
||||||
Then follow these steps:
|
Then follow these steps:
|
||||||
|
|
||||||
1. Turn on the drone and remote and wait a few seconds for them to connect.
|
1. Turn on the drone and remote and wait a few seconds for them to connect.
|
||||||
2. Connect your phone to the **bottom** USB port of the remote.
|
2. Connect your phone to the **bottom** USB port of the remote.
|
||||||
3. Click on 'Send FCC Patch'.
|
3. Click on 'Send FCC Patch'.
|
||||||
4. Disconnect your phone from the bottom USB port of the remote and connect it to the **top** USB port.
|
4. Disconnect your phone from the bottom USB port of the remote and connect it to the **top** USB port.
|
||||||
5. Enjoy your drone with FCC mode.
|
5. Enjoy your drone with FCC mode.
|
||||||
|
|
||||||
## Compatibility
|
## Compatibility
|
||||||
|
|
||||||
This app should work on any Android device running Android 8 and above.
|
This app should work on any Android device running Android 8 and above.
|
||||||
|
|
||||||
**Tested on the following drones:**
|
**Tested on the following drones:**
|
||||||
|
|
||||||
* DJI Mavic Air 2
|
* DJI Mavic Air 2
|
||||||
* DJI Mini 4K
|
* DJI Mini 4K
|
||||||
* DJI Mini 2
|
* DJI Mini 2
|
||||||
* DJI Air 2S
|
* DJI Air 2S
|
||||||
* DJI Neo 2
|
* DJI Neo 2
|
||||||
|
|
||||||
> [!WARNING]
|
> [!WARNING]
|
||||||
> Many people reported that this hack doesn't work with the Mini 3, if anyone finds a working hack I could take a look at reverse-engineering it.
|
> Many people reported that this hack doesn't work with the Mini 3, if anyone finds a working hack I could take a look at reverse-engineering it.
|
||||||
|
|
||||||
> [!NOTE]
|
> [!NOTE]
|
||||||
> Please let me know if you have tested this app on another drone so I can update this README.
|
> Please let me know if you have tested this app on another drone so I can update this README.
|
||||||
|
|
||||||
## How do I know if it worked?
|
## How do I know if it worked?
|
||||||
|
|
||||||
Open the DJI Fly app and go to the Transmission tab. Look at the horizontal bar around -90 dBm:
|
Open the DJI Fly app and go to the Transmission tab. Look at the horizontal bar around -90 dBm:
|
||||||
|
|
||||||
* If it lines up with the 1km mark, your drone is in CE mode.
|
* If it lines up with the 1km mark, your drone is in CE mode.
|
||||||
* If it falls below the 1km mark, your drone is in FCC mode.
|
* If it falls below the 1km mark, your drone is in FCC mode.
|
||||||
|
|
||||||
*Check the images below for reference.*
|
*Check the images below for reference.*
|
||||||
|
|
||||||
| FCC | CE |
|
| FCC | CE |
|
||||||
| ----------------------------- | --------------------------- |
|
| ----------------------------- | --------------------------- |
|
||||||
|  |  |
|
|  |  |
|
||||||
|
|
||||||
## FAQ
|
## FAQ
|
||||||
|
|
||||||
### Does this work on iOS?
|
### Does this work on iOS?
|
||||||
|
|
||||||
No, this app is only available for Android.
|
No, this app is only available for Android.
|
||||||
|
|
||||||
### Does this work on DJI Smart Controller?
|
### Does this work on DJI Smart Controller?
|
||||||
|
|
||||||
No, this app only works with N1 remotes (the ones without a screen).
|
No, this app only works with N1 remotes (the ones without a screen).
|
||||||
|
|
||||||
### Does this work on DJI XYZ drone?
|
### Does this work on DJI XYZ drone?
|
||||||
|
|
||||||
Maybe? Give it a try and let me know so I can update this README.
|
Maybe? Give it a try and let me know so I can update this README.
|
||||||
|
|
||||||
### How does this work?
|
### How does this work?
|
||||||
|
|
||||||
This app simply sends a command to the remote to switch to FCC mode over the USB port.
|
This app simply sends a command to the remote to switch to FCC mode over the USB port.
|
||||||
|
|
||||||
### Can I use this app to switch back to CE mode?
|
### Can I use this app to switch back to CE mode?
|
||||||
|
|
||||||
No, this app only switches to FCC mode. To switch back to CE, turn off the drone and remote, then power them back on.
|
No, this app only switches to FCC mode. To switch back to CE, turn off the drone and remote, then power them back on.
|
||||||
|
|
||||||
## Goggles Support
|
## Goggles Support
|
||||||
>[!WARNING]
|
>[!WARNING]
|
||||||
> This app is not related to the following FCC file-based hacks for goggles; they are included here for reference only.
|
> This app is not related to the following FCC file-based hacks for goggles; they are included here for reference only.
|
||||||
|
|
||||||
Steps to enable higher power output for DJI Goggles:
|
Steps to enable higher power output for DJI Goggles:
|
||||||
|
|
||||||
**DJI Goggles V1/V2**
|
**DJI Goggles V1/V2**
|
||||||
|
|
||||||
* Create a text file named `naco_pwr.txt` with content: `pwr_2`
|
* Create a text file named `naco_pwr.txt` with content: `pwr_2`
|
||||||
* Copy it to a microSD card
|
* Copy it to a microSD card
|
||||||
* Power on Goggles and Air Unit, wait for camera image
|
* Power on Goggles and Air Unit, wait for camera image
|
||||||
* Insert SD card into Goggles and restart
|
* Insert SD card into Goggles and restart
|
||||||
|
|
||||||
**DJI Goggles 2 / Goggles 3**
|
**DJI Goggles 2 / Goggles 3**
|
||||||
|
|
||||||
* Create an empty file named `ham_cfg_support` (no extension)
|
* Create an empty file named `ham_cfg_support` (no extension)
|
||||||
* Copy it to a microSD card
|
* Copy it to a microSD card
|
||||||
* Insert SD card into Goggles
|
* Insert SD card into Goggles
|
||||||
* Power on Goggles
|
* Power on Goggles
|
||||||
|
|
||||||
## Air Units
|
## Air Units
|
||||||
>[!WARNING]
|
>[!WARNING]
|
||||||
> This app is not related to the following FCC file-based hacks for air units; they are included here for reference only.
|
> This app is not related to the following FCC file-based hacks for air units; they are included here for reference only.
|
||||||
|
|
||||||
Steps to enable FCC mode on DJI video transmitters:
|
Steps to enable FCC mode on DJI video transmitters:
|
||||||
|
|
||||||
**Air Unit V1**
|
**Air Unit V1**
|
||||||
|
|
||||||
* Create `naco_pwr.txt` with `pwr_2` inside
|
* Create `naco_pwr.txt` with `pwr_2` inside
|
||||||
* Copy to microSD card, insert into Air Unit
|
* Copy to microSD card, insert into Air Unit
|
||||||
* Power on
|
* Power on
|
||||||
|
|
||||||
**Vista**
|
**Vista**
|
||||||
|
|
||||||
* Create `naco_pwr.txt` with `pwr_2` inside
|
* Create `naco_pwr.txt` with `pwr_2` inside
|
||||||
* Power on Vista and connect via USB
|
* Power on Vista and connect via USB
|
||||||
* Copy file to Vista storage when it appears
|
* Copy file to Vista storage when it appears
|
||||||
* Power cycle the unit
|
* Power cycle the unit
|
||||||
|
|
||||||
**Air Unit O3**
|
**Air Unit O3**
|
||||||
|
|
||||||
* Create empty file `ham_cfg_support`
|
* Create empty file `ham_cfg_support`
|
||||||
* Connect O3 via USB
|
* Connect O3 via USB
|
||||||
* Copy to O3 storage
|
* Copy to O3 storage
|
||||||
* Power cycle
|
* Power cycle
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
This app is based on the work of [galbb](https://mavicpilots.com/members/galbb.148459/) on the [MavicPilots forum](https://mavicpilots.com/threads/mavic-air-2-switch-to-fcc-mode-using-an-android-app.115027/).
|
This app is based on the work of [galbb](https://mavicpilots.com/members/galbb.148459/) on the [MavicPilots forum](https://mavicpilots.com/threads/mavic-air-2-switch-to-fcc-mode-using-an-android-app.115027/).
|
||||||
|
|||||||
@ -1,60 +1,60 @@
|
|||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application)
|
alias(libs.plugins.android.application)
|
||||||
alias(libs.plugins.kotlin.android)
|
alias(libs.plugins.kotlin.android)
|
||||||
alias(libs.plugins.kotlin.compose)
|
alias(libs.plugins.kotlin.compose)
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "ch.mathieubroillet.djiffchack"
|
namespace = "ch.mathieubroillet.djiffchack"
|
||||||
compileSdk = 35
|
compileSdk = 35
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "ch.mathieubroillet.djiffchack"
|
applicationId = "ch.mathieubroillet.djiffchack"
|
||||||
minSdk = 26
|
minSdk = 26
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1
|
versionCode = 1
|
||||||
versionName = "1.1"
|
versionName = "1.1"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
release {
|
release {
|
||||||
isMinifyEnabled = false
|
isMinifyEnabled = false
|
||||||
proguardFiles(
|
proguardFiles(
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
getDefaultProguardFile("proguard-android-optimize.txt"),
|
||||||
"proguard-rules.pro"
|
"proguard-rules.pro"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
compose = true
|
compose = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("com.github.mik3y:usb-serial-for-android:3.8.1")
|
implementation("com.github.mik3y:usb-serial-for-android:3.8.1")
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(platform(libs.androidx.compose.bom))
|
implementation(platform(libs.androidx.compose.bom))
|
||||||
implementation(libs.androidx.ui)
|
implementation(libs.androidx.ui)
|
||||||
implementation(libs.androidx.ui.graphics)
|
implementation(libs.androidx.ui.graphics)
|
||||||
implementation(libs.androidx.ui.tooling.preview)
|
implementation(libs.androidx.ui.tooling.preview)
|
||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
androidTestImplementation(libs.androidx.espresso.core)
|
androidTestImplementation(libs.androidx.espresso.core)
|
||||||
androidTestImplementation(platform(libs.androidx.compose.bom))
|
androidTestImplementation(platform(libs.androidx.compose.bom))
|
||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
}
|
}
|
||||||
40
app/proguard-rules.pro
vendored
40
app/proguard-rules.pro
vendored
@ -1,21 +1,21 @@
|
|||||||
# Add project specific ProGuard rules here.
|
# Add project specific ProGuard rules here.
|
||||||
# You can control the set of applied configuration files using the
|
# You can control the set of applied configuration files using the
|
||||||
# proguardFiles setting in build.gradle.
|
# proguardFiles setting in build.gradle.
|
||||||
#
|
#
|
||||||
# For more details, see
|
# For more details, see
|
||||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
# If your project uses WebView with JS, uncomment the following
|
# If your project uses WebView with JS, uncomment the following
|
||||||
# and specify the fully qualified class name to the JavaScript interface
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
# class:
|
# class:
|
||||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
# public *;
|
# public *;
|
||||||
#}
|
#}
|
||||||
|
|
||||||
# Uncomment this to preserve the line number information for
|
# Uncomment this to preserve the line number information for
|
||||||
# debugging stack traces.
|
# debugging stack traces.
|
||||||
#-keepattributes SourceFile,LineNumberTable
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
# If you keep the line number information, uncomment this to
|
# If you keep the line number information, uncomment this to
|
||||||
# hide the original source file name.
|
# hide the original source file name.
|
||||||
#-renamesourcefileattribute SourceFile
|
#-renamesourcefileattribute SourceFile
|
||||||
@ -1,24 +1,24 @@
|
|||||||
package ch.mathieubroillet.djiffchack
|
package ch.mathieubroillet.djiffchack
|
||||||
|
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instrumented test, which will execute on an Android device.
|
* Instrumented test, which will execute on an Android device.
|
||||||
*
|
*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ExampleInstrumentedTest {
|
class ExampleInstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun useAppContext() {
|
fun useAppContext() {
|
||||||
// Context of the app under test.
|
// Context of the app under test.
|
||||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
assertEquals("ch.mathieubroillet.djiffchack", appContext.packageName)
|
assertEquals("ch.mathieubroillet.djiffchack", appContext.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,38 +1,34 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
|
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.usb.accessory" android:required="true"/>
|
<uses-feature android:name="android.hardware.usb.accessory" android:required="true"/>
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
<uses-permission android:name="android.permission.USB_PERMISSION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/icon"
|
android:icon="@mipmap/icon"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:roundIcon="@mipmap/icon_round"
|
android:roundIcon="@mipmap/icon_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.Djiffchack"
|
android:theme="@style/Theme.Djiffchack"
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
|
|
||||||
<uses-library android:name="com.android.future.usb.accessory"/>
|
<uses-library android:name="com.android.future.usb.accessory"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
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>
|
</activity>
|
||||||
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"/>
|
</application>
|
||||||
</intent-filter>
|
|
||||||
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" android:resource="@xml/device_filter"/>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@ -1,14 +1,14 @@
|
|||||||
package ch.mathieubroillet.djiffchack
|
package ch.mathieubroillet.djiffchack
|
||||||
|
|
||||||
|
|
||||||
object Constants {
|
object Constants {
|
||||||
private const val PACKAGE = "ch.mathieubroillet.djiffchack"
|
private const val PACKAGE = "ch.mathieubroillet.djiffchack"
|
||||||
const val INTENT_ACTION_GRANT_USB_PERMISSION = "$PACKAGE.USB_PERMISSION"
|
const val INTENT_ACTION_GRANT_USB_PERMISSION = "$PACKAGE.USB_PERMISSION"
|
||||||
|
|
||||||
// The "magic bytes" that enable FCC mode
|
// 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/
|
// 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_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)
|
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"
|
const val GITHUB_URL = "https://github.com/M4TH1EU/DJI-FCC-HACK"
|
||||||
}
|
}
|
||||||
@ -19,6 +19,8 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@ -32,7 +34,9 @@ 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.Info
|
||||||
import androidx.compose.material.icons.filled.Refresh
|
import androidx.compose.material.icons.filled.Refresh
|
||||||
|
import androidx.compose.material.icons.filled.Star
|
||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.CardDefaults
|
import androidx.compose.material3.CardDefaults
|
||||||
@ -45,9 +49,11 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshots.SnapshotStateList
|
||||||
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.platform.LocalUriHandler
|
||||||
@ -66,17 +72,44 @@ import kotlinx.coroutines.CoroutineScope
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
||||||
|
data class UsbDeviceInfo(
|
||||||
|
val device: UsbDevice,
|
||||||
|
val isDjiDevice: Boolean,
|
||||||
|
val hasPermission: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
private lateinit var usbManager: UsbManager
|
private lateinit var usbManager: UsbManager
|
||||||
private var usbConnected by mutableStateOf(false)
|
private var usbConnected by mutableStateOf(false)
|
||||||
private var isPatching by mutableStateOf(false)
|
private var isPatching by mutableStateOf(false)
|
||||||
|
private val debugLogs = mutableStateListOf<String>()
|
||||||
|
private val maxLogs = 50
|
||||||
|
private val usbDevices = mutableStateListOf<UsbDeviceInfo>()
|
||||||
|
private var selectedDevice by mutableStateOf<UsbDevice?>(null)
|
||||||
|
|
||||||
|
private fun addDebugLog(message: String) {
|
||||||
|
val timestamp = SimpleDateFormat("HH:mm:ss.SSS", Locale.getDefault()).format(Date())
|
||||||
|
val logEntry = "[$timestamp] $message"
|
||||||
|
debugLogs.add(0, logEntry) // Add to the beginning
|
||||||
|
Log.d("DEBUG_LOG", message)
|
||||||
|
|
||||||
|
// Keep only the most recent logs
|
||||||
|
if (debugLogs.size > maxLogs) {
|
||||||
|
debugLogs.removeAt(debugLogs.lastIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
|
usbManager = getSystemService(Context.USB_SERVICE) as UsbManager
|
||||||
|
addDebugLog("App started")
|
||||||
|
addDebugLog("Android ${android.os.Build.VERSION.RELEASE} (API ${android.os.Build.VERSION.SDK_INT})")
|
||||||
|
|
||||||
// Register receiver to detect USB plug/unplug events
|
// Register receiver to detect USB plug/unplug events
|
||||||
val filter = IntentFilter().apply {
|
val filter = IntentFilter().apply {
|
||||||
@ -85,15 +118,24 @@ class MainActivity : ComponentActivity() {
|
|||||||
addAction(Constants.INTENT_ACTION_GRANT_USB_PERMISSION)
|
addAction(Constants.INTENT_ACTION_GRANT_USB_PERMISSION)
|
||||||
}
|
}
|
||||||
ContextCompat.registerReceiver(
|
ContextCompat.registerReceiver(
|
||||||
this, usbReceiver, filter, ContextCompat.RECEIVER_NOT_EXPORTED
|
this, usbReceiver, filter, ContextCompat.RECEIVER_EXPORTED
|
||||||
)
|
)
|
||||||
|
|
||||||
// Initial check for USB connection
|
// Initial USB scan
|
||||||
refreshUsbConnection()
|
refreshUsbDeviceList()
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
DJI_FCC_HACK_Theme {
|
DJI_FCC_HACK_Theme {
|
||||||
MainScreen(usbConnected, ::refreshUsbConnection, ::sendPatch, isPatching)
|
MainScreen(
|
||||||
|
usbConnected = usbConnected,
|
||||||
|
onRefresh = ::refreshUsbDeviceList,
|
||||||
|
onSendPatch = ::sendPatch,
|
||||||
|
isPatching = isPatching,
|
||||||
|
debugLogs = debugLogs,
|
||||||
|
usbDevices = usbDevices,
|
||||||
|
selectedDevice = selectedDevice,
|
||||||
|
onDeviceSelected = ::selectDevice
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -103,29 +145,90 @@ class MainActivity : ComponentActivity() {
|
|||||||
unregisterReceiver(usbReceiver)
|
unregisterReceiver(usbReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun selectDevice(device: UsbDevice) {
|
||||||
|
addDebugLog("User selected device: VID=${device.vendorId}, PID=${device.productId}")
|
||||||
|
selectedDevice = device
|
||||||
|
|
||||||
|
// Check permission status
|
||||||
|
val hasPermission = usbManager.hasPermission(device)
|
||||||
|
addDebugLog("Permission status: $hasPermission")
|
||||||
|
|
||||||
|
if (!hasPermission) {
|
||||||
|
addDebugLog("Requesting permission for selected device...")
|
||||||
|
usbConnected = false
|
||||||
|
requestUsbPermission(device)
|
||||||
|
} else {
|
||||||
|
addDebugLog("✓ Permission already granted for selected device")
|
||||||
|
usbConnected = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes the USB connection status
|
* Refreshes the USB device list
|
||||||
*/
|
*/
|
||||||
private fun refreshUsbConnection() {
|
private fun refreshUsbDeviceList() {
|
||||||
if (usbManager.deviceList.isNotEmpty()) {
|
addDebugLog("=== Scanning for USB devices ===")
|
||||||
val device: UsbDevice = usbManager.deviceList.values.first()
|
addDebugLog("Android ${android.os.Build.VERSION.RELEASE} (API ${android.os.Build.VERSION.SDK_INT})")
|
||||||
Log.d("USB_CONNECTION", device.vendorId.toString() + ":" + device.productId.toString())
|
|
||||||
|
|
||||||
// Check to be sure the device is the initialized DJI Remote (and not another USB device)
|
// Check if USB host is supported
|
||||||
if (device.productId != 4128) {
|
val hasUsbHostFeature = packageManager.hasSystemFeature("android.hardware.usb.host")
|
||||||
Log.d("USB_CONNECTION", "Device not supported ${device.productId}")
|
addDebugLog("USB Host feature available: $hasUsbHostFeature")
|
||||||
usbConnected = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (usbManager.openDevice(device) == null) {
|
// Get device list
|
||||||
Log.d("USB_CONNECTION", "Requesting USB Permission")
|
val deviceList = usbManager.deviceList
|
||||||
requestUsbPermission(device)
|
addDebugLog("UsbManager.deviceList size: ${deviceList.size}")
|
||||||
} else {
|
|
||||||
usbConnected = true
|
if (deviceList.isEmpty()) {
|
||||||
|
addDebugLog("WARNING: No USB devices found")
|
||||||
|
addDebugLog("Possible reasons:")
|
||||||
|
addDebugLog(" - No USB device connected")
|
||||||
|
addDebugLog(" - Android 16 USB enumeration bug")
|
||||||
|
addDebugLog(" - USB Protection enabled (unlock phone)")
|
||||||
|
addDebugLog(" - Try unplugging and re-plugging USB")
|
||||||
|
// Don't clear usbDevices here - keep showing last known devices
|
||||||
|
// Only clear connection status
|
||||||
|
if (selectedDevice != null) {
|
||||||
|
val stillExists = deviceList.values.any { it == selectedDevice }
|
||||||
|
if (!stillExists) {
|
||||||
|
addDebugLog("Selected device no longer in list")
|
||||||
|
usbConnected = false
|
||||||
|
selectedDevice = null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
usbConnected = false
|
addDebugLog("Found ${deviceList.size} USB device(s):")
|
||||||
|
|
||||||
|
// Build new list without clearing immediately
|
||||||
|
val newDevices = mutableListOf<UsbDeviceInfo>()
|
||||||
|
|
||||||
|
deviceList.values.forEachIndexed { index, device ->
|
||||||
|
val isDji = device.vendorId == 11427 && device.productId == 4128
|
||||||
|
val hasPerm = usbManager.hasPermission(device)
|
||||||
|
|
||||||
|
addDebugLog(" [$index] VID=0x${device.vendorId.toString(16)} PID=0x${device.productId.toString(16)}")
|
||||||
|
addDebugLog(" Name: ${device.deviceName}")
|
||||||
|
addDebugLog(" DJI Device: $isDji")
|
||||||
|
addDebugLog(" Has Permission: $hasPerm")
|
||||||
|
|
||||||
|
newDevices.add(UsbDeviceInfo(device, isDji, hasPerm))
|
||||||
|
|
||||||
|
// Update selected device permission status if it's in the new list
|
||||||
|
if (device == selectedDevice) {
|
||||||
|
usbConnected = hasPerm
|
||||||
|
addDebugLog("Selected device found, permission: $hasPerm")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-select DJI device if found and has permission and nothing selected
|
||||||
|
if (isDji && hasPerm && selectedDevice == null) {
|
||||||
|
selectedDevice = device
|
||||||
|
usbConnected = true
|
||||||
|
addDebugLog("✓ Auto-selected DJI device with permission")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the list only after building complete list
|
||||||
|
usbDevices.clear()
|
||||||
|
usbDevices.addAll(newDevices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,12 +236,23 @@ class MainActivity : ComponentActivity() {
|
|||||||
* Sends the FCC patch to the DJI remote via USB
|
* Sends the FCC patch to the DJI remote via USB
|
||||||
*/
|
*/
|
||||||
private fun sendPatch(): Boolean {
|
private fun sendPatch(): Boolean {
|
||||||
// At this point, we assume the USB device is connected and we have permission to access it
|
addDebugLog("=== Starting FCC Patch ===")
|
||||||
if (!usbConnected) {
|
|
||||||
Toast.makeText(this, "No USB device connected!", Toast.LENGTH_SHORT).show()
|
if (selectedDevice == null) {
|
||||||
|
addDebugLog("ERROR: No device selected")
|
||||||
|
Toast.makeText(this, "Please select a USB device first!", Toast.LENGTH_SHORT).show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!usbManager.hasPermission(selectedDevice!!)) {
|
||||||
|
addDebugLog("ERROR: No permission for selected device")
|
||||||
|
Toast.makeText(this, "No USB permission!", Toast.LENGTH_SHORT).show()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
val device = selectedDevice!!
|
||||||
|
addDebugLog("Using device: VID=${device.vendorId}, PID=${device.productId}")
|
||||||
|
|
||||||
val probeTable = ProbeTable().apply {
|
val probeTable = ProbeTable().apply {
|
||||||
addProduct(11427, 4128, CdcAcmSerialDriver::class.java)
|
addProduct(11427, 4128, CdcAcmSerialDriver::class.java)
|
||||||
|
|
||||||
@ -148,30 +262,41 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve the custom device (DJI remote) with the correct driver from the probe table above
|
// Retrieve the custom device (DJI remote) with the correct driver from the probe table above
|
||||||
val driver = UsbSerialProber(probeTable).probeDevice(usbManager.deviceList.values.first())
|
addDebugLog("Probing USB device...")
|
||||||
|
val driver = UsbSerialProber(probeTable).probeDevice(device)
|
||||||
|
addDebugLog("Opening USB device...")
|
||||||
val deviceConnection = usbManager.openDevice(driver.device)
|
val deviceConnection = usbManager.openDevice(driver.device)
|
||||||
if (deviceConnection == null) {
|
if (deviceConnection == null) {
|
||||||
Log.e("USB_PATCH", "Error opening USB device")
|
Log.e("USB_PATCH", "Error opening USB device")
|
||||||
|
addDebugLog("ERROR: Failed to open USB device")
|
||||||
Toast.makeText(this, "Error opening USB device", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Error opening USB device", Toast.LENGTH_SHORT).show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
addDebugLog("Getting serial port...")
|
||||||
val deviceSerialPort = driver.ports.firstOrNull()
|
val deviceSerialPort = driver.ports.firstOrNull()
|
||||||
if (deviceSerialPort == null) {
|
if (deviceSerialPort == null) {
|
||||||
Log.e("USB_PATCH", "Error opening USB port")
|
Log.e("USB_PATCH", "Error opening USB port")
|
||||||
|
addDebugLog("ERROR: No serial port found")
|
||||||
Toast.makeText(this, "Error opening USB port", Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, "Error opening USB port", Toast.LENGTH_SHORT).show()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
addDebugLog("Opening serial port...")
|
||||||
deviceSerialPort.open(deviceConnection)
|
deviceSerialPort.open(deviceConnection)
|
||||||
|
addDebugLog("Setting parameters (19200 baud, 8N1)...")
|
||||||
deviceSerialPort.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE)
|
deviceSerialPort.setParameters(19200, 8, 1, UsbSerialPort.PARITY_NONE)
|
||||||
|
addDebugLog("Writing patch bytes (part 1)...")
|
||||||
deviceSerialPort.write(Constants.BYTES_1, 1000)
|
deviceSerialPort.write(Constants.BYTES_1, 1000)
|
||||||
|
addDebugLog("Writing patch bytes (part 2)...")
|
||||||
deviceSerialPort.write(Constants.BYTES_2, 1000)
|
deviceSerialPort.write(Constants.BYTES_2, 1000)
|
||||||
|
addDebugLog("SUCCESS: Patch sent successfully!")
|
||||||
|
|
||||||
Toast.makeText(this, "Patched successfully", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, "Patched successfully", Toast.LENGTH_LONG).show()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
addDebugLog("ERROR: ${e.javaClass.simpleName}: ${e.message}")
|
||||||
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
Toast.makeText(this, "Error: ${e.message}", Toast.LENGTH_LONG).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,14 +308,27 @@ class MainActivity : ComponentActivity() {
|
|||||||
* Requests USB permission for the device
|
* Requests USB permission for the device
|
||||||
*/
|
*/
|
||||||
private fun requestUsbPermission(device: UsbDevice) {
|
private fun requestUsbPermission(device: UsbDevice) {
|
||||||
|
addDebugLog("Creating permission request intent...")
|
||||||
|
addDebugLog("Package name: $packageName")
|
||||||
|
|
||||||
val permissionIntent = PendingIntent.getBroadcast(
|
val permissionIntent = PendingIntent.getBroadcast(
|
||||||
this,
|
this,
|
||||||
0,
|
0,
|
||||||
Intent(Constants.INTENT_ACTION_GRANT_USB_PERMISSION).apply { setPackage(packageName) },
|
Intent(Constants.INTENT_ACTION_GRANT_USB_PERMISSION).apply {
|
||||||
PendingIntent.FLAG_MUTABLE
|
setPackage(packageName)
|
||||||
|
putExtra(UsbManager.EXTRA_DEVICE, device)
|
||||||
|
},
|
||||||
|
PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
)
|
)
|
||||||
|
|
||||||
usbManager.requestPermission(device, permissionIntent)
|
addDebugLog("Calling usbManager.requestPermission()...")
|
||||||
|
try {
|
||||||
|
usbManager.requestPermission(device, permissionIntent)
|
||||||
|
addDebugLog("Permission request sent successfully")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
addDebugLog("ERROR requesting permission: ${e.message}")
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -202,18 +340,51 @@ class MainActivity : ComponentActivity() {
|
|||||||
when (intent.action) {
|
when (intent.action) {
|
||||||
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
|
UsbManager.ACTION_USB_DEVICE_ATTACHED -> {
|
||||||
Log.d("USB_EVENT", "USB Device Connected")
|
Log.d("USB_EVENT", "USB Device Connected")
|
||||||
refreshUsbConnection()
|
addDebugLog("EVENT: USB device attached")
|
||||||
|
refreshUsbDeviceList()
|
||||||
}
|
}
|
||||||
|
|
||||||
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
|
UsbManager.ACTION_USB_DEVICE_DETACHED -> {
|
||||||
Log.d("USB_EVENT", "USB Device Disconnected")
|
Log.d("USB_EVENT", "USB Device Disconnected")
|
||||||
refreshUsbConnection()
|
addDebugLog("EVENT: USB device detached")
|
||||||
|
val device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
|
||||||
|
if (device == selectedDevice) {
|
||||||
|
addDebugLog("Selected device was detached")
|
||||||
|
selectedDevice = null
|
||||||
|
usbConnected = false
|
||||||
|
}
|
||||||
|
refreshUsbDeviceList()
|
||||||
}
|
}
|
||||||
|
|
||||||
Constants.INTENT_ACTION_GRANT_USB_PERMISSION -> {
|
Constants.INTENT_ACTION_GRANT_USB_PERMISSION -> {
|
||||||
if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
|
val device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
|
||||||
|
val granted = intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)
|
||||||
|
|
||||||
|
addDebugLog("Permission response for device: VID=${device?.vendorId}, PID=${device?.productId}")
|
||||||
|
addDebugLog("Permission granted: $granted")
|
||||||
|
|
||||||
|
if (granted) {
|
||||||
Log.d("USB_EVENT", "USB Permission Granted")
|
Log.d("USB_EVENT", "USB Permission Granted")
|
||||||
refreshUsbConnection()
|
addDebugLog("✓ USB permission granted by user")
|
||||||
|
|
||||||
|
// Verify permission with hasPermission
|
||||||
|
if (device != null) {
|
||||||
|
val verified = usbManager.hasPermission(device)
|
||||||
|
addDebugLog("Permission verified with hasPermission(): $verified")
|
||||||
|
|
||||||
|
if (device == selectedDevice && verified) {
|
||||||
|
usbConnected = true
|
||||||
|
addDebugLog("✓ Selected device now connected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh immediately
|
||||||
|
refreshUsbDeviceList()
|
||||||
|
} else {
|
||||||
|
addDebugLog("❌ USB permission denied by user")
|
||||||
|
if (device == selectedDevice) {
|
||||||
|
usbConnected = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -227,14 +398,25 @@ fun MainScreen(
|
|||||||
usbConnected: Boolean,
|
usbConnected: Boolean,
|
||||||
onRefresh: () -> Unit,
|
onRefresh: () -> Unit,
|
||||||
onSendPatch: () -> Boolean,
|
onSendPatch: () -> Boolean,
|
||||||
isPatching: Boolean = false
|
isPatching: Boolean = false,
|
||||||
|
debugLogs: SnapshotStateList<String> = mutableStateListOf(),
|
||||||
|
usbDevices: SnapshotStateList<UsbDeviceInfo> = mutableStateListOf(),
|
||||||
|
selectedDevice: UsbDevice? = null,
|
||||||
|
onDeviceSelected: (UsbDevice) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
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) }
|
||||||
|
var showDebugLog by remember { mutableStateOf(false) }
|
||||||
val uriHandler = LocalUriHandler.current
|
val uriHandler = LocalUriHandler.current
|
||||||
|
|
||||||
Scaffold(topBar = {
|
Scaffold(topBar = {
|
||||||
TopAppBar(title = { Text("DJI FCC Hack") }, actions = {
|
TopAppBar(title = { Text("DJI FCC Hack") }, actions = {
|
||||||
|
androidx.compose.material3.TextButton(onClick = { showDebugLog = !showDebugLog }) {
|
||||||
|
Text(
|
||||||
|
text = if (showDebugLog) "Hide Log" else "Show Log",
|
||||||
|
style = MaterialTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
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")
|
||||||
}
|
}
|
||||||
@ -256,25 +438,25 @@ fun MainScreen(
|
|||||||
modifier = Modifier.size(75.dp),
|
modifier = Modifier.size(75.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
// Disclaimer Section
|
// // Disclaimer Section
|
||||||
Card(
|
// Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
// modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(16.dp),
|
// shape = RoundedCornerShape(16.dp),
|
||||||
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer)
|
// colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.errorContainer)
|
||||||
) {
|
// ) {
|
||||||
Column(modifier = Modifier.padding(16.dp)) {
|
// Column(modifier = Modifier.padding(16.dp)) {
|
||||||
Text(
|
// Text(
|
||||||
text = "Disclaimer",
|
// text = "Disclaimer",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
// style = MaterialTheme.typography.titleMedium,
|
||||||
fontWeight = FontWeight.Bold
|
// fontWeight = FontWeight.Bold
|
||||||
)
|
// )
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
// Spacer(modifier = Modifier.height(4.dp))
|
||||||
Text(
|
// Text(
|
||||||
text = "This app is provided as-is and is not affiliated with DJI. Use at your own risk.",
|
// text = "This app is provided as-is and is not affiliated with DJI. Use at your own risk.",
|
||||||
style = MaterialTheme.typography.bodyMedium
|
// style = MaterialTheme.typography.bodyMedium
|
||||||
)
|
// )
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Instructions Section
|
// Instructions Section
|
||||||
Card(
|
Card(
|
||||||
@ -311,28 +493,240 @@ fun MainScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// USB Connection Status
|
// USB Device Selection Card
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
colors = CardDefaults.cardColors(
|
colors = CardDefaults.cardColors(
|
||||||
containerColor = if (usbConnected) MaterialTheme.colorScheme.primaryContainer else MaterialTheme.colorScheme.errorContainer
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
modifier = Modifier.padding(16.dp),
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Build,
|
||||||
|
contentDescription = "USB Devices",
|
||||||
|
tint = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "USB Devices (${usbDevices.size})",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
if (usbDevices.isEmpty()) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||||
|
Text(
|
||||||
|
text = "No USB devices found",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
// Text(
|
||||||
|
// text = "Troubleshooting steps:",
|
||||||
|
// style = MaterialTheme.typography.bodySmall,
|
||||||
|
// fontWeight = FontWeight.Bold,
|
||||||
|
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
// )
|
||||||
|
// Column(modifier = Modifier.padding(start = 8.dp)) {
|
||||||
|
// listOf(
|
||||||
|
// "Make sure phone is UNLOCKED",
|
||||||
|
// "Try unplugging and re-plugging USB",
|
||||||
|
// "Check USB notification - change to 'File Transfer' mode",
|
||||||
|
// "Enable USB debugging in Developer options",
|
||||||
|
// "Known bug in Android 16 - may not work reliably"
|
||||||
|
// ).forEach { tip ->
|
||||||
|
// Row(modifier = Modifier.padding(vertical = 2.dp)) {
|
||||||
|
// Text("• ", style = MaterialTheme.typography.bodySmall)
|
||||||
|
// Text(
|
||||||
|
// text = tip,
|
||||||
|
// style = MaterialTheme.typography.bodySmall,
|
||||||
|
// color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
// Text(
|
||||||
|
// text = "Tap refresh (↻) after trying each step",
|
||||||
|
// style = MaterialTheme.typography.bodySmall,
|
||||||
|
// fontStyle = FontStyle.Italic,
|
||||||
|
// color = MaterialTheme.colorScheme.primary
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
usbDevices.forEach { deviceInfo ->
|
||||||
|
val isSelected = deviceInfo.device == selectedDevice
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 4.dp)
|
||||||
|
.clickable { onDeviceSelected(deviceInfo.device) }
|
||||||
|
.then(
|
||||||
|
if (isSelected) {
|
||||||
|
Modifier.border(
|
||||||
|
3.dp,
|
||||||
|
MaterialTheme.colorScheme.primary,
|
||||||
|
RoundedCornerShape(8.dp)
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = if (isSelected)
|
||||||
|
MaterialTheme.colorScheme.primaryContainer
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.surfaceContainerHighest
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.padding(12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Text(
|
||||||
|
text = "VID: 0x${deviceInfo.device.vendorId.toString(16)} / PID: 0x${deviceInfo.device.productId.toString(16)}",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface
|
||||||
|
)
|
||||||
|
if (deviceInfo.isDjiDevice) {
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Star,
|
||||||
|
contentDescription = "DJI Device",
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
text = deviceInfo.device.deviceName,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
if (deviceInfo.isDjiDevice) {
|
||||||
|
Text(
|
||||||
|
text = "✓ DJI Remote Controller",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = if (deviceInfo.hasPermission) "✓ Has Permission" else "⚠ No Permission",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = if (deviceInfo.hasPermission)
|
||||||
|
MaterialTheme.colorScheme.primary
|
||||||
|
else
|
||||||
|
MaterialTheme.colorScheme.error
|
||||||
|
)
|
||||||
|
if (!deviceInfo.hasPermission) {
|
||||||
|
androidx.compose.material3.TextButton(
|
||||||
|
onClick = { onDeviceSelected(deviceInfo.device) },
|
||||||
|
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp, vertical = 0.dp),
|
||||||
|
modifier = Modifier.height(24.dp)
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Grant",
|
||||||
|
style = MaterialTheme.typography.labelSmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isSelected) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.CheckCircle,
|
||||||
|
contentDescription = "Selected",
|
||||||
|
tint = MaterialTheme.colorScheme.primary,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug Log Section
|
||||||
|
if (showDebugLog) {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceVariant
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
Icon(
|
Column(modifier = Modifier.padding(16.dp)) {
|
||||||
if (usbConnected) Icons.Default.CheckCircle else Icons.Default.Clear,
|
Row(
|
||||||
contentDescription = "USB Status"
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
)
|
modifier = Modifier.fillMaxWidth()
|
||||||
Spacer(Modifier.width(8.dp))
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = if (usbConnected) "Remote Connected" else "Remote Not Connected",
|
Icons.Default.Info,
|
||||||
style = MaterialTheme.typography.bodyMedium
|
contentDescription = "Debug Info",
|
||||||
)
|
tint = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
text = "Debug Log",
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
fontWeight = FontWeight.Bold
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
if (debugLogs.isEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = "No logs yet...",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Card(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(200.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
debugLogs.forEach { log ->
|
||||||
|
Text(
|
||||||
|
text = log,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontFamily = androidx.compose.ui.text.font.FontFamily.Monospace,
|
||||||
|
modifier = Modifier.padding(vertical = 2.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,13 +747,22 @@ fun MainScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(56.dp),
|
.height(56.dp),
|
||||||
enabled = usbConnected && !isPatching && buttonEnabled
|
enabled = selectedDevice != null && !isPatching && buttonEnabled
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.Build, contentDescription = "Patch")
|
Icon(Icons.Default.Build, contentDescription = "Patch")
|
||||||
Spacer(Modifier.width(8.dp))
|
Spacer(Modifier.width(8.dp))
|
||||||
Text(buttonText)
|
Text(buttonText)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedDevice == null && usbDevices.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = "Please select a USB device above",
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
fontStyle = FontStyle.Italic
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Links
|
// Links
|
||||||
Row {
|
Row {
|
||||||
IconButton(onClick = { uriHandler.openUri(Constants.GITHUB_URL) }) {
|
IconButton(onClick = { uriHandler.openUri(Constants.GITHUB_URL) }) {
|
||||||
@ -384,6 +787,17 @@ fun MainScreen(
|
|||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
text = " updated by ",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.secondary
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "luhf",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package ch.mathieubroillet.djiffchack.ui.theme
|
package ch.mathieubroillet.djiffchack.ui.theme
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
val Purple80 = Color(0xFFD0BCFF)
|
val Purple80 = Color(0xFFD0BCFF)
|
||||||
val PurpleGrey80 = Color(0xFFCCC2DC)
|
val PurpleGrey80 = Color(0xFFCCC2DC)
|
||||||
val Pink80 = Color(0xFFEFB8C8)
|
val Pink80 = Color(0xFFEFB8C8)
|
||||||
|
|
||||||
val Purple40 = Color(0xFF6650a4)
|
val Purple40 = Color(0xFF6650a4)
|
||||||
val PurpleGrey40 = Color(0xFF625b71)
|
val PurpleGrey40 = Color(0xFF625b71)
|
||||||
val Pink40 = Color(0xFF7D5260)
|
val Pink40 = Color(0xFF7D5260)
|
||||||
@ -1,57 +1,57 @@
|
|||||||
package ch.mathieubroillet.djiffchack.ui.theme
|
package ch.mathieubroillet.djiffchack.ui.theme
|
||||||
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.compose.foundation.isSystemInDarkTheme
|
import androidx.compose.foundation.isSystemInDarkTheme
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.darkColorScheme
|
import androidx.compose.material3.darkColorScheme
|
||||||
import androidx.compose.material3.dynamicDarkColorScheme
|
import androidx.compose.material3.dynamicDarkColorScheme
|
||||||
import androidx.compose.material3.dynamicLightColorScheme
|
import androidx.compose.material3.dynamicLightColorScheme
|
||||||
import androidx.compose.material3.lightColorScheme
|
import androidx.compose.material3.lightColorScheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
|
||||||
private val DarkColorScheme = darkColorScheme(
|
private val DarkColorScheme = darkColorScheme(
|
||||||
primary = Purple80,
|
primary = Purple80,
|
||||||
secondary = PurpleGrey80,
|
secondary = PurpleGrey80,
|
||||||
tertiary = Pink80
|
tertiary = Pink80
|
||||||
)
|
)
|
||||||
|
|
||||||
private val LightColorScheme = lightColorScheme(
|
private val LightColorScheme = lightColorScheme(
|
||||||
primary = Purple40,
|
primary = Purple40,
|
||||||
secondary = PurpleGrey40,
|
secondary = PurpleGrey40,
|
||||||
tertiary = Pink40
|
tertiary = Pink40
|
||||||
|
|
||||||
/* Other default colors to override
|
/* Other default colors to override
|
||||||
background = Color(0xFFFFFBFE),
|
background = Color(0xFFFFFBFE),
|
||||||
surface = Color(0xFFFFFBFE),
|
surface = Color(0xFFFFFBFE),
|
||||||
onPrimary = Color.White,
|
onPrimary = Color.White,
|
||||||
onSecondary = Color.White,
|
onSecondary = Color.White,
|
||||||
onTertiary = Color.White,
|
onTertiary = Color.White,
|
||||||
onBackground = Color(0xFF1C1B1F),
|
onBackground = Color(0xFF1C1B1F),
|
||||||
onSurface = Color(0xFF1C1B1F),
|
onSurface = Color(0xFF1C1B1F),
|
||||||
*/
|
*/
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DJI_FCC_HACK_Theme(
|
fun DJI_FCC_HACK_Theme(
|
||||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||||
// Dynamic color is available on Android 12+
|
// Dynamic color is available on Android 12+
|
||||||
dynamicColor: Boolean = true,
|
dynamicColor: Boolean = true,
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit
|
||||||
) {
|
) {
|
||||||
val colorScheme = when {
|
val colorScheme = when {
|
||||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
darkTheme -> DarkColorScheme
|
darkTheme -> DarkColorScheme
|
||||||
else -> LightColorScheme
|
else -> LightColorScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialTheme(
|
MaterialTheme(
|
||||||
colorScheme = colorScheme,
|
colorScheme = colorScheme,
|
||||||
typography = Typography,
|
typography = Typography,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -1,34 +1,34 @@
|
|||||||
package ch.mathieubroillet.djiffchack.ui.theme
|
package ch.mathieubroillet.djiffchack.ui.theme
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
import androidx.compose.material3.Typography
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
// Set of Material typography styles to start with
|
||||||
val Typography = Typography(
|
val Typography = Typography(
|
||||||
bodyLarge = TextStyle(
|
bodyLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
lineHeight = 24.sp,
|
lineHeight = 24.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
)
|
)
|
||||||
/* Other default text styles to override
|
/* Other default text styles to override
|
||||||
titleLarge = TextStyle(
|
titleLarge = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
fontSize = 22.sp,
|
fontSize = 22.sp,
|
||||||
lineHeight = 28.sp,
|
lineHeight = 28.sp,
|
||||||
letterSpacing = 0.sp
|
letterSpacing = 0.sp
|
||||||
),
|
),
|
||||||
labelSmall = TextStyle(
|
labelSmall = TextStyle(
|
||||||
fontFamily = FontFamily.Default,
|
fontFamily = FontFamily.Default,
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.Medium,
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
lineHeight = 16.sp,
|
lineHeight = 16.sp,
|
||||||
letterSpacing = 0.5.sp
|
letterSpacing = 0.5.sp
|
||||||
)
|
)
|
||||||
*/
|
*/
|
||||||
)
|
)
|
||||||
@ -1,15 +1,15 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="46.51dp"
|
android:width="46.51dp"
|
||||||
android:height="26.87dp"
|
android:height="26.87dp"
|
||||||
android:viewportWidth="164.78"
|
android:viewportWidth="164.78"
|
||||||
android:viewportHeight="95.2">
|
android:viewportHeight="95.2">
|
||||||
<path
|
<path
|
||||||
android:pathData="m120.64,69.13 l12.16,-50.5l-26.1,-0l-11.12,45.29c-1.61,8.83 -11.1,12.96 -17.84,13.06l-18.49,-0l-6.27,18.2l38.86,-0c9.59,-0 23.74,-4.91 28.81,-26.06"
|
android:pathData="m120.64,69.13 l12.16,-50.5l-26.1,-0l-11.12,45.29c-1.61,8.83 -11.1,12.96 -17.84,13.06l-18.49,-0l-6.27,18.2l38.86,-0c9.59,-0 23.74,-4.91 28.81,-26.06"
|
||||||
android:fillColor="#000000"/>
|
android:fillColor="#000000"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m65.86,51.32 l12.27,-51.33l26.86,-0l-13.96,58.39c-2.69,11.27 -11.07,13.98 -18.81,13.98l-61.91,-0c-6.82,-0 -12.54,-2.9 -9.44,-15.92l5.57,-23.29c2.83,-11.81 11.61,-14.51 17.96,-14.51l43.21,-0l-3.48,14.55l-22.06,-0c-3.24,-0 -5.02,0.7 -5.93,4.49l-3.56,14.87c-1.27,5.34 0.59,5.71 4.5,5.71l20.21,-0c3.7,-0 6.95,-0.23 8.56,-6.94"
|
android:pathData="m65.86,51.32 l12.27,-51.33l26.86,-0l-13.96,58.39c-2.69,11.27 -11.07,13.98 -18.81,13.98l-61.91,-0c-6.82,-0 -12.54,-2.9 -9.44,-15.92l5.57,-23.29c2.83,-11.81 11.61,-14.51 17.96,-14.51l43.21,-0l-3.48,14.55l-22.06,-0c-3.24,-0 -5.02,0.7 -5.93,4.49l-3.56,14.87c-1.27,5.34 0.59,5.71 4.5,5.71l20.21,-0c3.7,-0 6.95,-0.23 8.56,-6.94"
|
||||||
android:fillColor="#000000"/>
|
android:fillColor="#000000"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m138.69,18.63 l-12.64,53.73l26.09,-0l12.64,-53.73l-26.09,-0z"
|
android:pathData="m138.69,18.63 l-12.64,53.73l26.09,-0l12.64,-53.73l-26.09,-0z"
|
||||||
android:fillColor="#000000"/>
|
android:fillColor="#000000"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,15 +1,15 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="46.51dp"
|
android:width="46.51dp"
|
||||||
android:height="26.87dp"
|
android:height="26.87dp"
|
||||||
android:viewportWidth="164.78"
|
android:viewportWidth="164.78"
|
||||||
android:viewportHeight="95.2">
|
android:viewportHeight="95.2">
|
||||||
<path
|
<path
|
||||||
android:pathData="m120.64,69.13 l12.16,-50.5l-26.1,-0l-11.12,45.29c-1.61,8.83 -11.1,12.96 -17.84,13.06l-18.49,-0l-6.27,18.2l38.86,-0c9.59,-0 23.74,-4.91 28.81,-26.06"
|
android:pathData="m120.64,69.13 l12.16,-50.5l-26.1,-0l-11.12,45.29c-1.61,8.83 -11.1,12.96 -17.84,13.06l-18.49,-0l-6.27,18.2l38.86,-0c9.59,-0 23.74,-4.91 28.81,-26.06"
|
||||||
android:fillColor="#fff"/>
|
android:fillColor="#fff"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m65.86,51.32 l12.27,-51.33l26.86,-0l-13.96,58.39c-2.69,11.27 -11.07,13.98 -18.81,13.98l-61.91,-0c-6.82,-0 -12.54,-2.9 -9.44,-15.92l5.57,-23.29c2.83,-11.81 11.61,-14.51 17.96,-14.51l43.21,-0l-3.48,14.55l-22.06,-0c-3.24,-0 -5.02,0.7 -5.93,4.49l-3.56,14.87c-1.27,5.34 0.59,5.71 4.5,5.71l20.21,-0c3.7,-0 6.95,-0.23 8.56,-6.94"
|
android:pathData="m65.86,51.32 l12.27,-51.33l26.86,-0l-13.96,58.39c-2.69,11.27 -11.07,13.98 -18.81,13.98l-61.91,-0c-6.82,-0 -12.54,-2.9 -9.44,-15.92l5.57,-23.29c2.83,-11.81 11.61,-14.51 17.96,-14.51l43.21,-0l-3.48,14.55l-22.06,-0c-3.24,-0 -5.02,0.7 -5.93,4.49l-3.56,14.87c-1.27,5.34 0.59,5.71 4.5,5.71l20.21,-0c3.7,-0 6.95,-0.23 8.56,-6.94"
|
||||||
android:fillColor="#fff"/>
|
android:fillColor="#fff"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m138.69,18.63 l-12.64,53.73l26.09,-0l12.64,-53.73l-26.09,-0z"
|
android:pathData="m138.69,18.63 l-12.64,53.73l26.09,-0l12.64,-53.73l-26.09,-0z"
|
||||||
android:fillColor="#fff"/>
|
android:fillColor="#fff"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,35 +1,35 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="1066"
|
android:viewportWidth="1066"
|
||||||
android:viewportHeight="1053">
|
android:viewportHeight="1053">
|
||||||
<path
|
<path
|
||||||
android:pathData="m211.8,0.9h642.4c116.7,0 211.2,94.6 211.2,211.2v629c0,116.6 -94.5,211.1 -211.2,211.1h-642.4c-116.6,0 -211.2,-94.5 -211.2,-211.1v-629c0,-116.6 94.6,-211.2 211.2,-211.2z"
|
android:pathData="m211.8,0.9h642.4c116.7,0 211.2,94.6 211.2,211.2v629c0,116.6 -94.5,211.1 -211.2,211.1h-642.4c-116.6,0 -211.2,-94.5 -211.2,-211.1v-629c0,-116.6 94.6,-211.2 211.2,-211.2z"
|
||||||
android:fillColor="#be3876"/>
|
android:fillColor="#be3876"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m1.9,838.7l-0.1,-395.1 511.6,608.7h-297.9c-123.3,-1.5 -213.2,-92.4 -213.6,-213.6z"
|
android:pathData="m1.9,838.7l-0.1,-395.1 511.6,608.7h-297.9c-123.3,-1.5 -213.2,-92.4 -213.6,-213.6z"
|
||||||
android:fillColor="#9b2b5f"/>
|
android:fillColor="#9b2b5f"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m278.6,772.9l-76.1,-90.6 -85,347.3c41.6,22.7 82.8,22 128,22.7z"
|
android:pathData="m278.6,772.9l-76.1,-90.6 -85,347.3c41.6,22.7 82.8,22 128,22.7z"
|
||||||
android:fillColor="#a62862"/>
|
android:fillColor="#a62862"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m245.5,1052.3h267.9l-234.8,-279.4z"
|
android:pathData="m245.5,1052.3h267.9l-234.8,-279.4z"
|
||||||
android:fillColor="#a0406c"/>
|
android:fillColor="#a0406c"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m278.6,772.9l80.9,-772 -157,681.4z"
|
android:pathData="m278.6,772.9l80.9,-772 -157,681.4z"
|
||||||
android:fillColor="#c44b83"/>
|
android:fillColor="#c44b83"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m278.6,772.9l80.9,-772h172.9l314.3,374.9 -160.7,676.4 -172.6,0.1z"
|
android:pathData="m278.6,772.9l80.9,-772h172.9l314.3,374.9 -160.7,676.4 -172.6,0.1z"
|
||||||
android:fillColor="#a62862"/>
|
android:fillColor="#a62862"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m1065.4,510.1l-533.4,-509.2 533.4,635.5z"
|
android:pathData="m1065.4,510.1l-533.4,-509.2 533.4,635.5z"
|
||||||
android:fillColor="#c44b83"/>
|
android:fillColor="#c44b83"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m1065.4,510.1l-533.4,-509.2h322.2c122.3,0.1 211.4,101.1 211.2,211.2z"
|
android:pathData="m1065.4,510.1l-533.4,-509.2h322.2c122.3,0.1 211.4,101.1 211.2,211.2z"
|
||||||
android:fillColor="#a0406c"/>
|
android:fillColor="#a0406c"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m686,1052.5c133,-56.7 272.3,-129.9 379.2,-252.8v54.5c0,85.5 -85.9,198.3 -205.5,198.3z"
|
android:pathData="m686,1052.5c133,-56.7 272.3,-129.9 379.2,-252.8v54.5c0,85.5 -85.9,198.3 -205.5,198.3z"
|
||||||
android:strokeAlpha="0.1"
|
android:strokeAlpha="0.1"
|
||||||
android:fillColor="#ac3a6f"
|
android:fillColor="#ac3a6f"
|
||||||
android:fillAlpha="0.1"/>
|
android:fillAlpha="0.1"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="633"
|
android:viewportWidth="633"
|
||||||
android:viewportHeight="586">
|
android:viewportHeight="586">
|
||||||
<group android:scaleX="0.67"
|
<group android:scaleX="0.67"
|
||||||
android:scaleY="0.67"
|
android:scaleY="0.67"
|
||||||
android:translateX="104.445"
|
android:translateX="104.445"
|
||||||
android:translateY="96.69">
|
android:translateY="96.69">
|
||||||
<path
|
<path
|
||||||
android:pathData="m319.7,122.6v100.2q0,5.7 -1.5,10.4 -1.4,4.5 -3.8,8 -2.4,3.4 -5.6,5.9 -3.1,2.4 -6.7,3.9 -3.4,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1q-5.7,0 -10.3,-1.5 -4.5,-1.4 -8,-3.8 -3.4,-2.5 -5.9,-5.6 -2.4,-3.2 -3.9,-6.6 -1.6,-3.5 -2.3,-7 -0.7,-3.6 -0.7,-6.7v-30.1q0,-8.5 3.1,-14.4 3.1,-6 7.7,-9.7 4.7,-3.7 10.2,-5.3 5.5,-1.7 10.1,-1.7h40.1v22.2h-39.9q-4.5,0 -6.8,2.4 -2.3,2.2 -2.3,6.5v29.9q0,4.6 2.2,6.9 2.3,2.3 6.7,2.3h40.1q4.5,0 6.7,-2.3 2.2,-2.3 2.2,-6.7v-100.2zM363.5,122.6v20.1h-22.2v-20.1zM363.5,161.6v101.3q0,5.7 -1.5,10.3 -1.4,4.6 -3.8,8.1 -2.4,3.4 -5.6,5.9 -3.1,2.3 -6.6,3.9 -3.5,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1v-22.2h40.1q4.4,0 6.6,-2.3 2.3,-2.4 2.3,-6.7v-101.3zM407.3,122.6v20.1h-22.2v-20.1zM407.3,161.6v92.4h-22.2v-92.4zM232.4,391.5v22.4h-70.2v-22.4zM242.3,341.5v22.2h-89.1v100.3h-22.2v-111.3q0,-2.3 0.9,-4.4 0.8,-2 2.3,-3.6 1.5,-1.5 3.6,-2.4 2,-0.8 4.4,-0.8zM370,441.8v22.2h-80.2q-3.1,0 -6.6,-0.7 -3.6,-0.8 -7.1,-2.3 -3.4,-1.5 -6.5,-3.9 -3.2,-2.5 -5.7,-5.9 -2.4,-3.5 -3.8,-8 -1.5,-4.7 -1.5,-10.4v-60.1q0,-3.1 0.7,-6.6 0.8,-3.6 2.3,-7 1.6,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8.1,-3.9 4.5,-1.4 10.2,-1.4h80.2v22.2h-80.2q-4.3,0 -6.6,2.3 -2.4,2.3 -2.4,6.8v60q0,4.3 2.4,6.7 2.3,2.3 6.6,2.3zM499.8,441.8v22.2h-80.1q-3.1,0 -6.7,-0.7 -3.5,-0.8 -7,-2.3 -3.4,-1.5 -6.6,-3.9 -3.2,-2.5 -5.6,-5.9 -2.4,-3.5 -3.9,-8 -1.4,-4.7 -1.4,-10.4v-60.1q0,-3.1 0.7,-6.6 0.7,-3.6 2.3,-7 1.5,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8,-3.9 4.5,-1.4 10.3,-1.4h80.1v22.2h-80.1q-4.4,0 -6.7,2.3 -2.3,2.3 -2.3,6.8v60q0,4.3 2.3,6.7 2.4,2.3 6.7,2.3z"
|
android:pathData="m319.7,122.6v100.2q0,5.7 -1.5,10.4 -1.4,4.5 -3.8,8 -2.4,3.4 -5.6,5.9 -3.1,2.4 -6.7,3.9 -3.4,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1q-5.7,0 -10.3,-1.5 -4.5,-1.4 -8,-3.8 -3.4,-2.5 -5.9,-5.6 -2.4,-3.2 -3.9,-6.6 -1.6,-3.5 -2.3,-7 -0.7,-3.6 -0.7,-6.7v-30.1q0,-8.5 3.1,-14.4 3.1,-6 7.7,-9.7 4.7,-3.7 10.2,-5.3 5.5,-1.7 10.1,-1.7h40.1v22.2h-39.9q-4.5,0 -6.8,2.4 -2.3,2.2 -2.3,6.5v29.9q0,4.6 2.2,6.9 2.3,2.3 6.7,2.3h40.1q4.5,0 6.7,-2.3 2.2,-2.3 2.2,-6.7v-100.2zM363.5,122.6v20.1h-22.2v-20.1zM363.5,161.6v101.3q0,5.7 -1.5,10.3 -1.4,4.6 -3.8,8.1 -2.4,3.4 -5.6,5.9 -3.1,2.3 -6.6,3.9 -3.5,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1v-22.2h40.1q4.4,0 6.6,-2.3 2.3,-2.4 2.3,-6.7v-101.3zM407.3,122.6v20.1h-22.2v-20.1zM407.3,161.6v92.4h-22.2v-92.4zM232.4,391.5v22.4h-70.2v-22.4zM242.3,341.5v22.2h-89.1v100.3h-22.2v-111.3q0,-2.3 0.9,-4.4 0.8,-2 2.3,-3.6 1.5,-1.5 3.6,-2.4 2,-0.8 4.4,-0.8zM370,441.8v22.2h-80.2q-3.1,0 -6.6,-0.7 -3.6,-0.8 -7.1,-2.3 -3.4,-1.5 -6.5,-3.9 -3.2,-2.5 -5.7,-5.9 -2.4,-3.5 -3.8,-8 -1.5,-4.7 -1.5,-10.4v-60.1q0,-3.1 0.7,-6.6 0.8,-3.6 2.3,-7 1.6,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8.1,-3.9 4.5,-1.4 10.2,-1.4h80.2v22.2h-80.2q-4.3,0 -6.6,2.3 -2.4,2.3 -2.4,6.8v60q0,4.3 2.4,6.7 2.3,2.3 6.6,2.3zM499.8,441.8v22.2h-80.1q-3.1,0 -6.7,-0.7 -3.5,-0.8 -7,-2.3 -3.4,-1.5 -6.6,-3.9 -3.2,-2.5 -5.6,-5.9 -2.4,-3.5 -3.9,-8 -1.4,-4.7 -1.4,-10.4v-60.1q0,-3.1 0.7,-6.6 0.7,-3.6 2.3,-7 1.5,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8,-3.9 4.5,-1.4 10.3,-1.4h80.1v22.2h-80.1q-4.4,0 -6.7,2.3 -2.3,2.3 -2.3,6.8v60q0,4.3 2.3,6.7 2.4,2.3 6.7,2.3z"
|
||||||
android:fillColor="#fff"/>
|
android:fillColor="#fff"/>
|
||||||
</group>
|
</group>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,35 +1,35 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="1066"
|
android:viewportWidth="1066"
|
||||||
android:viewportHeight="1053">
|
android:viewportHeight="1053">
|
||||||
<path
|
<path
|
||||||
android:pathData="m211.8,0.9h642.4c116.7,0 211.2,94.6 211.2,211.2v629c0,116.6 -94.5,211.1 -211.2,211.1h-642.4c-116.6,0 -211.2,-94.5 -211.2,-211.1v-629c0,-116.6 94.6,-211.2 211.2,-211.2z"
|
android:pathData="m211.8,0.9h642.4c116.7,0 211.2,94.6 211.2,211.2v629c0,116.6 -94.5,211.1 -211.2,211.1h-642.4c-116.6,0 -211.2,-94.5 -211.2,-211.1v-629c0,-116.6 94.6,-211.2 211.2,-211.2z"
|
||||||
android:fillColor="#be3876"/>
|
android:fillColor="#be3876"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m1.9,838.7l-0.1,-395.1 511.6,608.7h-297.9c-123.3,-1.5 -213.2,-92.4 -213.6,-213.6z"
|
android:pathData="m1.9,838.7l-0.1,-395.1 511.6,608.7h-297.9c-123.3,-1.5 -213.2,-92.4 -213.6,-213.6z"
|
||||||
android:fillColor="#9b2b5f"/>
|
android:fillColor="#9b2b5f"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m278.6,772.9l-76.1,-90.6 -85,347.3c41.6,22.7 82.8,22 128,22.7z"
|
android:pathData="m278.6,772.9l-76.1,-90.6 -85,347.3c41.6,22.7 82.8,22 128,22.7z"
|
||||||
android:fillColor="#a62862"/>
|
android:fillColor="#a62862"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m245.5,1052.3h267.9l-234.8,-279.4z"
|
android:pathData="m245.5,1052.3h267.9l-234.8,-279.4z"
|
||||||
android:fillColor="#a0406c"/>
|
android:fillColor="#a0406c"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m278.6,772.9l80.9,-772 -157,681.4z"
|
android:pathData="m278.6,772.9l80.9,-772 -157,681.4z"
|
||||||
android:fillColor="#c44b83"/>
|
android:fillColor="#c44b83"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m278.6,772.9l80.9,-772h172.9l314.3,374.9 -160.7,676.4 -172.6,0.1z"
|
android:pathData="m278.6,772.9l80.9,-772h172.9l314.3,374.9 -160.7,676.4 -172.6,0.1z"
|
||||||
android:fillColor="#a62862"/>
|
android:fillColor="#a62862"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m1065.4,510.1l-533.4,-509.2 533.4,635.5z"
|
android:pathData="m1065.4,510.1l-533.4,-509.2 533.4,635.5z"
|
||||||
android:fillColor="#c44b83"/>
|
android:fillColor="#c44b83"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m1065.4,510.1l-533.4,-509.2h322.2c122.3,0.1 211.4,101.1 211.2,211.2z"
|
android:pathData="m1065.4,510.1l-533.4,-509.2h322.2c122.3,0.1 211.4,101.1 211.2,211.2z"
|
||||||
android:fillColor="#a0406c"/>
|
android:fillColor="#a0406c"/>
|
||||||
<path
|
<path
|
||||||
android:pathData="m686,1052.5c133,-56.7 272.3,-129.9 379.2,-252.8v54.5c0,85.5 -85.9,198.3 -205.5,198.3z"
|
android:pathData="m686,1052.5c133,-56.7 272.3,-129.9 379.2,-252.8v54.5c0,85.5 -85.9,198.3 -205.5,198.3z"
|
||||||
android:strokeAlpha="0.1"
|
android:strokeAlpha="0.1"
|
||||||
android:fillColor="#ac3a6f"
|
android:fillColor="#ac3a6f"
|
||||||
android:fillAlpha="0.1"/>
|
android:fillAlpha="0.1"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:width="108dp"
|
android:width="108dp"
|
||||||
android:height="108dp"
|
android:height="108dp"
|
||||||
android:viewportWidth="633"
|
android:viewportWidth="633"
|
||||||
android:viewportHeight="586">
|
android:viewportHeight="586">
|
||||||
<path
|
<path
|
||||||
android:pathData="m319.7,122.6v100.2q0,5.7 -1.5,10.4 -1.4,4.5 -3.8,8 -2.4,3.4 -5.6,5.9 -3.1,2.4 -6.7,3.9 -3.4,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1q-5.7,0 -10.3,-1.5 -4.5,-1.4 -8,-3.8 -3.4,-2.5 -5.9,-5.6 -2.4,-3.2 -3.9,-6.6 -1.6,-3.5 -2.3,-7 -0.7,-3.6 -0.7,-6.7v-30.1q0,-8.5 3.1,-14.4 3.1,-6 7.7,-9.7 4.7,-3.7 10.2,-5.3 5.5,-1.7 10.1,-1.7h40.1v22.2h-39.9q-4.5,0 -6.8,2.4 -2.3,2.2 -2.3,6.5v29.9q0,4.6 2.2,6.9 2.3,2.3 6.7,2.3h40.1q4.5,0 6.7,-2.3 2.2,-2.3 2.2,-6.7v-100.2zM363.5,122.6v20.1h-22.2v-20.1zM363.5,161.6v101.3q0,5.7 -1.5,10.3 -1.4,4.6 -3.8,8.1 -2.4,3.4 -5.6,5.9 -3.1,2.3 -6.6,3.9 -3.5,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1v-22.2h40.1q4.4,0 6.6,-2.3 2.3,-2.4 2.3,-6.7v-101.3zM407.3,122.6v20.1h-22.2v-20.1zM407.3,161.6v92.4h-22.2v-92.4zM232.4,391.5v22.4h-70.2v-22.4zM242.3,341.5v22.2h-89.1v100.3h-22.2v-111.3q0,-2.3 0.9,-4.4 0.8,-2 2.3,-3.6 1.5,-1.5 3.6,-2.4 2,-0.8 4.4,-0.8zM370,441.8v22.2h-80.2q-3.1,0 -6.6,-0.7 -3.6,-0.8 -7.1,-2.3 -3.4,-1.5 -6.5,-3.9 -3.2,-2.5 -5.7,-5.9 -2.4,-3.5 -3.8,-8 -1.5,-4.7 -1.5,-10.4v-60.1q0,-3.1 0.7,-6.6 0.8,-3.6 2.3,-7 1.6,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8.1,-3.9 4.5,-1.4 10.2,-1.4h80.2v22.2h-80.2q-4.3,0 -6.6,2.3 -2.4,2.3 -2.4,6.8v60q0,4.3 2.4,6.7 2.3,2.3 6.6,2.3zM499.8,441.8v22.2h-80.1q-3.1,0 -6.7,-0.7 -3.5,-0.8 -7,-2.3 -3.4,-1.5 -6.6,-3.9 -3.2,-2.5 -5.6,-5.9 -2.4,-3.5 -3.9,-8 -1.4,-4.7 -1.4,-10.4v-60.1q0,-3.1 0.7,-6.6 0.7,-3.6 2.3,-7 1.5,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8,-3.9 4.5,-1.4 10.3,-1.4h80.1v22.2h-80.1q-4.4,0 -6.7,2.3 -2.3,2.3 -2.3,6.8v60q0,4.3 2.3,6.7 2.4,2.3 6.7,2.3z"
|
android:pathData="m319.7,122.6v100.2q0,5.7 -1.5,10.4 -1.4,4.5 -3.8,8 -2.4,3.4 -5.6,5.9 -3.1,2.4 -6.7,3.9 -3.4,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1q-5.7,0 -10.3,-1.5 -4.5,-1.4 -8,-3.8 -3.4,-2.5 -5.9,-5.6 -2.4,-3.2 -3.9,-6.6 -1.6,-3.5 -2.3,-7 -0.7,-3.6 -0.7,-6.7v-30.1q0,-8.5 3.1,-14.4 3.1,-6 7.7,-9.7 4.7,-3.7 10.2,-5.3 5.5,-1.7 10.1,-1.7h40.1v22.2h-39.9q-4.5,0 -6.8,2.4 -2.3,2.2 -2.3,6.5v29.9q0,4.6 2.2,6.9 2.3,2.3 6.7,2.3h40.1q4.5,0 6.7,-2.3 2.2,-2.3 2.2,-6.7v-100.2zM363.5,122.6v20.1h-22.2v-20.1zM363.5,161.6v101.3q0,5.7 -1.5,10.3 -1.4,4.6 -3.8,8.1 -2.4,3.4 -5.6,5.9 -3.1,2.3 -6.6,3.9 -3.5,1.5 -7,2.3 -3.5,0.7 -6.6,0.7h-40.1v-22.2h40.1q4.4,0 6.6,-2.3 2.3,-2.4 2.3,-6.7v-101.3zM407.3,122.6v20.1h-22.2v-20.1zM407.3,161.6v92.4h-22.2v-92.4zM232.4,391.5v22.4h-70.2v-22.4zM242.3,341.5v22.2h-89.1v100.3h-22.2v-111.3q0,-2.3 0.9,-4.4 0.8,-2 2.3,-3.6 1.5,-1.5 3.6,-2.4 2,-0.8 4.4,-0.8zM370,441.8v22.2h-80.2q-3.1,0 -6.6,-0.7 -3.6,-0.8 -7.1,-2.3 -3.4,-1.5 -6.5,-3.9 -3.2,-2.5 -5.7,-5.9 -2.4,-3.5 -3.8,-8 -1.5,-4.7 -1.5,-10.4v-60.1q0,-3.1 0.7,-6.6 0.8,-3.6 2.3,-7 1.6,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8.1,-3.9 4.5,-1.4 10.2,-1.4h80.2v22.2h-80.2q-4.3,0 -6.6,2.3 -2.4,2.3 -2.4,6.8v60q0,4.3 2.4,6.7 2.3,2.3 6.6,2.3zM499.8,441.8v22.2h-80.1q-3.1,0 -6.7,-0.7 -3.5,-0.8 -7,-2.3 -3.4,-1.5 -6.6,-3.9 -3.2,-2.5 -5.6,-5.9 -2.4,-3.5 -3.9,-8 -1.4,-4.7 -1.4,-10.4v-60.1q0,-3.1 0.7,-6.6 0.7,-3.6 2.3,-7 1.5,-3.5 4,-6.7 2.5,-3.2 5.9,-5.6 3.5,-2.4 8,-3.9 4.5,-1.4 10.3,-1.4h80.1v22.2h-80.1q-4.4,0 -6.7,2.3 -2.3,2.3 -2.3,6.8v60q0,4.3 2.3,6.7 2.4,2.3 6.7,2.3z"
|
||||||
android:fillColor="#fff"/>
|
android:fillColor="#fff"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/icon_background"/>
|
<background android:drawable="@drawable/icon_background"/>
|
||||||
<foreground android:drawable="@drawable/icon_foreground"/>
|
<foreground android:drawable="@drawable/icon_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/icon_foreground"/>
|
<monochrome android:drawable="@drawable/icon_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@drawable/icon_background"/>
|
<background android:drawable="@drawable/icon_background"/>
|
||||||
<foreground android:drawable="@drawable/icon_foreground"/>
|
<foreground android:drawable="@drawable/icon_foreground"/>
|
||||||
<monochrome android:drawable="@drawable/icon_foreground"/>
|
<monochrome android:drawable="@drawable/icon_foreground"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
@ -1,10 +1,10 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="purple_200">#FFBB86FC</color>
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
<color name="purple_500">#FF6200EE</color>
|
<color name="purple_500">#FF6200EE</color>
|
||||||
<color name="purple_700">#FF3700B3</color>
|
<color name="purple_700">#FF3700B3</color>
|
||||||
<color name="teal_200">#FF03DAC5</color>
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
<color name="teal_700">#FF018786</color>
|
<color name="teal_700">#FF018786</color>
|
||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,3 +1,3 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">DJI FCC</string>
|
<string name="app_name">DJI FCC</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
|
||||||
<style name="Theme.Djiffchack" parent="android:Theme.Material.Light.NoActionBar" />
|
<style name="Theme.Djiffchack" parent="android:Theme.Material.Light.NoActionBar" />
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,13 +1,13 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
Sample backup rules file; uncomment and customize as necessary.
|
Sample backup rules file; uncomment and customize as necessary.
|
||||||
See https://developer.android.com/guide/topics/data/autobackup
|
See https://developer.android.com/guide/topics/data/autobackup
|
||||||
for details.
|
for details.
|
||||||
Note: This file is ignored for devices older that API 31
|
Note: This file is ignored for devices older that API 31
|
||||||
See https://developer.android.com/about/versions/12/backup-restore
|
See https://developer.android.com/about/versions/12/backup-restore
|
||||||
-->
|
-->
|
||||||
<full-backup-content>
|
<full-backup-content>
|
||||||
<!--
|
<!--
|
||||||
<include domain="sharedpref" path="."/>
|
<include domain="sharedpref" path="."/>
|
||||||
<exclude domain="sharedpref" path="device.xml"/>
|
<exclude domain="sharedpref" path="device.xml"/>
|
||||||
-->
|
-->
|
||||||
</full-backup-content>
|
</full-backup-content>
|
||||||
@ -1,19 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?><!--
|
<?xml version="1.0" encoding="utf-8"?><!--
|
||||||
Sample data extraction rules file; uncomment and customize as necessary.
|
Sample data extraction rules file; uncomment and customize as necessary.
|
||||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||||
for details.
|
for details.
|
||||||
-->
|
-->
|
||||||
<data-extraction-rules>
|
<data-extraction-rules>
|
||||||
<cloud-backup>
|
<cloud-backup>
|
||||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||||
<include .../>
|
<include .../>
|
||||||
<exclude .../>
|
<exclude .../>
|
||||||
-->
|
-->
|
||||||
</cloud-backup>
|
</cloud-backup>
|
||||||
<!--
|
<!--
|
||||||
<device-transfer>
|
<device-transfer>
|
||||||
<include .../>
|
<include .../>
|
||||||
<exclude .../>
|
<exclude .../>
|
||||||
</device-transfer>
|
</device-transfer>
|
||||||
-->
|
-->
|
||||||
</data-extraction-rules>
|
</data-extraction-rules>
|
||||||
@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<usb-device
|
<usb-device
|
||||||
product-id="4128"
|
product-id="4128"
|
||||||
vendor-id="11427"/>
|
vendor-id="11427"/>
|
||||||
</resources>
|
</resources>
|
||||||
@ -1,17 +1,17 @@
|
|||||||
package ch.mathieubroillet.djiffchack
|
package ch.mathieubroillet.djiffchack
|
||||||
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
import org.junit.Assert.*
|
import org.junit.Assert.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Example local unit test, which will execute on the development machine (host).
|
* Example local unit test, which will execute on the development machine (host).
|
||||||
*
|
*
|
||||||
* See [testing documentation](http://d.android.com/tools/testing).
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
*/
|
*/
|
||||||
class ExampleUnitTest {
|
class ExampleUnitTest {
|
||||||
@Test
|
@Test
|
||||||
fun addition_isCorrect() {
|
fun addition_isCorrect() {
|
||||||
assertEquals(4, 2 + 2)
|
assertEquals(4, 2 + 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,6 @@
|
|||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
plugins {
|
plugins {
|
||||||
alias(libs.plugins.android.application) apply false
|
alias(libs.plugins.android.application) apply false
|
||||||
alias(libs.plugins.kotlin.android) apply false
|
alias(libs.plugins.kotlin.android) apply false
|
||||||
alias(libs.plugins.kotlin.compose) apply false
|
alias(libs.plugins.kotlin.compose) apply false
|
||||||
}
|
}
|
||||||
84
dji_fcc_patch.py
Normal file
84
dji_fcc_patch.py
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
# DJI FCC Patch Script for Windows
|
||||||
|
#
|
||||||
|
# This script sends a "magic packet" to a DJI controller
|
||||||
|
# to enable FCC mode, which can increase the drone's signal range.
|
||||||
|
#
|
||||||
|
# Credits:
|
||||||
|
# - Based on the Android app by Mathieu Broillet: https://github.com/M4TH1EU/DJI-FCC-HACK
|
||||||
|
# - Original research by @galbb on MavicPilots.com
|
||||||
|
#
|
||||||
|
# HOW TO USE:
|
||||||
|
# 1. Install Python from the Microsoft Store or python.org.
|
||||||
|
# 2. Install the pyserial library by opening a Command Prompt or PowerShell and running:
|
||||||
|
# pip install pyserial
|
||||||
|
# 3. Turn on your drone and controller, and wait for them to connect.
|
||||||
|
# 4. Connect your PC to the bottom USB port of the controller.
|
||||||
|
# 5. Run this script.
|
||||||
|
# 6. If successful, disconnect the PC and connect your phone to the top USB port of the controller.
|
||||||
|
#
|
||||||
|
# DISCLAIMER: Use at your own risk. This script modifies your device's operation.
|
||||||
|
# The authors are not responsible for any damage or legal issues that may arise.
|
||||||
|
|
||||||
|
import serial
|
||||||
|
import serial.tools.list_ports
|
||||||
|
import time
|
||||||
|
|
||||||
|
# DJI Controller USB identifiers
|
||||||
|
VENDOR_ID = 11427
|
||||||
|
PRODUCT_ID = 4128
|
||||||
|
|
||||||
|
# The "magic bytes" that enable FCC mode
|
||||||
|
# (from ch.mathieubroillet.djiffchack.Constants.kt)
|
||||||
|
BYTES_1 = bytes([85, 13, 4, 33, 42, 31, 0, 0, 0, 0, 1, -122 & 0xFF, 32])
|
||||||
|
BYTES_2 = bytes([85, 24, 4, 32, 2, 9, 0, 0, 64, 9, 39, 0, 2, 72, 0, -1 & 0xFF, -1 & 0xFF, 2, 0, 0, 0, 0, -127 & 0xFF, 31])
|
||||||
|
|
||||||
|
def find_dji_controller():
|
||||||
|
"""Finds the COM port of the connected DJI controller."""
|
||||||
|
ports = serial.tools.list_ports.comports()
|
||||||
|
for port in ports:
|
||||||
|
if port.vid == VENDOR_ID and port.pid == PRODUCT_ID:
|
||||||
|
print(f"Found DJI Controller at {port.device}")
|
||||||
|
return port.device
|
||||||
|
return None
|
||||||
|
|
||||||
|
def send_patch(com_port):
|
||||||
|
"""Opens the serial port and sends the patch bytes."""
|
||||||
|
try:
|
||||||
|
with serial.Serial(com_port, baudrate=19200, bytesize=8, parity='N', stopbits=1, timeout=1) as ser:
|
||||||
|
print("Sending patch...")
|
||||||
|
|
||||||
|
# Send first byte array
|
||||||
|
ser.write(BYTES_1)
|
||||||
|
print(f"Sent {len(BYTES_1)} bytes (Packet 1).")
|
||||||
|
time.sleep(0.1) # Small delay between writes
|
||||||
|
|
||||||
|
# Send second byte array
|
||||||
|
ser.write(BYTES_2)
|
||||||
|
print(f"Sent {len(BYTES_2)} bytes (Packet 2).")
|
||||||
|
|
||||||
|
print("\nPatch sent successfully!")
|
||||||
|
print("You can now disconnect the controller from your PC.")
|
||||||
|
return True
|
||||||
|
except serial.SerialException as e:
|
||||||
|
print(f"Error: Could not open or write to serial port {com_port}.")
|
||||||
|
print(f"Details: {e}")
|
||||||
|
print("\nPlease ensure the controller is properly connected and no other software is using the COM port.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("--- DJI FCC Patch Script ---")
|
||||||
|
print("Searching for DJI Controller...")
|
||||||
|
|
||||||
|
controller_port = find_dji_controller()
|
||||||
|
|
||||||
|
if controller_port:
|
||||||
|
send_patch(controller_port)
|
||||||
|
else:
|
||||||
|
print("\nDJI Controller not found.")
|
||||||
|
print("Please make sure:")
|
||||||
|
print("1. The controller is turned on and connected to the drone.")
|
||||||
|
print("2. Your PC is connected to the BOTTOM USB port of the controller.")
|
||||||
|
print("3. The necessary USB drivers are installed.")
|
||||||
|
|
||||||
|
print("\nPress Enter to exit.")
|
||||||
|
input()
|
||||||
@ -1,23 +1,23 @@
|
|||||||
# Project-wide Gradle settings.
|
# Project-wide Gradle settings.
|
||||||
# IDE (e.g. Android Studio) users:
|
# IDE (e.g. Android Studio) users:
|
||||||
# Gradle settings configured through the IDE *will override*
|
# Gradle settings configured through the IDE *will override*
|
||||||
# any settings specified in this file.
|
# any settings specified in this file.
|
||||||
# For more details on how to configure your build environment visit
|
# For more details on how to configure your build environment visit
|
||||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
# Specifies the JVM arguments used for the daemon process.
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
# The setting is particularly useful for tweaking memory settings.
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
# When configured, Gradle will run in incubating parallel mode.
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
# This option should only be used with decoupled projects. For more details, visit
|
# This option should only be used with decoupled projects. For more details, visit
|
||||||
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
|
||||||
# org.gradle.parallel=true
|
# org.gradle.parallel=true
|
||||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
# Android operating system, and which are packaged with your app's APK
|
# Android operating system, and which are packaged with your app's APK
|
||||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
# Kotlin code style for this project: "official" or "obsolete":
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
kotlin.code.style=official
|
kotlin.code.style=official
|
||||||
# Enables namespacing of each library's R class so that its R class includes only the
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
@ -1,33 +1,33 @@
|
|||||||
[versions]
|
[versions]
|
||||||
agp = "8.11.0"
|
agp = "8.11.0"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
junitVersion = "1.1.5"
|
junitVersion = "1.1.5"
|
||||||
espressoCore = "3.5.1"
|
espressoCore = "3.5.1"
|
||||||
lifecycleRuntimeKtx = "2.6.1"
|
lifecycleRuntimeKtx = "2.6.1"
|
||||||
activityCompose = "1.8.0"
|
activityCompose = "1.8.0"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
junit = { group = "junit", name = "junit", version.ref = "junit" }
|
||||||
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
|
||||||
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
|
||||||
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
|
||||||
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" }
|
||||||
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" }
|
||||||
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
androidx-ui = { group = "androidx.compose.ui", name = "ui" }
|
||||||
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
androidx-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
|
||||||
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
androidx-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
|
||||||
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
|
||||||
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
|
||||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version = "8.6.0" }
|
||||||
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
|
||||||
|
|
||||||
|
|||||||
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-all.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
5
gradlew
vendored
5
gradlew
vendored
@ -174,7 +174,8 @@ fi
|
|||||||
|
|
||||||
# Escape application args
|
# Escape application args
|
||||||
save () {
|
save () {
|
||||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
for i do printf %s\\n "$i" | sed "s/'/'\\''/g;1s/^/'/;
|
||||||
|
" ; done
|
||||||
echo " "
|
echo " "
|
||||||
}
|
}
|
||||||
APP_ARGS=`save "$@"`
|
APP_ARGS=`save "$@"`
|
||||||
@ -182,4 +183,4 @@ APP_ARGS=`save "$@"`
|
|||||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
exec "$JAVACMD" "$@"
|
exec "$JAVACMD" "$@"
|
||||||
@ -1,24 +1,24 @@
|
|||||||
pluginManagement {
|
pluginManagement {
|
||||||
repositories {
|
repositories {
|
||||||
google {
|
google {
|
||||||
content {
|
content {
|
||||||
includeGroupByRegex("com\\.android.*")
|
includeGroupByRegex("com\\.android.*")
|
||||||
includeGroupByRegex("com\\.google.*")
|
includeGroupByRegex("com\\.google.*")
|
||||||
includeGroupByRegex("androidx.*")
|
includeGroupByRegex("androidx.*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
gradlePluginPortal()
|
gradlePluginPortal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven(url = "https://jitpack.io")
|
maven(url = "https://jitpack.io")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = "dji-ffc-hack"
|
rootProject.name = "dji-ffc-hack"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user