feat(all): Init
15
.gitignore
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
*.iml
|
||||
.gradle
|
||||
/local.properties
|
||||
/.idea/caches
|
||||
/.idea/libraries
|
||||
/.idea/modules.xml
|
||||
/.idea/workspace.xml
|
||||
/.idea/navEditor.xml
|
||||
/.idea/assetWizardSettings.xml
|
||||
.DS_Store
|
||||
/build
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
local.properties
|
||||
3
.idea/.gitignore
generated
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
1
.idea/.name
generated
Normal file
@ -0,0 +1 @@
|
||||
Power
|
||||
6
.idea/compiler.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
||||
23
.idea/deploymentTargetDropDown.xml
generated
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="deploymentTargetDropDown">
|
||||
<value>
|
||||
<entry key="app">
|
||||
<State>
|
||||
<targetSelectedWithDropDown>
|
||||
<Target>
|
||||
<type value="QUICK_BOOT_TARGET" />
|
||||
<deviceKey>
|
||||
<Key>
|
||||
<type value="VIRTUAL_DEVICE_PATH" />
|
||||
<value value="$USER_HOME$/.android/avd/Pixel_7_Android_10.avd" />
|
||||
</Key>
|
||||
</deviceKey>
|
||||
</Target>
|
||||
</targetSelectedWithDropDown>
|
||||
<timeTargetWasSelectedWithDropDown value="2023-09-12T06:59:11.653261Z" />
|
||||
</State>
|
||||
</entry>
|
||||
</value>
|
||||
</component>
|
||||
</project>
|
||||
19
.idea/gradle.xml
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||
<component name="GradleSettings">
|
||||
<option name="linkedExternalProjectsSettings">
|
||||
<GradleProjectSettings>
|
||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||
<option name="modules">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveExternalAnnotations" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/kotlinc.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="KotlinJpsPluginSettings">
|
||||
<option name="version" value="1.6.20" />
|
||||
</component>
|
||||
</project>
|
||||
10
.idea/migrations.xml
generated
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectMigrations">
|
||||
<option name="MigrateToGradleLocalJavaHome">
|
||||
<set>
|
||||
<option value="$PROJECT_DIR$" />
|
||||
</set>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
9
.idea/misc.xml
generated
Normal file
@ -0,0 +1,9 @@
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
1
app/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/build
|
||||
112
app/build.gradle
Normal file
@ -0,0 +1,112 @@
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.2'
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.20'
|
||||
id 'kotlin-kapt'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.power.ops'
|
||||
compileSdk 33
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.power.ops"
|
||||
minSdk 23
|
||||
targetSdk 33
|
||||
versionCode 17
|
||||
versionName "1.8"
|
||||
|
||||
vectorDrawables {
|
||||
useSupportLibrary true
|
||||
}
|
||||
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
buildFeatures {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion '1.2.0-alpha08'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
pickFirst 'lib/arm64-v8a/libc++_shared.so'
|
||||
pickFirst 'lib/armeabi-v7a/libc++_shared.so'
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
doNotStrip "*/*/libconstants.so"
|
||||
doNotStrip "*/*/libdji_innertools.so"
|
||||
doNotStrip "*/*/libdjibase.so"
|
||||
doNotStrip "*/*/libDJICSDKCommon.so"
|
||||
doNotStrip "*/*/libDJIFlySafeCore-CSDK.so"
|
||||
doNotStrip "*/*/libdjifs_jni-CSDK.so"
|
||||
doNotStrip "*/*/libDJIRegister.so"
|
||||
doNotStrip "*/*/libdjisdk_jni.so"
|
||||
doNotStrip "*/*/libDJIUpgradeCore.so"
|
||||
doNotStrip "*/*/libDJIUpgradeJNI.so"
|
||||
doNotStrip "*/*/libDJIWaypointV2Core-CSDK.so"
|
||||
doNotStrip "*/*/libdjiwpv2-CSDK.so"
|
||||
doNotStrip "*/*/libffmpeg.so"
|
||||
doNotStrip "*/*/libFlightRecordEngine.so"
|
||||
doNotStrip "*/*/libvideo-framing.so"
|
||||
doNotStrip "*/*/libwaes.so"
|
||||
doNotStrip "*/*/libagora-rtsa-sdk.so"
|
||||
doNotStrip "*/*/libc++.so"
|
||||
doNotStrip "*/*/libc++_shared.so"
|
||||
doNotStrip "*/*/libmrtc_28181.so"
|
||||
doNotStrip "*/*/libmrtc_agora.so"
|
||||
doNotStrip "*/*/libmrtc_core.so"
|
||||
doNotStrip "*/*/libmrtc_core_jni.so"
|
||||
doNotStrip "*/*/libmrtc_data.so"
|
||||
doNotStrip "*/*/libmrtc_log.so"
|
||||
doNotStrip "*/*/libmrtc_onvif.so"
|
||||
doNotStrip "*/*/libmrtc_rtmp.so"
|
||||
doNotStrip "*/*/libmrtc_rtsp.so"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'com.github.delight-im:Android-AdvancedWebView:v3.2.1'
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.11.0'
|
||||
implementation 'com.google.code.gson:gson:2.10.1'
|
||||
|
||||
implementation "org.mozilla.geckoview:geckoview-nightly:106.0.20220918094414"
|
||||
|
||||
implementation 'com.dji:dji-sdk-v5-aircraft:5.6.0'
|
||||
compileOnly 'com.dji:dji-sdk-v5-aircraft-provided:5.6.0'
|
||||
runtimeOnly 'com.dji:dji-sdk-v5-networkImp:5.6.0'
|
||||
|
||||
implementation 'androidx.navigation:navigation-compose:2.5.3'
|
||||
implementation "androidx.core:core-ktx:1.9.0"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1"
|
||||
implementation "androidx.room:room-runtime:2.5.1"
|
||||
implementation "androidx.room:room-ktx:2.5.1"
|
||||
annotationProcessor "androidx.room:room-compiler:2.5.1"
|
||||
kapt "androidx.room:room-compiler:2.5.1"
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.5.1"
|
||||
|
||||
implementation "androidx.activity:activity-compose:1.2.0-alpha08"
|
||||
implementation "androidx.compose.material:material-icons-extended:1.2.0-alpha08"
|
||||
implementation platform("androidx.compose:compose-bom:2022.10.00")
|
||||
implementation "androidx.compose.ui:ui"
|
||||
implementation "androidx.compose.ui:ui-tooling"
|
||||
implementation "androidx.compose.ui:ui-tooling-preview"
|
||||
implementation "androidx.compose.material3:material3"
|
||||
}
|
||||
137
app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,137 @@
|
||||
-keepattributes Exceptions,InnerClasses,*Annotation*,Signature,EnclosingMethod
|
||||
|
||||
-dontshrink
|
||||
-dontoptimize
|
||||
-dontpreverify
|
||||
-dontnote
|
||||
-ignorewarnings
|
||||
|
||||
-keepclassmembers enum * {
|
||||
public static <methods>;
|
||||
}
|
||||
|
||||
# 避免混淆Annotation、内部类、泛型、匿名类
|
||||
-keepattributes *Annotation*,InnerClasses,Signature,EnclosingMethod
|
||||
|
||||
-keepnames class * implements java.io.Serializable
|
||||
-keepclassmembers class * implements java.io.Serializable {
|
||||
static final long serialVersionUID;
|
||||
private static final java.io.ObjectStreamField[] serialPersistentFields;
|
||||
!static !transient <fields>;
|
||||
private void writeObject(java.io.ObjectOutputStream);
|
||||
private void readObject(java.io.ObjectInputStream);
|
||||
java.lang.Object writeReplace();
|
||||
java.lang.Object readResolve();
|
||||
}
|
||||
-keep class * extends android.os.Parcelable {
|
||||
public static final android.os.Parcelable$Creator *;
|
||||
}
|
||||
|
||||
-keep,allowshrinking class * extends dji.publics.DJIUI.** {
|
||||
public <methods>;
|
||||
}
|
||||
#加固后的AAR,其内容无法被混淆工具识别,所以MSDK外部依赖的类,必须被Keep。
|
||||
-keep class net.sqlcipher.** { *; }
|
||||
-keep class net.sqlcipher.database.* { *; }
|
||||
-keep class dji.** { *; }
|
||||
-keep class com.dji.** { *; }
|
||||
-keep class djimrtc.** { *; }
|
||||
-keep class com.google.** { *; }
|
||||
-keep class org.bouncycastle.** { *; }
|
||||
-keep class org.** { *; }
|
||||
-keep class com.squareup.wire.** { *; }
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-keep class com.secneo.** {*;}
|
||||
-keep class io.reactivex.**{*;}
|
||||
-keep class okhttp3.**{*;}
|
||||
-keep class okio.**{*;}
|
||||
-keep class org.bouncycastle.**{*;}
|
||||
-keep class sun.**{*;}
|
||||
-keep class java.**{*;}
|
||||
-keep class com.amap.api.**{*;}
|
||||
-keep class com.here.**{*;}
|
||||
-keep class com.mapbox.**{*;}
|
||||
-keep class retrofit2.**{*;}
|
||||
|
||||
-keepclasseswithmembers,allowshrinking class * {
|
||||
native <methods>;
|
||||
}
|
||||
|
||||
-keep class * implements com.google.gson.TypeAdapterFactory
|
||||
-keep class * implements com.google.gson.JsonSerializer
|
||||
-keep class * implements com.google.gson.JsonDeserializer
|
||||
|
||||
-keep class androidx.appcompat.widget.SearchView { *; }
|
||||
|
||||
-keepclassmembers class * extends android.app.Service
|
||||
-keepclassmembers public class * extends android.view.View {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
-keepclassmembers class * extends android.app.Activity {
|
||||
public void *(android.view.View);
|
||||
}
|
||||
-keep class kotlin.** { *; }
|
||||
-keep class androidx.** { *; }
|
||||
-keep class android.** { *; }
|
||||
-keep class com.android.** { *; }
|
||||
-keep class android.media.** { *; }
|
||||
-keep class okio.** { *; }
|
||||
-keep class com.lmax.disruptor.** {
|
||||
*;
|
||||
}
|
||||
|
||||
-dontwarn com.mapbox.services.android.location.LostLocationEngine
|
||||
-dontwarn com.mapbox.services.android.location.MockLocationEngine
|
||||
-keepclassmembers class * implements android.arch.lifecycle.LifecycleObserver {
|
||||
<init>(...);
|
||||
}
|
||||
# ViewModel's empty constructor is considered to be unused by proguard
|
||||
-keepclassmembers class * extends android.arch.lifecycle.ViewModel {
|
||||
<init>(...);
|
||||
}
|
||||
# keep Lifecycle State and Event enums values
|
||||
-keepclassmembers class android.arch.lifecycle.Lifecycle$State { *; }
|
||||
-keepclassmembers class android.arch.lifecycle.Lifecycle$Event { *; }
|
||||
# keep methods annotated with @OnLifecycleEvent even if they seem to be unused
|
||||
# (Mostly for LiveData.LifecycleBoundObserver.onStateChange(), but who knows)
|
||||
-keepclassmembers class * {
|
||||
@android.arch.lifecycle.OnLifecycleEvent *;
|
||||
}
|
||||
|
||||
-keepclassmembers class * implements android.arch.lifecycle.LifecycleObserver {
|
||||
<init>(...);
|
||||
}
|
||||
|
||||
-keep class * implements android.arch.lifecycle.LifecycleObserver {
|
||||
<init>(...);
|
||||
}
|
||||
-keepclassmembers class android.arch.** { *; }
|
||||
-keep class android.arch.** { *; }
|
||||
-dontwarn android.arch.**
|
||||
|
||||
-dontwarn dalvik.**
|
||||
-dontwarn com.tencent.smtt.**
|
||||
-keep class com.tencent.smtt.** {
|
||||
*;
|
||||
}
|
||||
-keep class com.tencent.tbs.** {
|
||||
*;
|
||||
}
|
||||
#-keep class * extends android.webkit.WebChromeClient { *; }
|
||||
#-dontwarn im.delight.android.webview.**
|
||||
|
||||
#<------------ utmiss config start------------>
|
||||
-keep class dji.sdk.utmiss.** { *; }
|
||||
-keep class utmisslib.** { *; }
|
||||
#<------------ utmiss config end------------>
|
||||
|
||||
# 地图相关
|
||||
-keep class com.dji.mapkit.amap.provider.AMapProvider {*;}
|
||||
-keep class com.dji.mapkit.maplibre.provider.MapLibreProvider {*;}
|
||||
-keep class com.dji.mapkit.core.** {*;}
|
||||
-keep class com.autonavi.** {*;}
|
||||
|
||||
|
||||
## keep 千寻相关接口
|
||||
-keep class com.qx.wz.dj.rtcm.** {*;}
|
||||
61
app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.host"
|
||||
android:required="false"/>
|
||||
<uses-feature
|
||||
android:name="android.hardware.usb.accessory"
|
||||
android:required="true"/>
|
||||
|
||||
<application
|
||||
android:name="com.power.ops.MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
android:fullBackupContent="@xml/backup_rules"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true"
|
||||
tools:replace="android:usesCleartextTraffic"
|
||||
android:label="新能源智能"
|
||||
android:hardwareAccelerated="true"
|
||||
android:supportsRtl="true"
|
||||
android:requestLegacyExternalStorage="true"
|
||||
android:theme="@style/Theme.Power"
|
||||
tools:targetApi="33">
|
||||
|
||||
<meta-data
|
||||
android:name="com.dji.sdk.API_KEY"
|
||||
android:value="068356817738bbcc900bde2f"/>
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.Power">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<action android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.hardware.usb.action.USB_ACCESSORY_ATTACHED"
|
||||
android:resource="@xml/accessory_filter" />
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
BIN
app/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
75
app/src/main/java/com/power/ops/MainActivity.kt
Normal file
@ -0,0 +1,75 @@
|
||||
package com.power.ops
|
||||
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.media.projection.MediaProjectionManager
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.power.ops.managers.DjiManager
|
||||
import com.power.ops.managers.HttpManager
|
||||
import com.power.ops.managers.LogManager
|
||||
import com.power.ops.pages.AircraftPage
|
||||
import com.power.ops.pages.AircraftSettingPage
|
||||
import com.power.ops.pages.HomePage
|
||||
import com.power.ops.pages.LoginPage
|
||||
import com.power.ops.pages.MessagePage
|
||||
import com.power.ops.pages.SettingPage
|
||||
import com.power.ops.pages.SitePage
|
||||
import com.power.ops.pages.SubmitPage
|
||||
import com.power.ops.theme.PowerTheme
|
||||
import com.power.ops.utils.redirectTo
|
||||
import com.power.ops.vms.SettingVM
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContent {
|
||||
PowerTheme {
|
||||
Surface( modifier = Modifier.fillMaxSize() ) {
|
||||
Nav()
|
||||
}
|
||||
}
|
||||
}
|
||||
DjiManager.registerApp(this)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Nav() {
|
||||
val navController = rememberNavController()
|
||||
|
||||
SettingVM.singleton.initDao(LocalContext.current)
|
||||
|
||||
// LaunchedEffect(navController) {
|
||||
// val token = SettingVM.singleton.getSetting("token")
|
||||
// if (token == null || token.value == "") {
|
||||
// redirectTo(navController, "LoginPage")
|
||||
// } else {
|
||||
// HttpManager.singleton.token = token.value
|
||||
// }
|
||||
// }
|
||||
|
||||
NavHost(navController = navController, startDestination = "SitePage") {
|
||||
composable("HomePage") { HomePage(navController) }
|
||||
composable("AircraftPage") { AircraftPage(navController) }
|
||||
composable("AircraftSettingPage") { AircraftSettingPage(navController) }
|
||||
composable("MessagePage") { MessagePage(navController) }
|
||||
composable("SitePage") { SitePage(navController) }
|
||||
composable("SubmitPage") { SubmitPage(navController) }
|
||||
composable("SettingPage") { SettingPage(navController) }
|
||||
composable("LoginPage") { LoginPage(navController) }
|
||||
}
|
||||
}
|
||||
13
app/src/main/java/com/power/ops/MainApplication.kt
Normal file
@ -0,0 +1,13 @@
|
||||
package com.power.ops
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import com.secneo.sdk.Helper
|
||||
import com.power.ops.vms.SettingVM
|
||||
|
||||
class MainApplication : Application() {
|
||||
override fun attachBaseContext(base: Context?) {
|
||||
super.attachBaseContext(base)
|
||||
Helper.install(this)
|
||||
}
|
||||
}
|
||||
68
app/src/main/java/com/power/ops/components/BottomBar.kt
Normal file
@ -0,0 +1,68 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.CrisisAlert
|
||||
import androidx.compose.material.icons.rounded.FormatListBulleted
|
||||
import androidx.compose.material.icons.rounded.Home
|
||||
import androidx.compose.material.icons.rounded.Message
|
||||
import androidx.compose.material.icons.rounded.Settings
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.NavigationBar
|
||||
import androidx.compose.material3.NavigationBarItem
|
||||
import androidx.compose.material3.NavigationBarItemDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.theme.Main500
|
||||
import com.power.ops.theme.PowerTheme
|
||||
import com.power.ops.theme.Secondary400
|
||||
import com.power.ops.theme.Typography
|
||||
import com.power.ops.utils.redirectTo
|
||||
|
||||
data class BottomBarItem(
|
||||
val icon: ImageVector,
|
||||
val label: String,
|
||||
val value: String
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun BottomBar(navController: NavController, selected: String) {
|
||||
val items = listOf<BottomBarItem>(
|
||||
BottomBarItem(Icons.Rounded.Message, "消息", "MessagePage"),
|
||||
BottomBarItem(Icons.Rounded.FormatListBulleted, "工单","SubmitPage"),
|
||||
BottomBarItem(Icons.Rounded.Home, "首页", "HomePage"),
|
||||
BottomBarItem(Icons.Rounded.CrisisAlert, "全景", "SitePage"),
|
||||
BottomBarItem(Icons.Rounded.Settings, "设置", "SettingPage")
|
||||
)
|
||||
|
||||
// NavigationBar(
|
||||
// containerColor = Main500
|
||||
// ) {
|
||||
// items.forEach {
|
||||
// NavigationBarItem(
|
||||
// selected = selected == it.value,
|
||||
// onClick = {
|
||||
// redirectTo(navController, it.value)
|
||||
// },
|
||||
// icon = {
|
||||
// Icon(it.icon, contentDescription = null)
|
||||
// },
|
||||
// alwaysShowLabel = selected == it.value,
|
||||
// label = { Text(it.label, fontWeight = FontWeight.Bold, style = Typography.labelSmall) },
|
||||
// colors = NavigationBarItemDefaults.colors(indicatorColor = Secondary400)
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewBottomBar() {
|
||||
PowerTheme {
|
||||
BottomBar(NavController(LocalContext.current), "HomePage")
|
||||
}
|
||||
}
|
||||
70
app/src/main/java/com/power/ops/components/Dialog.kt
Normal file
@ -0,0 +1,70 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.power.ops.theme.Char600
|
||||
import com.power.ops.theme.Primary500
|
||||
import com.power.ops.theme.Secondary400
|
||||
import com.power.ops.theme.Typography
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Dialog(
|
||||
showDialog: Boolean,
|
||||
title: String = "请输入",
|
||||
defaultValue: String = "",
|
||||
onDismiss: () -> Unit,
|
||||
onConfirm: (String) -> Unit
|
||||
) {
|
||||
var inputValue by remember { mutableStateOf(defaultValue) }
|
||||
|
||||
if (showDialog) {
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismiss,
|
||||
title = {
|
||||
Text(text = title, modifier = Modifier.fillMaxWidth(), textAlign = TextAlign.Center, style = Typography.titleLarge) },
|
||||
text = {
|
||||
Column {
|
||||
OutlinedTextField(
|
||||
value = inputValue,
|
||||
onValueChange = { inputValue = it },
|
||||
modifier = Modifier
|
||||
.padding(top = 14.dp),
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(unfocusedBorderColor = Char600, focusedBorderColor = Primary500),
|
||||
keyboardActions = KeyboardActions(onDone = { onConfirm(inputValue) }),
|
||||
)
|
||||
}
|
||||
},
|
||||
confirmButton = {
|
||||
TextButton(onClick = {
|
||||
onConfirm(inputValue)
|
||||
onDismiss()
|
||||
}) {
|
||||
Text(text = "确认")
|
||||
}
|
||||
},
|
||||
dismissButton = {
|
||||
TextButton(onClick = onDismiss) {
|
||||
Text(text = "取消")
|
||||
}
|
||||
},
|
||||
containerColor = Secondary400
|
||||
)
|
||||
}
|
||||
}
|
||||
16
app/src/main/java/com/power/ops/components/Divider.kt
Normal file
@ -0,0 +1,16 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.power.ops.theme.Char500
|
||||
|
||||
@Composable
|
||||
fun Divider(color: Color = Char500.copy(0.05f)) {
|
||||
Row( modifier = Modifier.fillMaxWidth().height(0.5.dp).background(color) ) {}
|
||||
}
|
||||
36
app/src/main/java/com/power/ops/components/Dropdown.kt
Normal file
@ -0,0 +1,36 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import com.power.ops.theme.Secondary400
|
||||
|
||||
data class DropdownItem(val name: String, val action: () -> Unit)
|
||||
|
||||
@Composable
|
||||
fun Dropdown(imageVector: ImageVector, items: Array<DropdownItem>) {
|
||||
var isShowMenu by remember { mutableStateOf(false) }
|
||||
Box {
|
||||
IconButton(onClick = { isShowMenu = true }) { Icon(imageVector, contentDescription = null) }
|
||||
DropdownMenu(
|
||||
expanded = isShowMenu,
|
||||
onDismissRequest = { isShowMenu = false },
|
||||
modifier = Modifier.background(Secondary400)
|
||||
) {
|
||||
items.forEach {
|
||||
DropdownMenuItem(text = { Text(it.name) }, onClick = it.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
39
app/src/main/java/com/power/ops/components/GeckoWebview.kt
Normal file
@ -0,0 +1,39 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import com.power.ops.managers.GeckoManager
|
||||
import org.mozilla.geckoview.GeckoSession.ContentDelegate.ContextElement.*
|
||||
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonPrompt.Type.NEGATIVE
|
||||
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ButtonPrompt.Type.POSITIVE
|
||||
import org.mozilla.geckoview.GeckoSession.PromptDelegate.ChoicePrompt.Type.MULTIPLE
|
||||
import org.mozilla.geckoview.GeckoView
|
||||
import org.mozilla.geckoview.GeckoRuntime
|
||||
import org.mozilla.geckoview.WebResponse
|
||||
|
||||
@Composable
|
||||
fun GeckoWebview(modifier: Modifier = Modifier) {
|
||||
AndroidView(
|
||||
modifier = Modifier.fillMaxWidth(), // Occupy the max size in the Compose UI tree
|
||||
factory = { context ->
|
||||
// Creates custom view
|
||||
GeckoView(context).apply {
|
||||
GeckoManager.singleton.runtime = GeckoRuntime.create(context)
|
||||
GeckoManager.singleton.session.open(GeckoManager.singleton.runtime)
|
||||
this.releaseSession()
|
||||
this.setSession(GeckoManager.singleton.session)
|
||||
|
||||
GeckoManager.singleton.session.loadUri("http://nofee.fun")
|
||||
}
|
||||
},
|
||||
update = { view ->
|
||||
// View's been inflated or state read in this block has been updated
|
||||
// Add logic here if necessary
|
||||
view.releaseSession()
|
||||
view.setSession(GeckoManager.singleton.session)
|
||||
}
|
||||
)
|
||||
}
|
||||
123
app/src/main/java/com/power/ops/components/Header.kt
Normal file
@ -0,0 +1,123 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowLeft
|
||||
import androidx.compose.material.icons.rounded.CheckCircle
|
||||
import androidx.compose.material.icons.rounded.Dashboard
|
||||
import androidx.compose.material3.DropdownMenu
|
||||
import androidx.compose.material3.DropdownMenuItem
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.R
|
||||
import com.power.ops.theme.Char400
|
||||
import com.power.ops.theme.Main500
|
||||
import com.power.ops.theme.Secondary400
|
||||
import com.power.ops.theme.Typography
|
||||
import com.power.ops.utils.redirectTo
|
||||
|
||||
enum class HeaderType {
|
||||
CONFIRM, MAIN, NORMAL
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Header(
|
||||
navController: NavController,
|
||||
headerType: HeaderType,
|
||||
title: String,
|
||||
height: Dp = 54.dp,
|
||||
fontSize: TextUnit = 20.sp,
|
||||
action: () -> Unit = {}
|
||||
) {
|
||||
var isShowMenu by remember { mutableStateOf(false) }
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(height)
|
||||
.background(Main500)
|
||||
.zIndex(1f),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(15.dp))
|
||||
if (headerType == HeaderType.CONFIRM) {
|
||||
IconButton(onClick = {
|
||||
navController.popBackStack()
|
||||
}, modifier = Modifier.width(35.dp)) {
|
||||
Icon(Icons.Filled.KeyboardArrowLeft, contentDescription = null)
|
||||
}
|
||||
} else if (headerType == HeaderType.MAIN) {
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.i3060),
|
||||
contentDescription = null,
|
||||
modifier = Modifier
|
||||
.size(25.dp)
|
||||
.clip(CircleShape),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(15.dp))
|
||||
Text(title, style = TextStyle(
|
||||
color = Char400,
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = fontSize
|
||||
))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
if (headerType == HeaderType.CONFIRM) {
|
||||
IconButton(onClick = action, modifier = Modifier.width(35.dp)) {
|
||||
Icon(Icons.Rounded.CheckCircle, contentDescription = null)
|
||||
}
|
||||
} else if (headerType == HeaderType.MAIN) {
|
||||
Box {
|
||||
IconButton(onClick = { isShowMenu = true }) { Icon(Icons.Rounded.Dashboard, contentDescription = null) }
|
||||
DropdownMenu(
|
||||
expanded = isShowMenu,
|
||||
onDismissRequest = { isShowMenu = false },
|
||||
modifier = Modifier.background(Secondary400)
|
||||
) {
|
||||
listOf("设置").forEach { item ->
|
||||
DropdownMenuItem(text = { Text(item) }, onClick = {
|
||||
isShowMenu = false
|
||||
if (item == "设置") {
|
||||
navController.navigate("SettingPage")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(15.dp))
|
||||
}
|
||||
}
|
||||
20
app/src/main/java/com/power/ops/components/KVText.kt
Normal file
@ -0,0 +1,20 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.power.ops.theme.Char600
|
||||
import com.power.ops.theme.Char700
|
||||
|
||||
|
||||
@Composable
|
||||
fun KVText(key: String, value: String) {
|
||||
Row(modifier = Modifier.padding(vertical = 3.dp)) {
|
||||
Text(key, color = Char700, fontSize = 10.sp, modifier = Modifier.padding(horizontal = 3.dp))
|
||||
Text(value, color = Char600, fontSize = 10.sp, modifier = Modifier.padding(horizontal = 3.dp), lineHeight = 12.sp)
|
||||
}
|
||||
}
|
||||
57
app/src/main/java/com/power/ops/components/RichItem.kt
Normal file
@ -0,0 +1,57 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.rounded.Route
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.power.ops.theme.Char700
|
||||
import com.power.ops.theme.PowerTheme
|
||||
|
||||
@Composable
|
||||
fun RichItem(
|
||||
imageVector: ImageVector,
|
||||
text: String,
|
||||
intro: String? = null,
|
||||
action: () -> Unit
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(50.dp)
|
||||
.clickable(onClick = action),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Icon(imageVector, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
Text(text)
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
if (intro != null) {
|
||||
Text(intro, color = Char700)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
}
|
||||
Icon(Icons.Filled.KeyboardArrowRight, contentDescription = null)
|
||||
Spacer(modifier = Modifier.width(10.dp))
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewRichItem() {
|
||||
PowerTheme {
|
||||
RichItem(Icons.Rounded.Route, "头部", "20.3", action = {})
|
||||
}
|
||||
}
|
||||
49
app/src/main/java/com/power/ops/components/Tip.kt
Normal file
@ -0,0 +1,49 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Close
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.IconButtonDefaults
|
||||
import androidx.compose.material3.Snackbar
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.power.ops.theme.Char500
|
||||
import com.power.ops.theme.Main500
|
||||
import com.power.ops.theme.PowerTheme
|
||||
|
||||
@Composable
|
||||
fun Tip(
|
||||
showTip: Boolean,
|
||||
text: String,
|
||||
action: () -> Unit
|
||||
) {
|
||||
if (showTip) {
|
||||
Snackbar(
|
||||
modifier = Modifier.padding(14.dp),
|
||||
containerColor = Main500,
|
||||
dismissAction = {
|
||||
IconButton(
|
||||
onClick = action,
|
||||
colors = IconButtonDefaults.iconButtonColors(contentColor = Char500)
|
||||
) {
|
||||
Icon(Icons.Rounded.Close, contentDescription = null)
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewTip() {
|
||||
PowerTheme {
|
||||
Tip(true, "测试", {})
|
||||
}
|
||||
}
|
||||
45
app/src/main/java/com/power/ops/components/Webview.kt
Normal file
@ -0,0 +1,45 @@
|
||||
package com.power.ops.components
|
||||
|
||||
import android.view.ViewGroup
|
||||
import android.webkit.WebSettings
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import com.power.ops.managers.HttpManager
|
||||
import com.power.ops.theme.Char400
|
||||
import com.power.ops.utils.ip
|
||||
import im.delight.android.webview.AdvancedWebView
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun Webview(uri: String) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
AndroidView(
|
||||
factory = { context ->
|
||||
AdvancedWebView(context).apply {
|
||||
this.clearCache(true)
|
||||
|
||||
layoutParams = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
|
||||
settings.javaScriptEnabled = true
|
||||
settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
|
||||
settings.loadsImagesAutomatically = true
|
||||
settings.allowContentAccess = true
|
||||
settings.domStorageEnabled = true
|
||||
settings.allowFileAccess = true
|
||||
setCookiesEnabled(true)
|
||||
setMixedContentAllowed(true)
|
||||
settings.useWideViewPort = true
|
||||
settings.loadWithOverviewMode = true
|
||||
}
|
||||
},
|
||||
modifier = Modifier.fillMaxHeight().background(Char400)
|
||||
) { webview ->
|
||||
webview.loadUrl("https://$ip$uri?token=${HttpManager.singleton.token}")
|
||||
}
|
||||
}
|
||||
595
app/src/main/java/com/power/ops/managers/DjiManager.kt
Normal file
@ -0,0 +1,595 @@
|
||||
package com.power.ops.managers
|
||||
|
||||
import android.content.Context
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import com.dji.wpmzsdk.common.data.Template
|
||||
import com.dji.wpmzsdk.manager.WPMZManager
|
||||
import com.google.gson.Gson
|
||||
import com.power.ops.utils.bitmapToBase64
|
||||
import com.power.ops.utils.calculateMD5
|
||||
import com.power.ops.vms.SettingVM
|
||||
import dji.sdk.keyvalue.key.AirLinkKey
|
||||
import dji.sdk.keyvalue.key.BatteryKey
|
||||
import dji.sdk.keyvalue.key.CameraKey
|
||||
import dji.sdk.keyvalue.key.FlightControllerKey
|
||||
import dji.sdk.keyvalue.key.GimbalKey
|
||||
import dji.sdk.keyvalue.key.KeyTools
|
||||
import dji.sdk.keyvalue.value.camera.CameraMode
|
||||
import dji.sdk.keyvalue.value.camera.CameraVideoStreamSourceType
|
||||
import dji.sdk.keyvalue.value.camera.PhotoFileFormat
|
||||
import dji.sdk.keyvalue.value.camera.PhotoSize
|
||||
import dji.sdk.keyvalue.value.common.LocationCoordinate2D
|
||||
import dji.sdk.keyvalue.value.common.LocationCoordinate3D
|
||||
import dji.sdk.keyvalue.value.gimbal.GimbalSpeedRotation
|
||||
|
||||
// import dji.sdk.keyvalue.value.mission.WaypointMission
|
||||
|
||||
import dji.sdk.wpmz.value.mission.ActionGimbalRotateParam;
|
||||
import dji.sdk.wpmz.value.mission.ActionStartRecordParam;
|
||||
import dji.sdk.wpmz.value.mission.ActionStopRecordParam;
|
||||
import dji.sdk.wpmz.value.mission.ActionTakePhotoParam;
|
||||
import dji.sdk.wpmz.value.mission.CameraLensType;
|
||||
import dji.sdk.wpmz.value.mission.Wayline;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionGroup;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionInfo;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionNodeList;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionTreeNode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionTrigger;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionTriggerType;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionType;
|
||||
import dji.sdk.wpmz.value.mission.WaylineActionsRelationType;
|
||||
import dji.sdk.wpmz.value.mission.WaylineAltitudeMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineCoordinateMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineCoordinateParam;
|
||||
import dji.sdk.wpmz.value.mission.WaylineDroneInfo;
|
||||
import dji.sdk.wpmz.value.mission.WaylineExecuteAltitudeMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineExecuteWaypoint;
|
||||
import dji.sdk.wpmz.value.mission.WaylineExitOnRCLostAction;
|
||||
import dji.sdk.wpmz.value.mission.WaylineExitOnRCLostBehavior;
|
||||
import dji.sdk.wpmz.value.mission.WaylineFinishedAction;
|
||||
import dji.sdk.wpmz.value.mission.WaylineFlyToWaylineMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineGimbalActuatorRotateMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineLocationCoordinate2D;
|
||||
import dji.sdk.wpmz.value.mission.WaylineMission;
|
||||
import dji.sdk.wpmz.value.mission.WaylineMissionConfig;
|
||||
import dji.sdk.wpmz.value.mission.WaylinePayloadInfo;
|
||||
import dji.sdk.wpmz.value.mission.WaylinePositioningType;
|
||||
import dji.sdk.wpmz.value.mission.WaylineTemplateWaypointInfo;
|
||||
import dji.sdk.wpmz.value.mission.WaylineWaypoint;
|
||||
import dji.sdk.wpmz.value.mission.WaylineWaypointPitchMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineWaypointTurnParam;
|
||||
import dji.sdk.wpmz.value.mission.WaylineWaypointYawMode;
|
||||
import dji.sdk.wpmz.value.mission.WaylineWaypointYawParam;
|
||||
|
||||
|
||||
import dji.v5.common.callback.CommonCallbacks
|
||||
import dji.v5.common.callback.CommonCallbacks.CompletionCallback
|
||||
import dji.v5.common.error.IDJIError
|
||||
import dji.v5.common.register.DJISDKInitEvent
|
||||
import dji.v5.common.video.channel.VideoChannelState
|
||||
import dji.v5.common.video.channel.VideoChannelType
|
||||
import dji.v5.common.video.decoder.DecoderOutputMode
|
||||
import dji.v5.common.video.decoder.VideoDecoder
|
||||
import dji.v5.et.action
|
||||
import dji.v5.et.get
|
||||
import dji.v5.et.listen
|
||||
import dji.v5.et.set
|
||||
import dji.v5.manager.SDKManager
|
||||
import dji.v5.manager.aircraft.virtualstick.VirtualStickManager
|
||||
import dji.v5.manager.aircraft.waypoint3.WaypointMissionManager
|
||||
import dji.v5.manager.datacenter.MediaDataCenter
|
||||
import dji.v5.manager.datacenter.livestream.LiveStreamSettings
|
||||
import dji.v5.manager.datacenter.livestream.LiveStreamType
|
||||
import dji.v5.manager.datacenter.livestream.settings.AgoraSettings
|
||||
import dji.v5.manager.datacenter.livestream.settings.RtmpSettings
|
||||
import dji.v5.manager.datacenter.media.MediaFile
|
||||
import dji.v5.manager.datacenter.media.MediaFileDownloadListener
|
||||
import dji.v5.manager.datacenter.media.MediaFileListState
|
||||
import dji.v5.manager.datacenter.media.PullMediaFileListParam
|
||||
import dji.v5.manager.datacenter.video.VideoStreamManager
|
||||
import dji.v5.manager.interfaces.SDKManagerCallback
|
||||
import dji.sdk.wpmz.value.mission.*
|
||||
import dji.v5.utils.common.FileUtils
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.BufferedOutputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
|
||||
data class KMZPoint(val lon: Double, val lat: Double)
|
||||
data class KMZPoints(val points: ArrayList<KMZPoint>)
|
||||
data class FileListItem(val name: String, val createdTime: String)
|
||||
|
||||
class DjiManager {
|
||||
val videoStreamManager = VideoStreamManager.getInstance()
|
||||
val mediaManager = MediaDataCenter.getInstance().mediaManager
|
||||
val liveStreamManager = MediaDataCenter.getInstance().liveStreamManager
|
||||
val wpmzManager = WPMZManager.getInstance()
|
||||
|
||||
var airlineUrl = ""
|
||||
var airlineFile: File? = null
|
||||
|
||||
var fileList: MutableList<MediaFile> = mutableListOf()
|
||||
|
||||
var videoCtx: Context? = null
|
||||
var videoView: SurfaceView? = null
|
||||
var videoHolder: SurfaceHolder? = null
|
||||
|
||||
fun getChannelStatus(): String {
|
||||
val videoChannel = videoStreamManager.getAvailableVideoChannel(VideoChannelType.PRIMARY_STREAM_CHANNEL)
|
||||
if (videoChannel != null ) {
|
||||
return videoChannel.videoChannelStatus.toString()
|
||||
} else {
|
||||
return "无法初始化"
|
||||
}
|
||||
}
|
||||
|
||||
fun loadVideoSurface(ctx: Context, holder: SurfaceHolder, view: SurfaceView) {
|
||||
videoCtx = ctx
|
||||
videoHolder = holder
|
||||
videoView = view
|
||||
val videoChannel = videoStreamManager.getAvailableVideoChannel(VideoChannelType.PRIMARY_STREAM_CHANNEL)
|
||||
if (videoChannel != null && videoChannel.videoChannelStatus == VideoChannelState.ON) {
|
||||
VideoDecoder(
|
||||
ctx, VideoChannelType.PRIMARY_STREAM_CHANNEL, DecoderOutputMode.SURFACE_MODE,
|
||||
holder, -1, -1, true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setRtmp(url: String) {
|
||||
val rtmpSettings = RtmpSettings.Builder()
|
||||
.setUrl(url)
|
||||
.build()
|
||||
val liveStreamSettings = LiveStreamSettings.Builder()
|
||||
.setLiveStreamType(LiveStreamType.RTMP)
|
||||
.setRtmpSettings(rtmpSettings)
|
||||
.build()
|
||||
liveStreamManager.liveStreamSettings = liveStreamSettings
|
||||
}
|
||||
|
||||
fun setAgora(channelId: String, token: String, uid: String) {
|
||||
val agoraConfig = AgoraSettings.Builder()
|
||||
.setChannelId(channelId)
|
||||
.setToken(token)
|
||||
.setUid(uid)
|
||||
.setEnableSafety(false)
|
||||
.build()
|
||||
val liveStreamSettings = LiveStreamSettings.Builder()
|
||||
.setLiveStreamType(LiveStreamType.AGORA)
|
||||
.setAgoraSettings(agoraConfig)
|
||||
.build()
|
||||
liveStreamManager.liveStreamSettings = liveStreamSettings
|
||||
}
|
||||
|
||||
fun startLiveStream(onSuccess: () -> Unit, onFailure: (error: String) -> Unit) {
|
||||
liveStreamManager.startStream(object : CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { onSuccess() }
|
||||
override fun onFailure(error: IDJIError) { onFailure(error.toString()) }
|
||||
})
|
||||
}
|
||||
|
||||
fun enableStick() {
|
||||
VirtualStickManager.getInstance().enableVirtualStick(object: CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Enable success") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Enable stick fail: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun disableStick() {
|
||||
VirtualStickManager.getInstance().disableVirtualStick(object: CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Disable success") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Disable stick fail: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun changeRightStick(x: Int?, y: Int?) {
|
||||
val stick = VirtualStickManager.getInstance().rightStick
|
||||
x?.let { stick.horizontalPosition = x }
|
||||
y?.let { stick.verticalPosition = y }
|
||||
}
|
||||
|
||||
fun changeLeftStick(x: Int?, y: Int?) {
|
||||
val stick = VirtualStickManager.getInstance().leftStick
|
||||
x?.let { stick.horizontalPosition = x }
|
||||
y?.let { stick.verticalPosition = y }
|
||||
}
|
||||
|
||||
fun uploadKMZ() {
|
||||
airlineFile?.let {
|
||||
WaypointMissionManager.getInstance().pushKMZFileToAircraft(it.path, object : CommonCallbacks.CompletionCallbackWithProgress<Double> {
|
||||
override fun onProgressUpdate(progress: Double?) { LogManager.i("Upload KMZ progress: $progress") }
|
||||
override fun onSuccess() { LogManager.i("Upload KMZ success") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Upload KMZ error: $error") }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun startMission() {
|
||||
airlineFile?.let {
|
||||
val name = FileUtils.getFileName(it.path, ".kmz")
|
||||
val ids = WaypointMissionManager.getInstance().getAvailableWaylineIDs(it.path)
|
||||
WaypointMissionManager.getInstance().startMission(name, ids, object : CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Success start mission") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Fail start mission: $error") }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun pauseMission() {
|
||||
WaypointMissionManager.getInstance().pauseMission(object : CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Success pause mission") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Pause mission: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun resumeMission() {
|
||||
WaypointMissionManager.getInstance().resumeMission(object : CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Success resume mission") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Resume mission: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun stopMission() {
|
||||
airlineFile?.let {
|
||||
val name = FileUtils.getFileName(it.path, ".kmz")
|
||||
WaypointMissionManager.getInstance().stopMission(name, object : CommonCallbacks.CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Success stop mission") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Stop mission: $error") }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun addMissionStateListener() {
|
||||
WaypointMissionManager.getInstance().addWaypointMissionExecuteStateListener {
|
||||
WebsocketManager.singleton.sendPayload("KeyWaypointMissionExecuteState", it.name )
|
||||
}
|
||||
}
|
||||
|
||||
fun changeGimbalRotate(value: Double) {
|
||||
val rotation = GimbalSpeedRotation()
|
||||
rotation.pitch = value
|
||||
rotation.yaw = 0.0
|
||||
rotation.roll = 0.0
|
||||
KeyTools.createKey(GimbalKey.KeyRotateBySpeed).action(rotation, onSuccess = {
|
||||
LogManager.i(it.toString())
|
||||
})
|
||||
}
|
||||
|
||||
fun changeCameraType(type: CameraVideoStreamSourceType) {
|
||||
KeyTools.createKey(CameraKey.KeyCameraVideoStreamSource).set(type)
|
||||
}
|
||||
|
||||
fun changeCameraZoom(ratio: Double) {
|
||||
KeyTools.createKey(CameraKey.KeyCameraZoomRatios).set(ratio)
|
||||
}
|
||||
|
||||
fun takePhoto() {
|
||||
KeyTools.createKey(CameraKey.KeyCameraMode).set(CameraMode.PHOTO_NORMAL)
|
||||
KeyTools.createKey(CameraKey.KeyStartShootPhoto).action()
|
||||
}
|
||||
|
||||
fun initPhotoFormat() {
|
||||
KeyTools.createKey(CameraKey.KeyPhotoFileFormat).set(PhotoFileFormat.JPEG)
|
||||
KeyTools.createKey(CameraKey.KeyPhotoSize).set(PhotoSize.SIZE_DEFAULT)
|
||||
}
|
||||
|
||||
fun updateFileList() {
|
||||
val mediaFileListState = mediaManager.mediaFileListState
|
||||
if (mediaFileListState == MediaFileListState.UP_TO_DATE) {
|
||||
fileList = mediaManager.mediaFileListData.data
|
||||
LogManager.i("Get FileList size ${fileList.size}")
|
||||
val newList: List<FileListItem> = fileList.map {
|
||||
FileListItem(it.fileName, it.date.toString())
|
||||
}
|
||||
WebsocketManager.singleton.sendPayload("FileList", Gson().toJson(newList))
|
||||
} else if (mediaFileListState == MediaFileListState.IDLE) {
|
||||
mediaManager.pullMediaFileListFromCamera(PullMediaFileListParam.Builder().build(), object : CompletionCallback {
|
||||
override fun onSuccess() {
|
||||
fileList = mediaManager.mediaFileListData.data
|
||||
LogManager.i("Pull FileList size ${fileList.size}")
|
||||
val newList: List<FileListItem> = fileList.map {
|
||||
FileListItem(it.fileName, it.date.toString())
|
||||
}
|
||||
WebsocketManager.singleton.sendPayload("FileList", Gson().toJson(newList))
|
||||
}
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Pull media error: $error") }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun enableMedia() {
|
||||
mediaManager.enable(object : CompletionCallback {
|
||||
override fun onSuccess() { updateFileList() }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Enable media fail: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun disableMedia() {
|
||||
mediaManager.disable(object : CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Disable media success") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Disable media fail: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun pullOriginMedia(fileName: String) {
|
||||
val item = fileList.find { it.fileName == fileName }
|
||||
if (item != null) {
|
||||
val file = File(FileManager.singleton.tempDir, item.fileName)
|
||||
var offset = 0L
|
||||
if (file.exists()) { offset = file.length() }
|
||||
val outputStream = FileOutputStream(file,true)
|
||||
val bos = BufferedOutputStream(outputStream)
|
||||
item.pullOriginalMediaFileFromCamera(offset, object :
|
||||
MediaFileDownloadListener {
|
||||
override fun onStart() {}
|
||||
override fun onProgress(total: Long, current: Long) {}
|
||||
override fun onRealtimeDataUpdate(data: ByteArray, position: Long) {
|
||||
try {
|
||||
bos.write(data)
|
||||
bos.flush()
|
||||
} catch (error: IOException) {
|
||||
LogManager.i("Write bos error $error")
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinish() {
|
||||
LogManager.i("Successful downloaded")
|
||||
try {
|
||||
outputStream.close()
|
||||
bos.close()
|
||||
} catch (error: IOException) {
|
||||
LogManager.i("Close error $error")
|
||||
} finally {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val res = HttpManager.singleton.uploadJpg(file.readBytes())
|
||||
res?.let { HttpManager.singleton.reqCreateAirtaskPhoto(item.fileName, it.file) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(error: IDJIError?) { LogManager.i("Pull origin error $error") }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun clearCamera() {
|
||||
updateFileList()
|
||||
deleteCameraFiles(fileList)
|
||||
}
|
||||
|
||||
fun deleteCameraFiles(list: MutableList<MediaFile>) {
|
||||
mediaManager.deleteMediaFiles(list, object : CompletionCallback {
|
||||
override fun onSuccess() { LogManager.i("Delete camera files success") }
|
||||
override fun onFailure(error: IDJIError) { LogManager.i("Delete camera files error: $error") }
|
||||
})
|
||||
}
|
||||
|
||||
fun changeHomeLocation(lon: Double, lat: Double) {
|
||||
val location = LocationCoordinate2D()
|
||||
location.latitude = lat
|
||||
location.longitude = lon
|
||||
KeyTools.createKey(FlightControllerKey.KeyHomeLocation).set(location)
|
||||
}
|
||||
|
||||
fun changeFlyStatus(status: String) {
|
||||
if (status == "START_FLY") { KeyTools.createKey(FlightControllerKey.KeyStartTakeoff).action() }
|
||||
if (status == "GO_HOME") { KeyTools.createKey(FlightControllerKey.KeyStartGoHome).action() }
|
||||
if (status == "TAKE_OFF") { KeyTools.createKey(FlightControllerKey.KeyStartAutoLanding).action() }
|
||||
if (status == "TAKE_OFF_CONFIRM") { KeyTools.createKey(FlightControllerKey.KeyConfirmLanding).action() }
|
||||
}
|
||||
|
||||
fun getLocation(): LocationCoordinate3D? {
|
||||
return KeyTools.createKey(FlightControllerKey.KeyAircraftLocation3D).get()
|
||||
}
|
||||
|
||||
fun generateKMZ() {
|
||||
val points = listOf<KMZPoint>(
|
||||
KMZPoint(113.39986078791, 23.113859777049),
|
||||
KMZPoint(113.40060124500, 23.113874582813)
|
||||
)
|
||||
|
||||
val waylineMission = WaylineMission()
|
||||
waylineMission.setCreateTime(System.currentTimeMillis().toDouble())
|
||||
waylineMission.setUpdateTime(System.currentTimeMillis().toDouble())
|
||||
|
||||
val waylineMissionConfig = WaylineMissionConfig()
|
||||
waylineMissionConfig.flyToWaylineMode = WaylineFlyToWaylineMode.SAFELY
|
||||
waylineMissionConfig.finishAction = WaylineFinishedAction.GO_HOME
|
||||
waylineMissionConfig.droneInfo = WaylineDroneInfo()
|
||||
waylineMissionConfig.securityTakeOffHeight = 20.0
|
||||
waylineMissionConfig.isSecurityTakeOffHeightSet = true
|
||||
waylineMissionConfig.exitOnRCLostBehavior = WaylineExitOnRCLostBehavior.EXCUTE_RC_LOST_ACTION
|
||||
waylineMissionConfig.exitOnRCLostType = WaylineExitOnRCLostAction.GO_BACK
|
||||
waylineMissionConfig.globalTransitionalSpeed = 10.0
|
||||
// waylineMissionConfig.payloadInfo = arrayListOf()
|
||||
|
||||
val wayline = Wayline()
|
||||
wayline.autoFlightSpeed = 5.0
|
||||
wayline.waypoints = points.map(transform = {
|
||||
val point = WaylineExecuteWaypoint()
|
||||
point.location = WaylineLocationCoordinate2D(it.lat, it.lon)
|
||||
point
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
// 被动执行收到的 Websocket Payload 命令
|
||||
fun execute(payload: Payload) {
|
||||
try {
|
||||
payload.data["GetLogs"]?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
HttpManager.singleton.reqSendLogs(LogManager.logs)
|
||||
}
|
||||
}
|
||||
|
||||
payload.data["TakePhoto"]?.let { takePhoto() }
|
||||
payload.data["UpdateFileList"]?.let { updateFileList() }
|
||||
payload.data["PullOriginMedia"]?.let { pullOriginMedia(it) }
|
||||
payload.data["StartLiveStream"]?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val channelId = SettingVM.singleton.getSetting("agoraChannelId")
|
||||
val token = SettingVM.singleton.getSetting("agoraToken")
|
||||
val uid = SettingVM.singleton.getSetting("agoraUid")
|
||||
if (channelId != null && token != null && uid != null) {
|
||||
setAgora(channelId.value, token.value, uid.value)
|
||||
startLiveStream(onSuccess = {}, onFailure = { error -> LogManager.i(error) })
|
||||
}
|
||||
}
|
||||
}
|
||||
payload.data["ExecuteAirline"]?.let {
|
||||
if (it == "START") { startMission() }
|
||||
if (it == "STOP") { stopMission() }
|
||||
if (it == "RESUME") { resumeMission() }
|
||||
if (it == "PAUSE") { pauseMission() }
|
||||
}
|
||||
|
||||
payload.data["GenerateKMZ"]?.let {
|
||||
val res = Gson().fromJson(it, KMZPoints::class.java)
|
||||
|
||||
}
|
||||
|
||||
payload.data["ChangeStick"]?.let {
|
||||
if (it == "ENABLE") { enableStick() }
|
||||
if (it == "DISABLE") { disableStick() }
|
||||
}
|
||||
payload.data["ChangeCameraZoom"]?.let { changeCameraZoom(it.toDouble()) }
|
||||
payload.data["ChangeFlyStatus"]?.let { changeFlyStatus(it) }
|
||||
payload.data["ChangeHomeLocationLatitude"]?.let { lat ->
|
||||
payload.data["ChangeHomeLocationLongitude"]?.let { lon ->
|
||||
changeHomeLocation(lon.toDouble(), lat.toDouble())
|
||||
}
|
||||
}
|
||||
payload.data["ChangeAgoraChannelId"]?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch { SettingVM.singleton.setSetting("agoraChannelId", it) }
|
||||
}
|
||||
payload.data["ChangeAgoraToken"]?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch { SettingVM.singleton.setSetting("agoraToken", it) }
|
||||
}
|
||||
payload.data["ChangeAgoraUid"]?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch { SettingVM.singleton.setSetting("agoraUid", it) }
|
||||
}
|
||||
payload.data["ChangeRightStickX"]?.let { changeRightStick(it.toInt(), null) }
|
||||
payload.data["ChangeRightStickY"]?.let { changeRightStick(null, it.toInt()) }
|
||||
payload.data["ChangeLeftStickX"]?.let { changeLeftStick(it.toInt(), null) }
|
||||
payload.data["ChangeLeftStickY"]?.let { changeLeftStick(null, it.toInt()) }
|
||||
payload.data["ChangeGimbalPitch"]?.let { changeGimbalRotate(it.toDouble()) }
|
||||
payload.data["ChangeAirlineUrl"]?.let {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
airlineUrl = it
|
||||
val file = File(FileManager.singleton.airlineDir, "${calculateMD5(it)}.kmz")
|
||||
HttpManager.singleton.downloadFile(file, it)
|
||||
if (file.exists()) {
|
||||
DjiManager.singleton.airlineFile = file
|
||||
uploadKMZ()
|
||||
}
|
||||
}
|
||||
}
|
||||
payload.data["ChangeCameraType"]?.let {
|
||||
if (it == "ZOOM") { changeCameraType(CameraVideoStreamSourceType.ZOOM_CAMERA) }
|
||||
if (it == "WIDE") { changeCameraType(CameraVideoStreamSourceType.WIDE_CAMERA) }
|
||||
if (it == "INFRARED") { changeCameraType(CameraVideoStreamSourceType.INFRARED_CAMERA) }
|
||||
}
|
||||
} catch (error: Error) {
|
||||
LogManager.i("Execute payloads: $error")
|
||||
}
|
||||
}
|
||||
|
||||
// 主动发送 Websocket Payload 命令
|
||||
fun reportKeys(
|
||||
onChangeAltitude: (Double) -> Unit,
|
||||
onChangeTakeOffAltitude: (Double) -> Unit,
|
||||
onChangeCharger: (Int) -> Unit,
|
||||
onChangeAirlinkSignal: (Int) -> Unit
|
||||
) {
|
||||
// 飞行器
|
||||
KeyTools.createKey(FlightControllerKey.KeyIsFlying).listen(this, false) {
|
||||
WebsocketManager.singleton.sendPayload("KeyIsFlying", it.toString())
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyFlightTimeInSeconds).listen(this, false) {
|
||||
WebsocketManager.singleton.sendPayload("KeyFlightTimeInSeconds", it.toString())
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyWindSpeed).listen(this, false) {
|
||||
WebsocketManager.singleton.sendPayload("KeyWindSpeed", it.toString())
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyGPSSignalLevel).listen(this, false) {
|
||||
WebsocketManager.singleton.sendPayload("KeyGPSSignalLevel", it.toString())
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyTakeoffLocationAltitude).listen(this, false) {
|
||||
it?.let { onChangeTakeOffAltitude(it) }
|
||||
WebsocketManager.singleton.sendPayload("KeyTakeoffLocationAltitude", it.toString())
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyAircraftLocation3D).listen(this, false) {
|
||||
it?.let {
|
||||
onChangeAltitude(it.altitude)
|
||||
WebsocketManager.singleton.sendPayloads( mapOf(
|
||||
"KeyAircraftLocation3DAltitude" to it.altitude.toString(),
|
||||
"KeyAircraftLocation3DLatitude" to it.latitude.toString(),
|
||||
"KeyAircraftLocation3DLongitude" to it.longitude.toString(),
|
||||
))
|
||||
}
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyAircraftAttitude).listen(this, false) {
|
||||
it?.let {
|
||||
WebsocketManager.singleton.sendPayloads( mapOf(
|
||||
"KeyAircraftAttitudePitch" to it.pitch.toString(),
|
||||
"KeyAircraftAttitudeRoll" to it.roll.toString(),
|
||||
"KeyAircraftAttitudeYaw" to it.yaw.toString(),
|
||||
))
|
||||
}
|
||||
}
|
||||
KeyTools.createKey(FlightControllerKey.KeyAircraftVelocity).listen(this, false) {
|
||||
it?.let {
|
||||
WebsocketManager.singleton.sendPayloads( mapOf(
|
||||
"KeyAircraftVelocityX" to it.x.toString(),
|
||||
"KeyAircraftVelocityY" to it.y.toString(),
|
||||
"KeyAircraftVelocityZ" to it.z.toString(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
// 云台
|
||||
KeyTools.createKey(GimbalKey.KeyYawRelativeToAircraftHeading).listen(this, false) {
|
||||
WebsocketManager.singleton.sendPayload("KeyYawRelativeToAircraftHeading", it.toString())
|
||||
}
|
||||
|
||||
// 图传
|
||||
KeyTools.createKey(AirLinkKey.KeySignalQuality).listen(this, false) {
|
||||
it?.let { onChangeAirlinkSignal(it) }
|
||||
WebsocketManager.singleton.sendPayload("KeySignalQuality", it.toString())
|
||||
}
|
||||
|
||||
// 电池
|
||||
KeyTools.createKey(BatteryKey.KeyChargeRemainingInPercent).listen(this, false) {
|
||||
it?.let { onChangeCharger(it) }
|
||||
WebsocketManager.singleton.sendPayload("KeyChargeRemainingInPercent", it.toString())
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val singleton: DjiManager by lazy { DjiManager() }
|
||||
fun registerApp(context: Context) {
|
||||
SDKManager.getInstance().init(context, object : SDKManagerCallback {
|
||||
override fun onInitProcess(event: DJISDKInitEvent?, totalProcess: Int) {
|
||||
if (event == DJISDKInitEvent.INITIALIZE_COMPLETE) {
|
||||
SDKManager.getInstance().registerApp()
|
||||
LogManager.i("Init Dji")
|
||||
} else {
|
||||
LogManager.i("Can not init Dji")
|
||||
}
|
||||
}
|
||||
override fun onRegisterSuccess() { LogManager.i("Register Dji Success") }
|
||||
override fun onRegisterFailure(error: IDJIError?) { LogManager.i("Register Dji Fail: $error") }
|
||||
override fun onProductConnect(productId: Int) { }
|
||||
override fun onProductDisconnect(productId: Int) { }
|
||||
override fun onProductChanged(productId: Int) { LogManager.i("Dji product changed: $productId") }
|
||||
override fun onDatabaseDownloadProgress(current: Long, total: Long) { LogManager.i("Dji DB: ${current / total}") }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
37
app/src/main/java/com/power/ops/managers/FileManager.kt
Normal file
@ -0,0 +1,37 @@
|
||||
package com.power.ops.managers
|
||||
|
||||
import android.os.Environment
|
||||
import java.io.File
|
||||
|
||||
class FileManager {
|
||||
val baseDir = File(Environment.getExternalStoragePublicDirectory("Download"), "PowerOps")
|
||||
val tempDir = File(baseDir, "temp")
|
||||
val airlineDir = File(baseDir, "airline")
|
||||
|
||||
init {
|
||||
recreateDir(baseDir)
|
||||
recreateDir(tempDir)
|
||||
recreateDir(airlineDir)
|
||||
}
|
||||
|
||||
fun recreateDir(dir: File) {
|
||||
if (!dir.exists()) { dir.mkdir() }
|
||||
}
|
||||
|
||||
fun recreateFile(file: File) {
|
||||
if (file.exists()) { file.delete() }
|
||||
file.createNewFile()
|
||||
}
|
||||
|
||||
fun read(file: File): String {
|
||||
return file.readText()
|
||||
}
|
||||
|
||||
fun write(file: File, content: String) {
|
||||
file.writeText(content)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val singleton: FileManager = FileManager()
|
||||
}
|
||||
}
|
||||
14
app/src/main/java/com/power/ops/managers/GeckoManager.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package com.power.ops.managers
|
||||
|
||||
import org.mozilla.geckoview.*
|
||||
|
||||
class GeckoManager {
|
||||
lateinit var geckoView: GeckoView
|
||||
lateinit var runtime: GeckoRuntime
|
||||
|
||||
val session = GeckoSession()
|
||||
|
||||
companion object {
|
||||
val singleton: GeckoManager by lazy { GeckoManager() }
|
||||
}
|
||||
}
|
||||
144
app/src/main/java/com/power/ops/managers/HttpManager.kt
Normal file
@ -0,0 +1,144 @@
|
||||
package com.power.ops.managers
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.JsonSyntaxException
|
||||
import com.power.ops.utils.ip
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.Request
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import okio.IOException
|
||||
import java.io.File
|
||||
|
||||
enum class Method { POST, GET }
|
||||
|
||||
data class DefaultResponse(val data: Any, val message: String, val code: String)
|
||||
|
||||
data class LoginRes(val creator: Number, val token: String)
|
||||
data class UploadRes(val file: String)
|
||||
|
||||
class HttpManager {
|
||||
val baseUrl = "http://$ip"
|
||||
var token = ""
|
||||
var creator = 0
|
||||
|
||||
suspend fun downloadFile(file: File, fullUrl: String) {
|
||||
FileManager.singleton.recreateFile(file)
|
||||
val request = Request.Builder().url(fullUrl).get().build()
|
||||
val deferred = CoroutineScope(Dispatchers.IO).async {
|
||||
try {
|
||||
val res = httpClient.newCall(request).execute()
|
||||
res.body?.let {
|
||||
file.outputStream().use { output ->
|
||||
it.byteStream().use { input ->
|
||||
input.copyTo(output)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error: IOException) {
|
||||
LogManager.i("Download io error $error")
|
||||
}
|
||||
}
|
||||
deferred.await()
|
||||
}
|
||||
|
||||
suspend fun uploadJpg(byteArray: ByteArray): UploadRes? {
|
||||
val body = MultipartBody.Builder()
|
||||
.setType(MultipartBody.FORM)
|
||||
.addFormDataPart("file", "file.jpg", byteArray.toRequestBody("image/jpeg".toMediaType()))
|
||||
.build()
|
||||
val request = Request.Builder().url("$baseUrl/api/v1/file/files/upload").post(body).build()
|
||||
val deferred = CoroutineScope(Dispatchers.IO).async {
|
||||
try {
|
||||
val res = httpClient.newCall(request).execute()
|
||||
res.body?.let {
|
||||
parseDataJson(it.string())?.let { data ->
|
||||
try {
|
||||
return@async Gson().fromJson(data, UploadRes::class.java)
|
||||
} catch (_: JsonSyntaxException) {}
|
||||
}
|
||||
}
|
||||
} catch (error: IOException) {
|
||||
LogManager.i("Req io error $error")
|
||||
}
|
||||
return@async null
|
||||
}
|
||||
return deferred.await()
|
||||
}
|
||||
|
||||
fun parseDataJson(body: String): String? {
|
||||
try {
|
||||
val parsed = Gson().fromJson(body, DefaultResponse::class.java)
|
||||
if (parsed.code == "OK") {
|
||||
return Gson().toJson(parsed.data)
|
||||
}
|
||||
} catch (error: JsonSyntaxException) {
|
||||
LogManager.i("ParseDataJson error $error")
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
suspend fun req(
|
||||
method: Method, url: String, jsonString: String = ""
|
||||
): String? {
|
||||
val request: Request = if (method == Method.GET) {
|
||||
Request.Builder().url(baseUrl + url).get().build()
|
||||
} else {
|
||||
val mediaType = "application/json;charset=utf-8".toMediaType()
|
||||
val body = jsonString.toRequestBody(mediaType)
|
||||
Request.Builder().url(baseUrl + url).post(body).build()
|
||||
}
|
||||
|
||||
val deferred = CoroutineScope(Dispatchers.IO).async {
|
||||
try {
|
||||
val res = httpClient.newCall(request).execute()
|
||||
res.body?.let {
|
||||
return@async parseDataJson(it.string())
|
||||
}
|
||||
} catch (error: IOException) {
|
||||
LogManager.i("Req io error $error")
|
||||
}
|
||||
return@async null
|
||||
}
|
||||
return deferred.await()
|
||||
}
|
||||
|
||||
suspend fun reqSendLogs(logs: ArrayList<String>) {
|
||||
req(Method.POST, "/api/v1/aircraft/logs", Gson().toJson(
|
||||
mapOf( "logs" to logs )
|
||||
))
|
||||
}
|
||||
|
||||
suspend fun reqCreateAirtaskPhoto(name: String, fileId: String) {
|
||||
req(Method.POST, "/api/v1/aircraft/airtasks/photo", Gson().toJson(
|
||||
mapOf(
|
||||
"name" to name,
|
||||
"fileId" to fileId
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
suspend fun reqLogin(phone: String, password: String): LoginRes? {
|
||||
val res = req(Method.POST, "/api/v1/user/login", Gson().toJson(
|
||||
mapOf(
|
||||
"phone" to phone,
|
||||
"password" to password
|
||||
)
|
||||
))
|
||||
if (res !== null) {
|
||||
try {
|
||||
return Gson().fromJson(res, LoginRes::class.java)
|
||||
} catch (_: JsonSyntaxException) {}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
companion object {
|
||||
val httpClient: OkHttpClient = OkHttpClient()
|
||||
val singleton: HttpManager by lazy { HttpManager() }
|
||||
}
|
||||
}
|
||||
17
app/src/main/java/com/power/ops/managers/LogManager.kt
Normal file
@ -0,0 +1,17 @@
|
||||
package com.power.ops.managers
|
||||
|
||||
import android.util.Log
|
||||
class LogManager {
|
||||
companion object {
|
||||
val logs: ArrayList<String> = arrayListOf()
|
||||
fun i(message: String) {
|
||||
logs.add(message)
|
||||
if (logs.size > 1000) { logs.removeAt(0) }
|
||||
Log.i("POWER_OPS", message)
|
||||
}
|
||||
|
||||
fun d(message: String) {
|
||||
Log.d("POWER_OPS", message)
|
||||
}
|
||||
}
|
||||
}
|
||||
66
app/src/main/java/com/power/ops/managers/WebsocketManager.kt
Normal file
@ -0,0 +1,66 @@
|
||||
package com.power.ops.managers
|
||||
|
||||
import com.google.gson.Gson
|
||||
import com.power.ops.utils.wsip
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.WebSocket
|
||||
import okhttp3.WebSocketListener
|
||||
|
||||
data class Payload(val data: Map<String, String>)
|
||||
|
||||
class WebsocketManager {
|
||||
var websocket = websocketClient.newWebSocket(
|
||||
Request.Builder().url("ws://$wsip:81").build(),
|
||||
object: WebSocketListener() {
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
super.onOpen(webSocket, response)
|
||||
LogManager.i("Websocket opened")
|
||||
}
|
||||
|
||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||
super.onClosed(webSocket, code, reason)
|
||||
LogManager.i("Websocket closed")
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
super.onFailure(webSocket, t, response)
|
||||
LogManager.i("Websocket failure: $t")
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
super.onMessage(webSocket, text)
|
||||
handlePayloads(text)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
fun handlePayloads(message: String) {
|
||||
try {
|
||||
val payload = Gson().fromJson(message, Payload::class.java)
|
||||
DjiManager.singleton.execute(payload)
|
||||
} catch (e: Error) {
|
||||
LogManager.i("Handle payloads: $e")
|
||||
}
|
||||
}
|
||||
|
||||
fun send(message: String) {
|
||||
websocket.send(message)
|
||||
}
|
||||
|
||||
fun sendPayload(key: String, value: String) {
|
||||
val payload = Payload(mapOf( key to value ))
|
||||
send(Gson().toJson(payload))
|
||||
}
|
||||
|
||||
fun sendPayloads(map: Map<String, String>) {
|
||||
val payload = Payload(map)
|
||||
send(Gson().toJson(payload))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val websocketClient: OkHttpClient by lazy { OkHttpClient() }
|
||||
val singleton: WebsocketManager by lazy { WebsocketManager() }
|
||||
}
|
||||
}
|
||||
255
app/src/main/java/com/power/ops/pages/AircraftPage.kt
Normal file
@ -0,0 +1,255 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import android.content.Context
|
||||
import android.view.SurfaceHolder
|
||||
import android.view.SurfaceView
|
||||
import android.view.TextureView
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.Camera
|
||||
import androidx.compose.material.icons.rounded.Cameraswitch
|
||||
import androidx.compose.material.icons.rounded.Flight
|
||||
import androidx.compose.material.icons.rounded.GroupWork
|
||||
import androidx.compose.material.icons.rounded.PlayCircleOutline
|
||||
import androidx.compose.material.icons.rounded.Refresh
|
||||
import androidx.compose.material.icons.rounded.Route
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.compose.ui.zIndex
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleEventObserver
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.Dropdown
|
||||
import com.power.ops.components.DropdownItem
|
||||
import com.power.ops.components.Header
|
||||
import com.power.ops.components.HeaderType
|
||||
import com.power.ops.components.KVText
|
||||
import com.power.ops.components.Tip
|
||||
import com.power.ops.managers.DjiManager
|
||||
import com.power.ops.managers.FileManager
|
||||
import com.power.ops.managers.HttpManager
|
||||
import com.power.ops.managers.LogManager
|
||||
import com.power.ops.managers.WebsocketManager
|
||||
import com.power.ops.theme.Main400
|
||||
import com.power.ops.theme.Main500
|
||||
import com.power.ops.theme.Secondary500
|
||||
import com.power.ops.theme.Success500
|
||||
import com.power.ops.utils.calculateMD5
|
||||
import com.power.ops.utils.redirectTo
|
||||
import com.power.ops.vms.SettingVM
|
||||
import dji.sdk.keyvalue.value.camera.CameraVideoStreamSourceType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Composable
|
||||
fun AircraftPage(navController: NavController) {
|
||||
var ctx: Context? = null
|
||||
var view: SurfaceView? = null
|
||||
var holder: SurfaceHolder? = null
|
||||
|
||||
var isShowTip by remember { mutableStateOf(false) }
|
||||
var tipText by remember { mutableStateOf("成功") }
|
||||
|
||||
var altitude by remember { mutableStateOf(0.0) }
|
||||
var takeOffAltitude by remember { mutableStateOf(0.0) }
|
||||
var charger by remember { mutableStateOf(0) }
|
||||
var airlinkSignal by remember { mutableStateOf(0) }
|
||||
|
||||
var livestreamStatus by remember { mutableStateOf("") }
|
||||
var channelStatus by remember { mutableStateOf("未获取") }
|
||||
var airlineUrl by remember { mutableStateOf(".kmz") }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val lifecycle = LocalLifecycleOwner.current.lifecycle
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
val observer = LifecycleEventObserver { source, event ->
|
||||
if (event == Lifecycle.Event.ON_RESUME) {
|
||||
coroutineScope.launch {
|
||||
delay(1000)
|
||||
LogManager.i(calculateMD5("http://nofee-default-bucket.oss-cn-guangzhou.aliyuncs.com/files/0acd8cf3-af61-4d6c-bebd-c21a1708dac0.kmz?OSSAccessKeyId=LTAI5tQ3dzfKXUnqW6XA6Yvb&Expires=1702017543&Signature=jPmkiXaH3xP2hKtAsnkWv6b0T0A%3D"))
|
||||
if (ctx != null && holder != null && view != null) {
|
||||
DjiManager.singleton.loadVideoSurface(ctx!!, holder!!, view!!)
|
||||
}
|
||||
DjiManager.singleton.reportKeys(
|
||||
onChangeAltitude = { altitude = it },
|
||||
onChangeTakeOffAltitude = { takeOffAltitude = it },
|
||||
onChangeCharger = { charger = it },
|
||||
onChangeAirlinkSignal = { airlinkSignal = it }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycle.addObserver(observer)
|
||||
}
|
||||
|
||||
Column {
|
||||
Header(
|
||||
navController = navController,
|
||||
headerType = HeaderType.MAIN,
|
||||
title = "新能源飞控中心",
|
||||
height = 35.dp,
|
||||
fontSize = 16.sp
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.background(color = Secondary500))
|
||||
{
|
||||
Column(modifier = Modifier
|
||||
.background(Main400)
|
||||
.width(55.dp)
|
||||
.fillMaxHeight(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceAround
|
||||
) {
|
||||
Dropdown(imageVector = Icons.Rounded.Camera, items = arrayOf(
|
||||
DropdownItem("拍照") {
|
||||
DjiManager.singleton.takePhoto()
|
||||
},
|
||||
DropdownItem("开启媒体") {
|
||||
DjiManager.singleton.enableMedia()
|
||||
},
|
||||
DropdownItem("关闭媒体") {
|
||||
DjiManager.singleton.disableMedia()
|
||||
}
|
||||
))
|
||||
|
||||
Dropdown(imageVector = Icons.Rounded.Cameraswitch, items = arrayOf(
|
||||
DropdownItem("广角") {
|
||||
DjiManager.singleton.changeCameraType(CameraVideoStreamSourceType.WIDE_CAMERA)
|
||||
},
|
||||
DropdownItem("短焦") {
|
||||
DjiManager.singleton.changeCameraType(CameraVideoStreamSourceType.ZOOM_CAMERA)
|
||||
DjiManager.singleton.changeCameraZoom(2.0)
|
||||
},
|
||||
DropdownItem("中焦") {
|
||||
DjiManager.singleton.changeCameraType(CameraVideoStreamSourceType.ZOOM_CAMERA)
|
||||
DjiManager.singleton.changeCameraZoom(4.0)
|
||||
},
|
||||
DropdownItem("长焦") {
|
||||
DjiManager.singleton.changeCameraType(CameraVideoStreamSourceType.ZOOM_CAMERA)
|
||||
DjiManager.singleton.changeCameraZoom(6.0)
|
||||
},
|
||||
DropdownItem("红外") {
|
||||
DjiManager.singleton.changeCameraType(CameraVideoStreamSourceType.INFRARED_CAMERA)
|
||||
}
|
||||
))
|
||||
|
||||
Dropdown(imageVector = Icons.Rounded.Flight, items = arrayOf(
|
||||
DropdownItem("起飞") {
|
||||
DjiManager.singleton.changeFlyStatus("START_FLY")
|
||||
},
|
||||
DropdownItem("返航") {
|
||||
DjiManager.singleton.changeFlyStatus("GO_HOME")
|
||||
},
|
||||
DropdownItem("降落") {
|
||||
DjiManager.singleton.changeFlyStatus("TAKE_OFF")
|
||||
}
|
||||
))
|
||||
|
||||
Dropdown(imageVector = Icons.Rounded.Refresh, items = arrayOf(
|
||||
DropdownItem("重置实时通讯") {
|
||||
WebsocketManager.singleton.sendPayload("KeyIsFlying", "false")
|
||||
DjiManager.singleton.reportKeys(
|
||||
onChangeAltitude = { altitude = it },
|
||||
onChangeTakeOffAltitude = { takeOffAltitude = it },
|
||||
onChangeCharger = { charger = it },
|
||||
onChangeAirlinkSignal = { airlinkSignal = it }
|
||||
)
|
||||
},
|
||||
|
||||
DropdownItem("重置流通道") {
|
||||
channelStatus = DjiManager.singleton.getChannelStatus()
|
||||
if (ctx != null && holder != null && view != null) {
|
||||
DjiManager.singleton.loadVideoSurface(ctx!!, holder!!, view!!)
|
||||
}
|
||||
},
|
||||
|
||||
DropdownItem("重置拍摄格式") {
|
||||
DjiManager.singleton.initPhotoFormat()
|
||||
},
|
||||
|
||||
DropdownItem("重置相机内存") {
|
||||
DjiManager.singleton.clearCamera()
|
||||
},
|
||||
|
||||
DropdownItem("上报日志") {
|
||||
coroutineScope.launch {
|
||||
HttpManager.singleton.reqSendLogs(LogManager.logs)
|
||||
}
|
||||
},
|
||||
))
|
||||
|
||||
}
|
||||
|
||||
Row(modifier = Modifier
|
||||
.background(Success500)
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.zIndex(-999f)
|
||||
) {
|
||||
AndroidView(factory = { context ->
|
||||
ctx = context
|
||||
SurfaceView(context).apply {
|
||||
holder = this.holder
|
||||
view = this
|
||||
}
|
||||
}, modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zIndex(-99f))
|
||||
|
||||
Tip(showTip = isShowTip, text = tipText) { isShowTip = false }
|
||||
}
|
||||
Column(modifier = Modifier
|
||||
.background(Main400)
|
||||
.width(120.dp)
|
||||
.fillMaxHeight()) {
|
||||
KVText(key = "图传", value = "$airlinkSignal")
|
||||
KVText(key = "直播", value = livestreamStatus)
|
||||
KVText(key = "通道", value = channelStatus)
|
||||
KVText(key = "链接", value = airlineUrl)
|
||||
KVText(key = "电量", value = "$charger")
|
||||
KVText(key = "基准", value = "$takeOffAltitude")
|
||||
KVText(key = "高度", value = "$altitude")
|
||||
}
|
||||
}
|
||||
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
// .fillMaxWidth()
|
||||
// .padding(horizontal = 10.dp),
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// horizontalArrangement = Arrangement.SpaceBetween
|
||||
// ) {
|
||||
//
|
||||
//
|
||||
// }
|
||||
}
|
||||
}
|
||||
113
app/src/main/java/com/power/ops/pages/AircraftSettingPage.kt
Normal file
@ -0,0 +1,113 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextField
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.Header
|
||||
import com.power.ops.components.HeaderType
|
||||
import com.power.ops.theme.Char700
|
||||
import com.power.ops.theme.Main400
|
||||
import com.power.ops.theme.PowerTheme
|
||||
import com.power.ops.theme.Secondary500
|
||||
import com.power.ops.theme.Typography
|
||||
import com.power.ops.vms.SettingVM
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun AircraftSettingPage(navController: NavController) {
|
||||
|
||||
var agoraChannelId by remember { mutableStateOf("") }
|
||||
var agoraToken by remember { mutableStateOf("") }
|
||||
var agoraUid by remember { mutableStateOf("") }
|
||||
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(coroutineScope) {
|
||||
val channelId = SettingVM.singleton.getSetting("agoraChannelId")
|
||||
val token = SettingVM.singleton.getSetting("agoraToken")
|
||||
val uid = SettingVM.singleton.getSetting("agoraUid")
|
||||
|
||||
if (channelId != null) { agoraChannelId = channelId.value }
|
||||
if (token != null) { agoraToken = token.value }
|
||||
if (uid != null) { agoraUid = uid.value }
|
||||
}
|
||||
|
||||
Column(modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Secondary500)) {
|
||||
Header(
|
||||
navController,
|
||||
HeaderType.CONFIRM,
|
||||
title = "飞控设置",
|
||||
) {
|
||||
coroutineScope.launch {
|
||||
SettingVM.singleton.setSetting("agoraChannelId", agoraChannelId)
|
||||
SettingVM.singleton.setSetting("agoraToken", agoraToken)
|
||||
SettingVM.singleton.setSetting("agoraUid", agoraUid)
|
||||
navController.popBackStack()
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(14.dp)
|
||||
) {
|
||||
TextField(
|
||||
value = agoraChannelId,
|
||||
onValueChange = { agoraChannelId = it },
|
||||
label = { Text("通道 ID", color = Char700, style = Typography.labelSmall) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 7.dp),
|
||||
colors = TextFieldDefaults.textFieldColors(containerColor = Main400)
|
||||
)
|
||||
|
||||
TextField(
|
||||
value = agoraToken,
|
||||
onValueChange = { agoraToken = it },
|
||||
label = { Text("Token", color = Char700, style = Typography.labelSmall) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 7.dp),
|
||||
colors = TextFieldDefaults.textFieldColors(containerColor = Main400)
|
||||
)
|
||||
|
||||
TextField(
|
||||
value = agoraUid,
|
||||
onValueChange = { agoraUid = it },
|
||||
label = { Text("UID", color = Char700, style = Typography.labelSmall) },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(vertical = 7.dp),
|
||||
colors = TextFieldDefaults.textFieldColors(containerColor = Main400)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewAircraftSettingPage() {
|
||||
PowerTheme {
|
||||
AircraftSettingPage(navController = NavController(LocalContext.current))
|
||||
}
|
||||
}
|
||||
43
app/src/main/java/com/power/ops/pages/HomePage.kt
Normal file
@ -0,0 +1,43 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.BottomBar
|
||||
import com.power.ops.components.GeckoWebview
|
||||
import com.power.ops.components.Header
|
||||
import com.power.ops.components.HeaderType
|
||||
import com.power.ops.components.Webview
|
||||
import com.power.ops.theme.PowerTheme
|
||||
import com.power.ops.theme.Secondary500
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomePage(navController: NavController) {
|
||||
Column {
|
||||
Header(navController = navController,headerType = HeaderType.MAIN, title = "首页")
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.background(color = Secondary500)
|
||||
) {
|
||||
GeckoWebview()
|
||||
}
|
||||
BottomBar(navController, "HomePage")
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewHomePage() {
|
||||
PowerTheme {
|
||||
HomePage(navController = NavController(LocalContext.current))
|
||||
}
|
||||
}
|
||||
137
app/src/main/java/com/power/ops/pages/LoginPage.kt
Normal file
@ -0,0 +1,137 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextFieldDefaults
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.Tip
|
||||
import com.power.ops.managers.HttpManager
|
||||
import com.power.ops.theme.Char400
|
||||
import com.power.ops.theme.Char500
|
||||
import com.power.ops.theme.Char600
|
||||
import com.power.ops.theme.Main400
|
||||
import com.power.ops.theme.Main500
|
||||
import com.power.ops.theme.PowerTheme
|
||||
import com.power.ops.theme.Primary500
|
||||
import com.power.ops.theme.Secondary500
|
||||
import com.power.ops.theme.Typography
|
||||
import com.power.ops.utils.openUrl
|
||||
import com.power.ops.vms.SettingVM
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun LoginPage(navController: NavController) {
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
var phone by remember { mutableStateOf("") }
|
||||
var password by remember { mutableStateOf("") }
|
||||
|
||||
var isShowTip by remember { mutableStateOf(false) }
|
||||
var tipText by remember { mutableStateOf("成功") }
|
||||
|
||||
LaunchedEffect(coroutineScope) {
|
||||
SettingVM.singleton.setSetting("token", "")
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(color = Secondary500),
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Text(text = "新能源智能", style = Typography.headlineLarge)
|
||||
Text(text = "Welcome to Power Ops System", color = Char600, modifier = Modifier.padding(4.dp), style = Typography.labelSmall)
|
||||
|
||||
OutlinedTextField(
|
||||
value = phone,
|
||||
onValueChange = { phone = it },
|
||||
placeholder = { Text(text = "账号") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 20.dp, start = 30.dp, end = 30.dp),
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(unfocusedBorderColor = Char400, focusedBorderColor = Primary500)
|
||||
)
|
||||
|
||||
OutlinedTextField(
|
||||
value = password,
|
||||
onValueChange = { password = it },
|
||||
placeholder = { Text(text = "密码") },
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 14.dp, start = 30.dp, end = 30.dp),
|
||||
colors = TextFieldDefaults.outlinedTextFieldColors(unfocusedBorderColor = Char400, focusedBorderColor = Primary500)
|
||||
)
|
||||
|
||||
Button(
|
||||
onClick = {
|
||||
coroutineScope.launch {
|
||||
val res = HttpManager.singleton.reqLogin(phone, password)
|
||||
if (res != null) {
|
||||
HttpManager.singleton.creator = res.creator.toInt()
|
||||
HttpManager.singleton.token = res.token
|
||||
SettingVM.singleton.setSetting("token", res.token)
|
||||
navController.navigate("HomePage")
|
||||
} else {
|
||||
tipText = "登录出错"
|
||||
isShowTip = true
|
||||
}
|
||||
}
|
||||
},
|
||||
shape = RoundedCornerShape(5.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(65.dp)
|
||||
.padding(top = 14.dp, start = 30.dp, end = 30.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Primary500, contentColor = Main500)) {
|
||||
Text("立即登录", color = Main500)
|
||||
}
|
||||
|
||||
Button(
|
||||
onClick = { openUrl(context, "http://power.nofee.fun") },
|
||||
shape = RoundedCornerShape(5.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(65.dp)
|
||||
.padding(top = 14.dp, start = 30.dp, end = 30.dp),
|
||||
colors = ButtonDefaults.buttonColors(containerColor = Main400, contentColor = Char500)) {
|
||||
Text("前往注册")
|
||||
}
|
||||
|
||||
Tip(showTip = isShowTip, text = tipText) { isShowTip = false }
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewLoginPage() {
|
||||
PowerTheme {
|
||||
LoginPage(navController = NavController(LocalContext.current))
|
||||
}
|
||||
}
|
||||
28
app/src/main/java/com/power/ops/pages/MessagePage.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.BottomBar
|
||||
import com.power.ops.components.Webview
|
||||
import com.power.ops.theme.Secondary500
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun MessagePage(navController: NavController) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.background(color = Secondary500)
|
||||
) {
|
||||
Webview(uri = "/message")
|
||||
}
|
||||
BottomBar(navController, "MessagePage")
|
||||
}
|
||||
}
|
||||
160
app/src/main/java/com/power/ops/pages/SettingPage.kt
Normal file
@ -0,0 +1,160 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.AccountCircle
|
||||
import androidx.compose.material.icons.rounded.AirplanemodeActive
|
||||
import androidx.compose.material.icons.rounded.ListAlt
|
||||
import androidx.compose.material.icons.rounded.Logout
|
||||
import androidx.compose.material.icons.rounded.NetworkCheck
|
||||
import androidx.compose.material.icons.rounded.PrivacyTip
|
||||
import androidx.compose.material.icons.rounded.Upload
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.BottomBar
|
||||
import com.power.ops.components.Dialog
|
||||
import com.power.ops.components.Divider
|
||||
import com.power.ops.components.Header
|
||||
import com.power.ops.components.HeaderType
|
||||
import com.power.ops.components.RichItem
|
||||
import com.power.ops.components.Tip
|
||||
import com.power.ops.managers.HttpManager
|
||||
import com.power.ops.managers.LogManager
|
||||
import com.power.ops.theme.Main400
|
||||
import com.power.ops.theme.PowerTheme
|
||||
import com.power.ops.theme.Secondary500
|
||||
import com.power.ops.theme.Typography
|
||||
import com.power.ops.utils.getVersionName
|
||||
import com.power.ops.utils.openUrl
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SettingPage(navController: NavController) {
|
||||
var isShowDialog by remember { mutableStateOf(false) }
|
||||
|
||||
var isShowTip by remember { mutableStateOf(false) }
|
||||
var tipText by remember { mutableStateOf("成功") }
|
||||
|
||||
|
||||
//val settingDao: SettingDao = SettingDatabase.getDatabase(LocalContext.current).settingDao()
|
||||
// SettingVM.singleton(LocalContext.current).setSetting()
|
||||
// val settings by settingDao.querySettings().collectAsState(initial = emptyList())
|
||||
// var settings by object : SettingVM()
|
||||
|
||||
val context = LocalContext.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
Column(modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.background(color = Secondary500)) {
|
||||
Header(
|
||||
navController = navController,
|
||||
headerType = HeaderType.CONFIRM,
|
||||
title = "设置"
|
||||
) {
|
||||
navController.popBackStack()
|
||||
}
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
) {
|
||||
this.item {
|
||||
Column(modifier = Modifier
|
||||
.padding(start = 14.dp, end = 14.dp, top = 14.dp)
|
||||
.clip(shape = RoundedCornerShape(10.dp))
|
||||
.background(Main400)
|
||||
) {
|
||||
RichItem(imageVector = Icons.Rounded.AccountCircle, text = "修改资料") {
|
||||
tipText = "未开放"
|
||||
isShowTip = true
|
||||
}
|
||||
Divider()
|
||||
RichItem(imageVector = Icons.Rounded.NetworkCheck, text = "网络设置") {
|
||||
tipText = "开发中"
|
||||
isShowTip = true
|
||||
}
|
||||
Dialog(defaultValue = "http://", showDialog = isShowDialog, onDismiss = {
|
||||
isShowDialog = false
|
||||
}, onConfirm = {
|
||||
LogManager.i(it)
|
||||
})
|
||||
Divider()
|
||||
RichItem(imageVector = Icons.Rounded.AirplanemodeActive, text = "飞控设置") {
|
||||
navController.navigate("AircraftSettingPage")
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier
|
||||
.padding(start = 14.dp, end = 14.dp, top = 14.dp)
|
||||
.clip(shape = RoundedCornerShape(10.dp))
|
||||
.background(Main400)
|
||||
) {
|
||||
RichItem(imageVector = Icons.Rounded.ListAlt, text = "上传日志") {
|
||||
coroutineScope.launch {
|
||||
tipText = "上传成功"
|
||||
HttpManager.singleton.reqSendLogs(LogManager.logs)
|
||||
isShowTip = true
|
||||
}
|
||||
}
|
||||
Divider()
|
||||
RichItem(imageVector = Icons.Rounded.PrivacyTip, text = "隐私政策") {
|
||||
openUrl(context, "http://power.nofee.fun/privacy")
|
||||
}
|
||||
Divider()
|
||||
RichItem(imageVector = Icons.Rounded.Upload, text = "版本更新", intro = "内测版:${getVersionName(context)}") {
|
||||
openUrl(context, "http://power.nofee.fun/android")
|
||||
}
|
||||
}
|
||||
|
||||
Column(modifier = Modifier
|
||||
.padding(start = 14.dp, end = 14.dp, top = 14.dp)
|
||||
.clip(shape = RoundedCornerShape(10.dp))
|
||||
.background(Main400)
|
||||
) {
|
||||
RichItem(imageVector = Icons.Rounded.Logout, text = "退出登录") {
|
||||
navController.navigate("LoginPage")
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
text = "© 2023 新能源智能",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(20.dp),
|
||||
textAlign = TextAlign.Center,
|
||||
style = Typography.bodySmall
|
||||
)
|
||||
}
|
||||
}
|
||||
Tip(showTip = isShowTip, text = tipText) { isShowTip = false }
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
fun PreviewSettingPage() {
|
||||
PowerTheme {
|
||||
SettingPage(navController = NavController(LocalContext.current))
|
||||
}
|
||||
}
|
||||
28
app/src/main/java/com/power/ops/pages/SitePage.kt
Normal file
@ -0,0 +1,28 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.BottomBar
|
||||
import com.power.ops.components.Webview
|
||||
import com.power.ops.theme.Secondary500
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SitePage(navController: NavController) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.weight(1f)
|
||||
.background(color = Secondary500)
|
||||
) {
|
||||
Webview(uri = "/developer")
|
||||
}
|
||||
BottomBar(navController, "SitePage")
|
||||
}
|
||||
}
|
||||
26
app/src/main/java/com/power/ops/pages/SubmitPage.kt
Normal file
@ -0,0 +1,26 @@
|
||||
package com.power.ops.pages
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.navigation.NavController
|
||||
import com.power.ops.components.BottomBar
|
||||
import com.power.ops.components.Webview
|
||||
import com.power.ops.theme.Secondary500
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun SubmitPage(navController: NavController) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxHeight()
|
||||
.weight(1f).background(color = Secondary500)
|
||||
) {
|
||||
Webview(uri = "/workflow")
|
||||
}
|
||||
BottomBar(navController, "SubmitPage")
|
||||
}
|
||||
}
|
||||
51
app/src/main/java/com/power/ops/theme/Color.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package com.power.ops.theme
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
|
||||
val Main300 = Color(0xFFD0BCFF)
|
||||
val Main400 = Color(0xFF062e5f)
|
||||
val Main500 = Color(0xFF00224c)
|
||||
val Main600 = Color(0xFF011835)
|
||||
val Main700 = Color(0xFF010a15)
|
||||
|
||||
val Secondary300 = Color(0xFF004684)
|
||||
val Secondary400 = Color(0xFF003e73)
|
||||
val Secondary500 = Color(0xFF003767)
|
||||
val Secondary600 = Color(0xFF02335d)
|
||||
val Secondary700 = Color(0xFF052d4f)
|
||||
|
||||
val Primary300 = Color(0xFF9dfdff)
|
||||
val Primary400 = Color(0xFF52ecef)
|
||||
val Primary500 = Color(0xFF00d3d8)
|
||||
val Primary600 = Color(0xFF02bcc0)
|
||||
val Primary700 = Color(0xFF03888b)
|
||||
|
||||
val Error300 = Color(0xFF936f82)
|
||||
val Error400 = Color(0xFFdb696d)
|
||||
val Error500 = Color(0xFFfa3f3f)
|
||||
val Error600 = Color(0xFFcf2f33)
|
||||
val Error700 = Color(0xFFa70c14)
|
||||
|
||||
val Warning300 = Color(0xFF919681)
|
||||
val Warning400 = Color(0xFFdbb36d)
|
||||
val Warning500 = Color(0xFFf9b630)
|
||||
val Warning600 = Color(0xFFcf9633)
|
||||
val Warning700 = Color(0xFFaf7815)
|
||||
|
||||
val Success300 = Color(0xFF59a79c)
|
||||
val Success400 = Color(0xFF62e0a4)
|
||||
val Success500 = Color(0xFF17d872)
|
||||
val Success600 = Color(0xFF24cc79)
|
||||
val Success700 = Color(0xFF02a85b)
|
||||
|
||||
val Info300 = Color(0xFF5386b8)
|
||||
val Info400 = Color(0xFF609edf)
|
||||
val Info500 = Color(0xFF1c86f7)
|
||||
val Info600 = Color(0xFF186dc7)
|
||||
val Info700 = Color(0xFF0252a9)
|
||||
|
||||
val Char300 = Color(0xFFf1f5f9)
|
||||
val Char400 = Color(0xFFe2e8f0)
|
||||
val Char500 = Color(0xFFcbd5e1)
|
||||
val Char600 = Color(0xFF94a3b8)
|
||||
val Char700 = Color(0xFF64748b)
|
||||
100
app/src/main/java/com/power/ops/theme/Theme.kt
Normal file
@ -0,0 +1,100 @@
|
||||
package com.power.ops.theme
|
||||
|
||||
import android.app.Activity
|
||||
import android.os.Build
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.dynamicDarkColorScheme
|
||||
import androidx.compose.material3.dynamicLightColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.ui.graphics.toArgb
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.compose.material3.Typography
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.unit.sp
|
||||
|
||||
val Typography = Typography(
|
||||
displayLarge = TextStyle(color = Char500),
|
||||
displayMedium = TextStyle(color = Char500),
|
||||
displaySmall = TextStyle(color = Char500),
|
||||
bodyLarge = TextStyle(
|
||||
color = Char500,
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Normal,
|
||||
fontSize = 16.sp,
|
||||
lineHeight = 24.sp,
|
||||
letterSpacing = 0.5.sp
|
||||
),
|
||||
bodyMedium = TextStyle(color = Char500),
|
||||
bodySmall = TextStyle(
|
||||
color = Char700,
|
||||
fontSize = 10.sp
|
||||
),
|
||||
titleLarge = TextStyle(
|
||||
color = Char400,
|
||||
fontFamily = FontFamily.Default,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 20.sp,
|
||||
lineHeight = 28.sp,
|
||||
letterSpacing = 0.sp
|
||||
),
|
||||
titleMedium = TextStyle(color = Char500),
|
||||
titleSmall = TextStyle(color = Char500),
|
||||
labelLarge = TextStyle(color = Char500),
|
||||
labelMedium = TextStyle(color = Char500),
|
||||
labelSmall = TextStyle(color = Char500),
|
||||
headlineLarge = TextStyle(
|
||||
color = Char500,
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = 28.sp,
|
||||
lineHeight = 30.sp,
|
||||
),
|
||||
headlineMedium = TextStyle(color = Char500),
|
||||
headlineSmall = TextStyle(color = Char500)
|
||||
)
|
||||
|
||||
private val DarkColorScheme = darkColorScheme(
|
||||
primary = Primary500,
|
||||
secondary = Secondary500,
|
||||
tertiary = Secondary400
|
||||
)
|
||||
|
||||
private val LightColorScheme = lightColorScheme()
|
||||
|
||||
@Composable
|
||||
fun PowerTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
val darkTheme = true
|
||||
val dynamicColor = false
|
||||
val colorScheme = when {
|
||||
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
|
||||
val context = LocalContext.current
|
||||
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
|
||||
}
|
||||
|
||||
darkTheme -> DarkColorScheme
|
||||
else -> LightColorScheme
|
||||
}
|
||||
val view = LocalView.current
|
||||
if (!view.isInEditMode) {
|
||||
SideEffect {
|
||||
val window = (view.context as Activity).window
|
||||
window.statusBarColor = Main500.toArgb()
|
||||
|
||||
WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTheme(
|
||||
colorScheme = colorScheme,
|
||||
typography = Typography,
|
||||
content = content
|
||||
)
|
||||
}
|
||||
51
app/src/main/java/com/power/ops/utils/Helper.kt
Normal file
@ -0,0 +1,51 @@
|
||||
package com.power.ops.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.navigation.NavController
|
||||
import androidx.navigation.NavOptions
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import android.util.Base64
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.security.MessageDigest
|
||||
|
||||
fun calculateMD5(input: String): String {
|
||||
val md = MessageDigest.getInstance("MD5")
|
||||
val byteArray = md.digest(input.toByteArray())
|
||||
|
||||
val result = StringBuilder()
|
||||
for (byte in byteArray) {
|
||||
result.append(String.format("%02x", byte))
|
||||
}
|
||||
|
||||
return result.toString()
|
||||
}
|
||||
|
||||
fun bitmapToBase64(bitmap: Bitmap): String {
|
||||
val byteArrayOutputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
|
||||
val byteArray = byteArrayOutputStream.toByteArray()
|
||||
return Base64.encodeToString(byteArray, Base64.DEFAULT)
|
||||
}
|
||||
|
||||
val wsip = "8.134.154.108"
|
||||
// val ip = "8.134.154.108"
|
||||
val ip = "power.nofee.fun"
|
||||
// val ip = "172.20.10.9"
|
||||
// val ip = "192.168.31.178"
|
||||
|
||||
fun redirectTo(navController: NavController, route: String) {
|
||||
val navOptions = NavOptions.Builder().setPopUpTo(navController.graph.startDestinationRoute, inclusive = true).build()
|
||||
navController.navigate(route, navOptions = navOptions)
|
||||
}
|
||||
|
||||
fun openUrl(context: Context, url: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
context.startActivity(intent)
|
||||
}
|
||||
|
||||
fun getVersionName(context: Context): String {
|
||||
return context.packageManager.getPackageInfo(context.packageName, 0).versionName
|
||||
}
|
||||
99
app/src/main/java/com/power/ops/vms/SettingVM.kt
Normal file
@ -0,0 +1,99 @@
|
||||
package com.power.ops.vms
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Database
|
||||
import androidx.room.Entity
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Query
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.Update
|
||||
import com.power.ops.managers.FileManager
|
||||
import com.power.ops.managers.LogManager
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
|
||||
@Entity(tableName = "settings")
|
||||
data class SettingEntity(
|
||||
@PrimaryKey(autoGenerate = true) val id: Int = 0,
|
||||
var key: String,
|
||||
var value: String
|
||||
)
|
||||
|
||||
@Dao
|
||||
interface SettingDao {
|
||||
@Query("select * from settings")
|
||||
fun querySettings(): Flow<List<SettingEntity>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
suspend fun createSetting(setting: SettingEntity)
|
||||
|
||||
@Update
|
||||
suspend fun updateSetting(setting: SettingEntity)
|
||||
}
|
||||
|
||||
@Database(entities = [SettingEntity::class], version = 1)
|
||||
abstract class SettingDatabase : RoomDatabase() {
|
||||
abstract fun settingDao(): SettingDao
|
||||
companion object {
|
||||
@Volatile
|
||||
private var INSTANCE: SettingDatabase? = null
|
||||
fun getDatabase(context: Context): SettingDatabase {
|
||||
return INSTANCE ?: synchronized(this) {
|
||||
val instance = Room.databaseBuilder(
|
||||
context.applicationContext,
|
||||
SettingDatabase::class.java,
|
||||
"setting"
|
||||
).build()
|
||||
INSTANCE = instance
|
||||
instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SettingVM {
|
||||
lateinit var dao: SettingDao
|
||||
lateinit var settings:Flow<List<SettingEntity>>
|
||||
suspend fun getSetting(key: String): SettingEntity? {
|
||||
settings = dao.querySettings()
|
||||
val res = settings.map {
|
||||
it.find { setting ->
|
||||
setting.key == key
|
||||
}
|
||||
}.first()
|
||||
return res
|
||||
}
|
||||
|
||||
suspend fun setSetting(key: String, value: String) {
|
||||
val res = getSetting(key)
|
||||
if (res == null) {
|
||||
dao.createSetting(SettingEntity(key = key, value = value))
|
||||
} else {
|
||||
res.value = value
|
||||
dao.updateSetting(res)
|
||||
}
|
||||
}
|
||||
|
||||
fun initDao(context: Context) {
|
||||
dao = SettingDatabase.getDatabase(context).settingDao()
|
||||
settings = dao.querySettings()
|
||||
}
|
||||
|
||||
companion object {
|
||||
val singleton: SettingVM = SettingVM()
|
||||
}
|
||||
}
|
||||
63
app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,63 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="213.37"
|
||||
android:viewportHeight="213.84">
|
||||
<group android:scaleX="0.3691868"
|
||||
android:scaleY="0.37"
|
||||
android:translateX="67.29831"
|
||||
android:translateY="67.3596">
|
||||
<path
|
||||
android:pathData="M186.29,85.38c0.17,13.25 -1.41,26.98 -4.9,40.79 -3.79,14.99 -9.53,28.84 -16.77,41.17 -16.81,-5.74 -34.09,-14.98 -50.54,-27.44 3.66,-8.97 6.79,-18.47 9.3,-28.4 2.24,-8.84 3.87,-17.61 4.94,-26.23 2.58,-20.63 1.92,-40.35 -1.57,-57.88 -1.97,-9.91 -4.85,-19.11 -8.55,-27.39 3.75,0.54 7.51,1.28 11.25,2.23 15.6,3.94 29.55,11.11 41.28,20.64 2.25,4.13 4.28,8.46 6.07,12.95 5.97,14.99 9.29,31.84 9.49,49.56ZM84.25,189.61c-5.83,6.73 -12.01,12.62 -18.42,17.54 3.55,1.31 7.2,2.44 10.95,3.39 15.6,3.95 31.28,4.26 46.13,1.46 2.71,-1.76 5.38,-3.66 7.98,-5.68 -15.65,-2.5 -31.54,-8.1 -46.64,-16.71Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="153.27"
|
||||
android:startY="53.83"
|
||||
android:endX="114.4"
|
||||
android:endY="166.97"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF00D3D8"/>
|
||||
<item android:offset="0.22" android:color="#FF00CBD5"/>
|
||||
<item android:offset="0.55" android:color="#FF00B7CD"/>
|
||||
<item android:offset="0.95" android:color="#FF0096C0"/>
|
||||
<item android:offset="1" android:color="#FF0091BF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M128.32,85.27c-8.73,1.32 -17.62,3.23 -26.56,5.76 -9.59,2.72 -18.76,6.01 -27.42,9.79 -19,8.28 -35.54,18.88 -48.73,30.8 -6.9,6.23 -12.89,12.83 -17.82,19.64 -1.41,-3.51 -2.65,-7.13 -3.7,-10.85C-0.29,124.93 -1.05,109.27 1.33,94.34c2.12,-3.48 4.46,-6.88 6.99,-10.2 9.81,-12.83 22.62,-24.33 37.78,-33.57 11.79,-7.2 25.02,-13.04 39.36,-17.11 13.93,-3.94 27.83,-5.91 41.29,-6.07 3.49,17.53 4.15,37.25 1.57,57.88ZM210.83,81.87c-4.38,-15.49 -11.95,-29.22 -21.8,-40.68 -3.92,-2 -8.01,-3.79 -12.23,-5.37 5.97,14.99 9.29,31.84 9.49,49.56 9.75,1.69 18.85,4.27 27.08,7.67 -0.64,-3.73 -1.48,-7.46 -2.54,-11.18Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="10.66"
|
||||
android:startY="103.3"
|
||||
android:endX="186.47"
|
||||
android:endY="52.56"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF00D3D8"/>
|
||||
<item android:offset="0.22" android:color="#FF00CBD5"/>
|
||||
<item android:offset="0.54" android:color="#FF00B8CD"/>
|
||||
<item android:offset="0.92" android:color="#FF0098C1"/>
|
||||
<item android:offset="1" android:color="#FF0091BF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M192.97,173.76c-2.34,2.97 -4.86,5.86 -7.55,8.63 -11.22,11.54 -24.4,20.02 -38.52,25.42 -5.28,-0.13 -10.63,-0.63 -16.01,-1.49 -15.65,-2.5 -31.54,-8.1 -46.64,-16.71 -11.29,-6.4 -22.14,-14.49 -32.14,-24.21 -10.7,-10.4 -19.55,-21.83 -26.5,-33.78 13.19,-11.92 29.73,-22.52 48.73,-30.8 5.81,7.45 12.32,14.71 19.48,21.67 6.54,6.36 13.33,12.18 20.26,17.41 16.45,12.46 33.73,21.7 50.54,27.44 9.68,3.32 19.22,5.47 28.35,6.42ZM39.76,24.83c-2.91,2.42 -5.72,5.01 -8.41,7.79 -11.22,11.53 -19.33,24.95 -24.33,39.22 0.21,4.07 0.65,8.17 1.3,12.3 9.81,-12.83 22.62,-24.33 37.78,-33.57 -3.11,-8.76 -5.24,-17.4 -6.34,-25.74Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="144.67"
|
||||
android:startY="191.51"
|
||||
android:endX="13.5"
|
||||
android:endY="61.56"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF00D3D8"/>
|
||||
<item android:offset="0.22" android:color="#FF00CBD5"/>
|
||||
<item android:offset="0.55" android:color="#FF00B7CD"/>
|
||||
<item android:offset="0.95" android:color="#FF0095C0"/>
|
||||
<item android:offset="1" android:color="#FF0091BF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
||||
58
app/src/main/res/drawable/i3060.xml
Normal file
@ -0,0 +1,58 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="213.37dp"
|
||||
android:height="213.84dp"
|
||||
android:viewportWidth="213.37"
|
||||
android:viewportHeight="213.84">
|
||||
<path
|
||||
android:pathData="M186.29,85.38c0.17,13.25 -1.41,26.98 -4.9,40.79 -3.79,14.99 -9.53,28.84 -16.77,41.17 -16.81,-5.74 -34.09,-14.98 -50.54,-27.44 3.66,-8.97 6.79,-18.47 9.3,-28.4 2.24,-8.84 3.87,-17.61 4.94,-26.23 2.58,-20.63 1.92,-40.35 -1.57,-57.88 -1.97,-9.91 -4.85,-19.11 -8.55,-27.39 3.75,0.54 7.51,1.28 11.25,2.23 15.6,3.94 29.55,11.11 41.28,20.64 2.25,4.13 4.28,8.46 6.07,12.95 5.97,14.99 9.29,31.84 9.49,49.56ZM84.25,189.61c-5.83,6.73 -12.01,12.62 -18.42,17.54 3.55,1.31 7.2,2.44 10.95,3.39 15.6,3.95 31.28,4.26 46.13,1.46 2.71,-1.76 5.38,-3.66 7.98,-5.68 -15.65,-2.5 -31.54,-8.1 -46.64,-16.71Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="153.27"
|
||||
android:startY="53.83"
|
||||
android:endX="114.4"
|
||||
android:endY="166.97"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF00D3D8"/>
|
||||
<item android:offset="0.22" android:color="#FF00CBD5"/>
|
||||
<item android:offset="0.55" android:color="#FF00B7CD"/>
|
||||
<item android:offset="0.95" android:color="#FF0096C0"/>
|
||||
<item android:offset="1" android:color="#FF0091BF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M128.32,85.27c-8.73,1.32 -17.62,3.23 -26.56,5.76 -9.59,2.72 -18.76,6.01 -27.42,9.79 -19,8.28 -35.54,18.88 -48.73,30.8 -6.9,6.23 -12.89,12.83 -17.82,19.64 -1.41,-3.51 -2.65,-7.13 -3.7,-10.85C-0.29,124.93 -1.05,109.27 1.33,94.34c2.12,-3.48 4.46,-6.88 6.99,-10.2 9.81,-12.83 22.62,-24.33 37.78,-33.57 11.79,-7.2 25.02,-13.04 39.36,-17.11 13.93,-3.94 27.83,-5.91 41.29,-6.07 3.49,17.53 4.15,37.25 1.57,57.88ZM210.83,81.87c-4.38,-15.49 -11.95,-29.22 -21.8,-40.68 -3.92,-2 -8.01,-3.79 -12.23,-5.37 5.97,14.99 9.29,31.84 9.49,49.56 9.75,1.69 18.85,4.27 27.08,7.67 -0.64,-3.73 -1.48,-7.46 -2.54,-11.18Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="10.66"
|
||||
android:startY="103.3"
|
||||
android:endX="186.47"
|
||||
android:endY="52.56"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF00D3D8"/>
|
||||
<item android:offset="0.22" android:color="#FF00CBD5"/>
|
||||
<item android:offset="0.54" android:color="#FF00B8CD"/>
|
||||
<item android:offset="0.92" android:color="#FF0098C1"/>
|
||||
<item android:offset="1" android:color="#FF0091BF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:pathData="M192.97,173.76c-2.34,2.97 -4.86,5.86 -7.55,8.63 -11.22,11.54 -24.4,20.02 -38.52,25.42 -5.28,-0.13 -10.63,-0.63 -16.01,-1.49 -15.65,-2.5 -31.54,-8.1 -46.64,-16.71 -11.29,-6.4 -22.14,-14.49 -32.14,-24.21 -10.7,-10.4 -19.55,-21.83 -26.5,-33.78 13.19,-11.92 29.73,-22.52 48.73,-30.8 5.81,7.45 12.32,14.71 19.48,21.67 6.54,6.36 13.33,12.18 20.26,17.41 16.45,12.46 33.73,21.7 50.54,27.44 9.68,3.32 19.22,5.47 28.35,6.42ZM39.76,24.83c-2.91,2.42 -5.72,5.01 -8.41,7.79 -11.22,11.53 -19.33,24.95 -24.33,39.22 0.21,4.07 0.65,8.17 1.3,12.3 9.81,-12.83 22.62,-24.33 37.78,-33.57 -3.11,-8.76 -5.24,-17.4 -6.34,-25.74Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="144.67"
|
||||
android:startY="191.51"
|
||||
android:endX="13.5"
|
||||
android:endY="61.56"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FF00D3D8"/>
|
||||
<item android:offset="0.22" android:color="#FF00CBD5"/>
|
||||
<item android:offset="0.55" android:color="#FF00B7CD"/>
|
||||
<item android:offset="0.95" android:color="#FF0095C0"/>
|
||||
<item android:offset="1" android:color="#FF0091BF"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
5
app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 956 B |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 6.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
4
app/src/main/res/values/ic_launcher_background.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#003767</color>
|
||||
</resources>
|
||||
4
app/src/main/res/values/themes.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<style name="Theme.Power" parent="android:Theme.Material.NoActionBar" />
|
||||
</resources>
|
||||
13
app/src/main/res/xml/backup_rules.xml
Normal file
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample backup rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/guide/topics/data/autobackup
|
||||
for details.
|
||||
Note: This file is ignored for devices older that API 31
|
||||
See https://developer.android.com/about/versions/12/backup-restore
|
||||
-->
|
||||
<full-backup-content>
|
||||
<!--
|
||||
<include domain="sharedpref" path="."/>
|
||||
<exclude domain="sharedpref" path="device.xml"/>
|
||||
-->
|
||||
</full-backup-content>
|
||||
19
app/src/main/res/xml/data_extraction_rules.xml
Normal file
@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
Sample data extraction rules file; uncomment and customize as necessary.
|
||||
See https://developer.android.com/about/versions/12/backup-restore#xml-changes
|
||||
for details.
|
||||
-->
|
||||
<data-extraction-rules>
|
||||
<cloud-backup>
|
||||
<!-- TODO: Use <include> and <exclude> to control what is backed up.
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
-->
|
||||
</cloud-backup>
|
||||
<!--
|
||||
<device-transfer>
|
||||
<include .../>
|
||||
<exclude .../>
|
||||
</device-transfer>
|
||||
-->
|
||||
</data-extraction-rules>
|
||||
26
build.gradle
Normal file
@ -0,0 +1,26 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url 'https://maven.fabric.io/public' }
|
||||
maven { url 'https://plugins.gradle.org/m2/' }
|
||||
maven { url 'https://dl.bintray.com/kotlin/kotlin-eap' }
|
||||
maven { url "https://maven.mozilla.org/maven2/" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.github.kezong:fat-aar:1.3.6'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.20"
|
||||
classpath 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:3.1.1'
|
||||
classpath "org.jacoco:org.jacoco.core:0.8.7"
|
||||
}
|
||||
}
|
||||
|
||||
plugins {
|
||||
id 'com.android.application' version '7.4.2' apply false
|
||||
id 'org.jetbrains.kotlin.android' version '1.6.20' apply false
|
||||
}
|
||||
|
||||
4
gradle.properties
Normal file
@ -0,0 +1,4 @@
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
android.useAndroidX=true
|
||||
kotlin.code.style=official
|
||||
android.nonTransitiveRClass=true
|
||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Mon Aug 07 18:20:51 CST 2023
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
185
gradlew
vendored
Executable file
@ -0,0 +1,185 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
#
|
||||
# Copyright 2015 the original author or authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=`expr $i + 1`
|
||||
done
|
||||
case $i in
|
||||
0) set -- ;;
|
||||
1) set -- "$args0" ;;
|
||||
2) set -- "$args0" "$args1" ;;
|
||||
3) set -- "$args0" "$args1" "$args2" ;;
|
||||
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=`save "$@"`
|
||||
|
||||
# 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"
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
89
gradlew.bat
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
22
settings.gradle
Normal file
@ -0,0 +1,22 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url "https://maven.mozilla.org/maven2/" }
|
||||
}
|
||||
}
|
||||
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
maven { url "https://maven.mozilla.org/maven2/" }
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "Power"
|
||||
include ':app'
|
||||