commit 0214fb09edd46d1294c9b60138b05bdcaa990e0a Author: yezhiqiu <983577727@qq.com> Date: Tue Sep 30 10:01:20 2025 +0800 first commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -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 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml new file mode 100644 index 0000000..4a53bee --- /dev/null +++ b/.idea/AndroidProjectSystem.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..1c5ab05 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..dea3418 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/.idea/migrations.xml b/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..74dd639 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..16660f1 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,17 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/FastBleLib/build.gradle b/FastBleLib/build.gradle new file mode 100644 index 0000000..56a2cf3 --- /dev/null +++ b/FastBleLib/build.gradle @@ -0,0 +1,31 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 31 +// buildToolsVersion "30.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 31 + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + namespace 'com.clj.fastble' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) +} + +task makeAAR(type: Copy) { + from('build/outputs/aar/') + into('build/aarFloder/') + include('FastBleLib-release.aar') + rename('FastBleLib-release.aar', 'FastBLE-' + '"2.4.0".aar') +} +makeAAR.dependsOn(build) diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/results.bin b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/results.bin new file mode 100644 index 0000000..7ed749e --- /dev/null +++ b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/results.bin @@ -0,0 +1 @@ +o/bundleLibRuntimeToDirDebug diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/BleManager$BleManagerHolder.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/BleManager$BleManagerHolder.dex new file mode 100644 index 0000000..5b9949e Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/BleManager$BleManagerHolder.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/BleManager.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/BleManager.dex new file mode 100644 index 0000000..658d608 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/BleManager.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$1.dex new file mode 100644 index 0000000..36f7742 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$LastState.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$LastState.dex new file mode 100644 index 0000000..fdcad24 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$LastState.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.dex new file mode 100644 index 0000000..3c5c0ef Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth.dex new file mode 100644 index 0000000..bad76b2 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleBluetooth.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleConnector$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleConnector$1.dex new file mode 100644 index 0000000..e812710 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleConnector$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleConnector.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleConnector.dex new file mode 100644 index 0000000..359f460 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/BleConnector.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/MultipleBluetoothController$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/MultipleBluetoothController$1.dex new file mode 100644 index 0000000..cb27e26 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/MultipleBluetoothController$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/MultipleBluetoothController.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/MultipleBluetoothController.dex new file mode 100644 index 0000000..2f616ba Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/MultipleBluetoothController.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter$1.dex new file mode 100644 index 0000000..6344f75 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter$2.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter$2.dex new file mode 100644 index 0000000..a5db251 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter$2.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter.dex new file mode 100644 index 0000000..67fc8be Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/bluetooth/SplitWriter.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleBaseCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleBaseCallback.dex new file mode 100644 index 0000000..33b2d4d Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleBaseCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleGattCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleGattCallback.dex new file mode 100644 index 0000000..3ccb93a Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleGattCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleIndicateCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleIndicateCallback.dex new file mode 100644 index 0000000..3a4fe85 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleIndicateCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleMtuChangedCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleMtuChangedCallback.dex new file mode 100644 index 0000000..de9da3d Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleMtuChangedCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleNotifyCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleNotifyCallback.dex new file mode 100644 index 0000000..2eb9c42 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleNotifyCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleReadCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleReadCallback.dex new file mode 100644 index 0000000..a51bbbb Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleReadCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleRssiCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleRssiCallback.dex new file mode 100644 index 0000000..0f63787 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleRssiCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanAndConnectCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanAndConnectCallback.dex new file mode 100644 index 0000000..b80d373 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanAndConnectCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanCallback.dex new file mode 100644 index 0000000..d53eb01 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanPresenterImp.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanPresenterImp.dex new file mode 100644 index 0000000..9c30da3 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleScanPresenterImp.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleWriteCallback.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleWriteCallback.dex new file mode 100644 index 0000000..314a2f6 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/callback/BleWriteCallback.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleConnectStateParameter.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleConnectStateParameter.dex new file mode 100644 index 0000000..9566d14 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleConnectStateParameter.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleDevice$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleDevice$1.dex new file mode 100644 index 0000000..13b1c79 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleDevice$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleDevice.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleDevice.dex new file mode 100644 index 0000000..896a768 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleDevice.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleMsg.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleMsg.dex new file mode 100644 index 0000000..5696358 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleMsg.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleScanState.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleScanState.dex new file mode 100644 index 0000000..fb61059 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleScanState.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleWriteState.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleWriteState.dex new file mode 100644 index 0000000..975e19c Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/data/BleWriteState.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/BleException.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/BleException.dex new file mode 100644 index 0000000..a06dafd Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/BleException.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/ConnectException.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/ConnectException.dex new file mode 100644 index 0000000..30f720b Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/ConnectException.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/GattException.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/GattException.dex new file mode 100644 index 0000000..c3179cf Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/GattException.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/OtherException.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/OtherException.dex new file mode 100644 index 0000000..d07a739 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/OtherException.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/TimeoutException.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/TimeoutException.dex new file mode 100644 index 0000000..8a78f6d Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/exception/TimeoutException.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$1.dex new file mode 100644 index 0000000..a6cad62 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$2.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$2.dex new file mode 100644 index 0000000..0d1b5c0 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$2.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$3.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$3.dex new file mode 100644 index 0000000..71cdab7 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$3.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$4.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$4.dex new file mode 100644 index 0000000..4480a2f Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$4.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$5.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$5.dex new file mode 100644 index 0000000..7bd58d2 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$5.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$6.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$6.dex new file mode 100644 index 0000000..62850b5 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$6.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$ScanHandler.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$ScanHandler.dex new file mode 100644 index 0000000..f6d9858 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter$ScanHandler.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter.dex new file mode 100644 index 0000000..41d26c4 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanPresenter.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanRuleConfig$Builder.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanRuleConfig$Builder.dex new file mode 100644 index 0000000..3466afb Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanRuleConfig$Builder.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanRuleConfig.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanRuleConfig.dex new file mode 100644 index 0000000..7324360 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanRuleConfig.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$1$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$1$1.dex new file mode 100644 index 0000000..7bb8bf4 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$1$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$1.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$1.dex new file mode 100644 index 0000000..ce818fd Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$1.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$BleScannerHolder.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$BleScannerHolder.dex new file mode 100644 index 0000000..b460735 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner$BleScannerHolder.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner.dex new file mode 100644 index 0000000..544dd87 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/scan/BleScanner.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/BleLog.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/BleLog.dex new file mode 100644 index 0000000..c5304f4 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/BleLog.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/BleLruHashMap.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/BleLruHashMap.dex new file mode 100644 index 0000000..c25b40a Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/BleLruHashMap.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/HexUtil.dex b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/HexUtil.dex new file mode 100644 index 0000000..ba6ac28 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/bundleLibRuntimeToDirDebug_dex/com/clj/fastble/utils/HexUtil.dex differ diff --git a/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin new file mode 100644 index 0000000..601f245 Binary files /dev/null and b/FastBleLib/build/.transforms/2f4d96d8815bdfa87d85c8f08c4f3592/transformed/bundleLibRuntimeToDirDebug/desugar_graph.bin differ diff --git a/FastBleLib/build/.transforms/3c80c0ecb176ee6895d12ab1dcd92718/results.bin b/FastBleLib/build/.transforms/3c80c0ecb176ee6895d12ab1dcd92718/results.bin new file mode 100644 index 0000000..0d259dd --- /dev/null +++ b/FastBleLib/build/.transforms/3c80c0ecb176ee6895d12ab1dcd92718/results.bin @@ -0,0 +1 @@ +o/classes diff --git a/FastBleLib/build/.transforms/3c80c0ecb176ee6895d12ab1dcd92718/transformed/classes/classes_dex/classes.dex b/FastBleLib/build/.transforms/3c80c0ecb176ee6895d12ab1dcd92718/transformed/classes/classes_dex/classes.dex new file mode 100644 index 0000000..039160d Binary files /dev/null and b/FastBleLib/build/.transforms/3c80c0ecb176ee6895d12ab1dcd92718/transformed/classes/classes_dex/classes.dex differ diff --git a/FastBleLib/build/.transforms/49adf8ea3a69f71ccfa8eaab736f5d82/results.bin b/FastBleLib/build/.transforms/49adf8ea3a69f71ccfa8eaab736f5d82/results.bin new file mode 100644 index 0000000..0d259dd --- /dev/null +++ b/FastBleLib/build/.transforms/49adf8ea3a69f71ccfa8eaab736f5d82/results.bin @@ -0,0 +1 @@ +o/classes diff --git a/FastBleLib/build/.transforms/49adf8ea3a69f71ccfa8eaab736f5d82/transformed/classes/classes_dex/classes.dex b/FastBleLib/build/.transforms/49adf8ea3a69f71ccfa8eaab736f5d82/transformed/classes/classes_dex/classes.dex new file mode 100644 index 0000000..fe27d16 Binary files /dev/null and b/FastBleLib/build/.transforms/49adf8ea3a69f71ccfa8eaab736f5d82/transformed/classes/classes_dex/classes.dex differ diff --git a/FastBleLib/build/.transforms/c826ccda5a6b4aaea2cfe5c0a041266f/results.bin b/FastBleLib/build/.transforms/c826ccda5a6b4aaea2cfe5c0a041266f/results.bin new file mode 100644 index 0000000..0d259dd --- /dev/null +++ b/FastBleLib/build/.transforms/c826ccda5a6b4aaea2cfe5c0a041266f/results.bin @@ -0,0 +1 @@ +o/classes diff --git a/FastBleLib/build/.transforms/c826ccda5a6b4aaea2cfe5c0a041266f/transformed/classes/classes_dex/classes.dex b/FastBleLib/build/.transforms/c826ccda5a6b4aaea2cfe5c0a041266f/transformed/classes/classes_dex/classes.dex new file mode 100644 index 0000000..79983d5 Binary files /dev/null and b/FastBleLib/build/.transforms/c826ccda5a6b4aaea2cfe5c0a041266f/transformed/classes/classes_dex/classes.dex differ diff --git a/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml new file mode 100644 index 0000000..e00e686 --- /dev/null +++ b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json new file mode 100644 index 0000000..b1b4462 --- /dev/null +++ b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/debug/processDebugManifest/aapt/output-metadata.json @@ -0,0 +1,18 @@ +{ + "version": 3, + "artifactType": { + "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", + "kind": "Directory" + }, + "applicationId": "com.clj.fastble", + "variantName": "debug", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "outputFile": "AndroidManifest.xml" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/AndroidManifest.xml b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/AndroidManifest.xml new file mode 100644 index 0000000..e00e686 --- /dev/null +++ b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/output-metadata.json b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/output-metadata.json new file mode 100644 index 0000000..fa42747 --- /dev/null +++ b/FastBleLib/build/intermediates/aapt_friendly_merged_manifests/release/processReleaseManifest/aapt/output-metadata.json @@ -0,0 +1,18 @@ +{ + "version": 3, + "artifactType": { + "type": "AAPT_FRIENDLY_MERGED_MANIFESTS", + "kind": "Directory" + }, + "applicationId": "com.clj.fastble", + "variantName": "release", + "elements": [ + { + "type": "SINGLE", + "filters": [], + "attributes": [], + "outputFile": "AndroidManifest.xml" + } + ], + "elementType": "File" +} \ No newline at end of file diff --git a/FastBleLib/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties b/FastBleLib/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties new file mode 100644 index 0000000..1211b1e --- /dev/null +++ b/FastBleLib/build/intermediates/aar_metadata/debug/writeDebugAarMetadata/aar-metadata.properties @@ -0,0 +1,6 @@ +aarFormatVersion=1.0 +aarMetadataVersion=1.0 +minCompileSdk=1 +minCompileSdkExtension=0 +minAndroidGradlePluginVersion=1.0.0 +coreLibraryDesugaringEnabled=false diff --git a/FastBleLib/build/intermediates/aar_metadata/release/writeReleaseAarMetadata/aar-metadata.properties b/FastBleLib/build/intermediates/aar_metadata/release/writeReleaseAarMetadata/aar-metadata.properties new file mode 100644 index 0000000..1211b1e --- /dev/null +++ b/FastBleLib/build/intermediates/aar_metadata/release/writeReleaseAarMetadata/aar-metadata.properties @@ -0,0 +1,6 @@ +aarFormatVersion=1.0 +aarMetadataVersion=1.0 +minCompileSdk=1 +minCompileSdkExtension=0 +minAndroidGradlePluginVersion=1.0.0 +coreLibraryDesugaringEnabled=false diff --git a/FastBleLib/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json b/FastBleLib/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/FastBleLib/build/intermediates/annotation_processor_list/debug/javaPreCompileDebug/annotationProcessors.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/FastBleLib/build/intermediates/annotation_processor_list/release/javaPreCompileRelease/annotationProcessors.json b/FastBleLib/build/intermediates/annotation_processor_list/release/javaPreCompileRelease/annotationProcessors.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/FastBleLib/build/intermediates/annotation_processor_list/release/javaPreCompileRelease/annotationProcessors.json @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/FastBleLib/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar b/FastBleLib/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar new file mode 100644 index 0000000..1da2404 Binary files /dev/null and b/FastBleLib/build/intermediates/compile_library_classes_jar/debug/bundleLibCompileToJarDebug/classes.jar differ diff --git a/FastBleLib/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar b/FastBleLib/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar new file mode 100644 index 0000000..1da2404 Binary files /dev/null and b/FastBleLib/build/intermediates/compile_library_classes_jar/release/bundleLibCompileToJarRelease/classes.jar differ diff --git a/FastBleLib/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar b/FastBleLib/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar new file mode 100644 index 0000000..35c7129 Binary files /dev/null and b/FastBleLib/build/intermediates/compile_r_class_jar/debug/generateDebugRFile/R.jar differ diff --git a/FastBleLib/build/intermediates/compile_r_class_jar/release/generateReleaseRFile/R.jar b/FastBleLib/build/intermediates/compile_r_class_jar/release/generateReleaseRFile/R.jar new file mode 100644 index 0000000..35c7129 Binary files /dev/null and b/FastBleLib/build/intermediates/compile_r_class_jar/release/generateReleaseRFile/R.jar differ diff --git a/FastBleLib/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt b/FastBleLib/build/intermediates/compile_symbol_list/debug/generateDebugRFile/R.txt new file mode 100644 index 0000000..e69de29 diff --git a/FastBleLib/build/intermediates/compile_symbol_list/release/generateReleaseRFile/R.txt b/FastBleLib/build/intermediates/compile_symbol_list/release/generateReleaseRFile/R.txt new file mode 100644 index 0000000..e69de29 diff --git a/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties b/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties new file mode 100644 index 0000000..05f1889 --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/compile-file-map.properties @@ -0,0 +1 @@ +#Mon Sep 29 15:22:53 CST 2025 diff --git a/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/merger.xml b/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/merger.xml new file mode 100644 index 0000000..37b1420 --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/debug/packageDebugResources/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/mergeDebugAssets/merger.xml b/FastBleLib/build/intermediates/incremental/mergeDebugAssets/merger.xml new file mode 100644 index 0000000..376d3ab --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/mergeDebugAssets/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml b/FastBleLib/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml new file mode 100644 index 0000000..0bf21ef --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/mergeDebugJniLibFolders/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/mergeDebugShaders/merger.xml b/FastBleLib/build/intermediates/incremental/mergeDebugShaders/merger.xml new file mode 100644 index 0000000..1776399 --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/mergeDebugShaders/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/mergeReleaseAssets/merger.xml b/FastBleLib/build/intermediates/incremental/mergeReleaseAssets/merger.xml new file mode 100644 index 0000000..db36b46 --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/mergeReleaseAssets/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml b/FastBleLib/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml new file mode 100644 index 0000000..55e1c9b --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/mergeReleaseJniLibFolders/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/mergeReleaseShaders/merger.xml b/FastBleLib/build/intermediates/incremental/mergeReleaseShaders/merger.xml new file mode 100644 index 0000000..d51b877 --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/mergeReleaseShaders/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties b/FastBleLib/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties new file mode 100644 index 0000000..6eb27cd --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/release/packageReleaseResources/compile-file-map.properties @@ -0,0 +1 @@ +#Tue Sep 09 14:41:15 CST 2025 diff --git a/FastBleLib/build/intermediates/incremental/release/packageReleaseResources/merger.xml b/FastBleLib/build/intermediates/incremental/release/packageReleaseResources/merger.xml new file mode 100644 index 0000000..795eef3 --- /dev/null +++ b/FastBleLib/build/intermediates/incremental/release/packageReleaseResources/merger.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/BleManager$BleManagerHolder.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/BleManager$BleManagerHolder.class new file mode 100644 index 0000000..95ac447 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/BleManager$BleManagerHolder.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/BleManager.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/BleManager.class new file mode 100644 index 0000000..9c16ab8 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/BleManager.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$1.class new file mode 100644 index 0000000..d75fc73 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$LastState.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$LastState.class new file mode 100644 index 0000000..25df41c Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$LastState.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class new file mode 100644 index 0000000..6601ed8 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth.class new file mode 100644 index 0000000..a4a991a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector$1.class new file mode 100644 index 0000000..3dd3925 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector.class new file mode 100644 index 0000000..b95be0a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class new file mode 100644 index 0000000..773a59d Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController.class new file mode 100644 index 0000000..9507406 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$1.class new file mode 100644 index 0000000..21762b3 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$2.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$2.class new file mode 100644 index 0000000..1ee6499 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$2.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter.class new file mode 100644 index 0000000..a96ba5b Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleBaseCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleBaseCallback.class new file mode 100644 index 0000000..7d5c801 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleBaseCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleGattCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleGattCallback.class new file mode 100644 index 0000000..3430e03 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleGattCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleIndicateCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleIndicateCallback.class new file mode 100644 index 0000000..1a284d9 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleIndicateCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleMtuChangedCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleMtuChangedCallback.class new file mode 100644 index 0000000..d25ef63 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleMtuChangedCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleNotifyCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleNotifyCallback.class new file mode 100644 index 0000000..c3788ac Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleNotifyCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleReadCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleReadCallback.class new file mode 100644 index 0000000..7e867ec Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleReadCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleRssiCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleRssiCallback.class new file mode 100644 index 0000000..e456b1a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleRssiCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanAndConnectCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanAndConnectCallback.class new file mode 100644 index 0000000..a0bde2e Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanAndConnectCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanCallback.class new file mode 100644 index 0000000..f1d6bd5 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanPresenterImp.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanPresenterImp.class new file mode 100644 index 0000000..169c27a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleScanPresenterImp.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleWriteCallback.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleWriteCallback.class new file mode 100644 index 0000000..ce9b5c4 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/callback/BleWriteCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleConnectStateParameter.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleConnectStateParameter.class new file mode 100644 index 0000000..16fb2aa Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleConnectStateParameter.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleDevice$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleDevice$1.class new file mode 100644 index 0000000..8bc2acd Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleDevice$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleDevice.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleDevice.class new file mode 100644 index 0000000..bb58e6d Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleDevice.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleMsg.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleMsg.class new file mode 100644 index 0000000..4aae379 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleMsg.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleScanState.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleScanState.class new file mode 100644 index 0000000..3f02e22 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleScanState.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleWriteState.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleWriteState.class new file mode 100644 index 0000000..46d6969 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/data/BleWriteState.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/BleException.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/BleException.class new file mode 100644 index 0000000..8194904 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/BleException.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/ConnectException.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/ConnectException.class new file mode 100644 index 0000000..022b5e8 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/ConnectException.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/GattException.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/GattException.class new file mode 100644 index 0000000..0d1a068 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/GattException.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/OtherException.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/OtherException.class new file mode 100644 index 0000000..09f4849 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/OtherException.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/TimeoutException.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/TimeoutException.class new file mode 100644 index 0000000..ee2989f Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/exception/TimeoutException.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$1.class new file mode 100644 index 0000000..b8b5cdb Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$2.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$2.class new file mode 100644 index 0000000..068b643 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$2.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$3.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$3.class new file mode 100644 index 0000000..c9d1602 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$3.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$4.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$4.class new file mode 100644 index 0000000..ec8c486 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$4.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$5.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$5.class new file mode 100644 index 0000000..69ea040 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$5.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$6.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$6.class new file mode 100644 index 0000000..7fdc812 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$6.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class new file mode 100644 index 0000000..6596459 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter.class new file mode 100644 index 0000000..e1bd61f Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig$Builder.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig$Builder.class new file mode 100644 index 0000000..b93ebc4 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig$Builder.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig.class new file mode 100644 index 0000000..0c1232b Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1$1.class new file mode 100644 index 0000000..118cdbe Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1.class new file mode 100644 index 0000000..5fa79ff Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$BleScannerHolder.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$BleScannerHolder.class new file mode 100644 index 0000000..50e2d94 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$BleScannerHolder.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner.class new file mode 100644 index 0000000..4a53377 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/scan/BleScanner.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/BleLog.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/BleLog.class new file mode 100644 index 0000000..f91fd0e Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/BleLog.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/BleLruHashMap.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/BleLruHashMap.class new file mode 100644 index 0000000..ffd8f3d Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/BleLruHashMap.class differ diff --git a/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/HexUtil.class b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/HexUtil.class new file mode 100644 index 0000000..6cd82ab Binary files /dev/null and b/FastBleLib/build/intermediates/javac/debug/compileDebugJavaWithJavac/classes/com/clj/fastble/utils/HexUtil.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/BleManager$BleManagerHolder.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/BleManager$BleManagerHolder.class new file mode 100644 index 0000000..95ac447 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/BleManager$BleManagerHolder.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/BleManager.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/BleManager.class new file mode 100644 index 0000000..9c16ab8 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/BleManager.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$1.class new file mode 100644 index 0000000..d75fc73 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$LastState.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$LastState.class new file mode 100644 index 0000000..25df41c Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$LastState.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class new file mode 100644 index 0000000..6601ed8 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth.class new file mode 100644 index 0000000..a4a991a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleBluetooth.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector$1.class new file mode 100644 index 0000000..3dd3925 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector.class new file mode 100644 index 0000000..b95be0a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/BleConnector.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class new file mode 100644 index 0000000..773a59d Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController.class new file mode 100644 index 0000000..9507406 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/MultipleBluetoothController.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$1.class new file mode 100644 index 0000000..21762b3 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$2.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$2.class new file mode 100644 index 0000000..1ee6499 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter$2.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter.class new file mode 100644 index 0000000..a96ba5b Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/bluetooth/SplitWriter.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleBaseCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleBaseCallback.class new file mode 100644 index 0000000..7d5c801 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleBaseCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleGattCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleGattCallback.class new file mode 100644 index 0000000..3430e03 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleGattCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleIndicateCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleIndicateCallback.class new file mode 100644 index 0000000..1a284d9 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleIndicateCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleMtuChangedCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleMtuChangedCallback.class new file mode 100644 index 0000000..d25ef63 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleMtuChangedCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleNotifyCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleNotifyCallback.class new file mode 100644 index 0000000..c3788ac Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleNotifyCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleReadCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleReadCallback.class new file mode 100644 index 0000000..7e867ec Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleReadCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleRssiCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleRssiCallback.class new file mode 100644 index 0000000..e456b1a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleRssiCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanAndConnectCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanAndConnectCallback.class new file mode 100644 index 0000000..a0bde2e Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanAndConnectCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanCallback.class new file mode 100644 index 0000000..f1d6bd5 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanPresenterImp.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanPresenterImp.class new file mode 100644 index 0000000..169c27a Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleScanPresenterImp.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleWriteCallback.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleWriteCallback.class new file mode 100644 index 0000000..ce9b5c4 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/callback/BleWriteCallback.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleConnectStateParameter.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleConnectStateParameter.class new file mode 100644 index 0000000..16fb2aa Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleConnectStateParameter.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleDevice$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleDevice$1.class new file mode 100644 index 0000000..8bc2acd Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleDevice$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleDevice.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleDevice.class new file mode 100644 index 0000000..bb58e6d Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleDevice.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleMsg.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleMsg.class new file mode 100644 index 0000000..4aae379 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleMsg.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleScanState.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleScanState.class new file mode 100644 index 0000000..3f02e22 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleScanState.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleWriteState.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleWriteState.class new file mode 100644 index 0000000..46d6969 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/data/BleWriteState.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/BleException.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/BleException.class new file mode 100644 index 0000000..8194904 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/BleException.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/ConnectException.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/ConnectException.class new file mode 100644 index 0000000..022b5e8 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/ConnectException.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/GattException.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/GattException.class new file mode 100644 index 0000000..0d1a068 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/GattException.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/OtherException.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/OtherException.class new file mode 100644 index 0000000..09f4849 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/OtherException.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/TimeoutException.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/TimeoutException.class new file mode 100644 index 0000000..ee2989f Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/exception/TimeoutException.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$1.class new file mode 100644 index 0000000..b8b5cdb Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$2.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$2.class new file mode 100644 index 0000000..068b643 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$2.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$3.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$3.class new file mode 100644 index 0000000..c9d1602 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$3.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$4.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$4.class new file mode 100644 index 0000000..ec8c486 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$4.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$5.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$5.class new file mode 100644 index 0000000..69ea040 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$5.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$6.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$6.class new file mode 100644 index 0000000..7fdc812 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$6.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class new file mode 100644 index 0000000..6596459 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter.class new file mode 100644 index 0000000..e1bd61f Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanPresenter.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig$Builder.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig$Builder.class new file mode 100644 index 0000000..b93ebc4 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig$Builder.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig.class new file mode 100644 index 0000000..0c1232b Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanRuleConfig.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1$1.class new file mode 100644 index 0000000..118cdbe Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1.class new file mode 100644 index 0000000..5fa79ff Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$1.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$BleScannerHolder.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$BleScannerHolder.class new file mode 100644 index 0000000..50e2d94 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner$BleScannerHolder.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner.class new file mode 100644 index 0000000..4a53377 Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/scan/BleScanner.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/BleLog.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/BleLog.class new file mode 100644 index 0000000..f91fd0e Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/BleLog.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/BleLruHashMap.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/BleLruHashMap.class new file mode 100644 index 0000000..ffd8f3d Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/BleLruHashMap.class differ diff --git a/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/HexUtil.class b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/HexUtil.class new file mode 100644 index 0000000..6cd82ab Binary files /dev/null and b/FastBleLib/build/intermediates/javac/release/compileReleaseJavaWithJavac/classes/com/clj/fastble/utils/HexUtil.class differ diff --git a/FastBleLib/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt b/FastBleLib/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt new file mode 100644 index 0000000..78ac5b8 --- /dev/null +++ b/FastBleLib/build/intermediates/local_only_symbol_list/debug/parseDebugLocalResources/R-def.txt @@ -0,0 +1,2 @@ +R_DEF: Internal format may change without notice +local diff --git a/FastBleLib/build/intermediates/local_only_symbol_list/release/parseReleaseLocalResources/R-def.txt b/FastBleLib/build/intermediates/local_only_symbol_list/release/parseReleaseLocalResources/R-def.txt new file mode 100644 index 0000000..78ac5b8 --- /dev/null +++ b/FastBleLib/build/intermediates/local_only_symbol_list/release/parseReleaseLocalResources/R-def.txt @@ -0,0 +1,2 @@ +R_DEF: Internal format may change without notice +local diff --git a/FastBleLib/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt b/FastBleLib/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt new file mode 100644 index 0000000..9530167 --- /dev/null +++ b/FastBleLib/build/intermediates/manifest_merge_blame_file/debug/processDebugManifest/manifest-merger-blame-debug-report.txt @@ -0,0 +1,20 @@ +1 +2 +4 +5 +6 +7 +7-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:5-68 +7-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:22-65 +8 +8-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:5-74 +8-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:22-71 +9 +9-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:5-81 +9-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:22-78 +10 +10-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:5-79 +10-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:22-76 +11 +12 diff --git a/FastBleLib/build/intermediates/manifest_merge_blame_file/release/processReleaseManifest/manifest-merger-blame-release-report.txt b/FastBleLib/build/intermediates/manifest_merge_blame_file/release/processReleaseManifest/manifest-merger-blame-release-report.txt new file mode 100644 index 0000000..9530167 --- /dev/null +++ b/FastBleLib/build/intermediates/manifest_merge_blame_file/release/processReleaseManifest/manifest-merger-blame-release-report.txt @@ -0,0 +1,20 @@ +1 +2 +4 +5 +6 +7 +7-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:5-68 +7-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:22-65 +8 +8-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:5-74 +8-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:22-71 +9 +9-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:5-81 +9-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:22-78 +10 +10-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:5-79 +10-->E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:22-76 +11 +12 diff --git a/FastBleLib/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml b/FastBleLib/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml new file mode 100644 index 0000000..e00e686 --- /dev/null +++ b/FastBleLib/build/intermediates/merged_manifest/debug/processDebugManifest/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/merged_manifest/release/processReleaseManifest/AndroidManifest.xml b/FastBleLib/build/intermediates/merged_manifest/release/processReleaseManifest/AndroidManifest.xml new file mode 100644 index 0000000..e00e686 --- /dev/null +++ b/FastBleLib/build/intermediates/merged_manifest/release/processReleaseManifest/AndroidManifest.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/FastBleLib/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json b/FastBleLib/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/FastBleLib/build/intermediates/navigation_json/debug/extractDeepLinksDebug/navigation.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/FastBleLib/build/intermediates/navigation_json/release/extractDeepLinksRelease/navigation.json b/FastBleLib/build/intermediates/navigation_json/release/extractDeepLinksRelease/navigation.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/FastBleLib/build/intermediates/navigation_json/release/extractDeepLinksRelease/navigation.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/FastBleLib/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt b/FastBleLib/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt new file mode 100644 index 0000000..08f4ebe --- /dev/null +++ b/FastBleLib/build/intermediates/nested_resources_validation_report/debug/generateDebugResources/nestedResourcesValidationReport.txt @@ -0,0 +1 @@ +0 Warning/Error \ No newline at end of file diff --git a/FastBleLib/build/intermediates/nested_resources_validation_report/release/generateReleaseResources/nestedResourcesValidationReport.txt b/FastBleLib/build/intermediates/nested_resources_validation_report/release/generateReleaseResources/nestedResourcesValidationReport.txt new file mode 100644 index 0000000..08f4ebe --- /dev/null +++ b/FastBleLib/build/intermediates/nested_resources_validation_report/release/generateReleaseResources/nestedResourcesValidationReport.txt @@ -0,0 +1 @@ +0 Warning/Error \ No newline at end of file diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/BleManager$BleManagerHolder.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/BleManager$BleManagerHolder.class new file mode 100644 index 0000000..95ac447 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/BleManager$BleManagerHolder.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/BleManager.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/BleManager.class new file mode 100644 index 0000000..9c16ab8 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/BleManager.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$1.class new file mode 100644 index 0000000..d75fc73 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$LastState.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$LastState.class new file mode 100644 index 0000000..25df41c Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$LastState.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class new file mode 100644 index 0000000..6601ed8 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth$MainHandler.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth.class new file mode 100644 index 0000000..a4a991a Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleBluetooth.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleConnector$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleConnector$1.class new file mode 100644 index 0000000..3dd3925 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleConnector$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleConnector.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleConnector.class new file mode 100644 index 0000000..b95be0a Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/BleConnector.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class new file mode 100644 index 0000000..773a59d Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/MultipleBluetoothController$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/MultipleBluetoothController.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/MultipleBluetoothController.class new file mode 100644 index 0000000..9507406 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/MultipleBluetoothController.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter$1.class new file mode 100644 index 0000000..21762b3 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter$2.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter$2.class new file mode 100644 index 0000000..1ee6499 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter$2.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter.class new file mode 100644 index 0000000..a96ba5b Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/bluetooth/SplitWriter.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleBaseCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleBaseCallback.class new file mode 100644 index 0000000..7d5c801 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleBaseCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleGattCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleGattCallback.class new file mode 100644 index 0000000..3430e03 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleGattCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleIndicateCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleIndicateCallback.class new file mode 100644 index 0000000..1a284d9 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleIndicateCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleMtuChangedCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleMtuChangedCallback.class new file mode 100644 index 0000000..d25ef63 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleMtuChangedCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleNotifyCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleNotifyCallback.class new file mode 100644 index 0000000..c3788ac Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleNotifyCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleReadCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleReadCallback.class new file mode 100644 index 0000000..7e867ec Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleReadCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleRssiCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleRssiCallback.class new file mode 100644 index 0000000..e456b1a Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleRssiCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanAndConnectCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanAndConnectCallback.class new file mode 100644 index 0000000..a0bde2e Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanAndConnectCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanCallback.class new file mode 100644 index 0000000..f1d6bd5 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanPresenterImp.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanPresenterImp.class new file mode 100644 index 0000000..169c27a Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleScanPresenterImp.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleWriteCallback.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleWriteCallback.class new file mode 100644 index 0000000..ce9b5c4 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/callback/BleWriteCallback.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleConnectStateParameter.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleConnectStateParameter.class new file mode 100644 index 0000000..16fb2aa Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleConnectStateParameter.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleDevice$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleDevice$1.class new file mode 100644 index 0000000..8bc2acd Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleDevice$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleDevice.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleDevice.class new file mode 100644 index 0000000..bb58e6d Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleDevice.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleMsg.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleMsg.class new file mode 100644 index 0000000..4aae379 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleMsg.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleScanState.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleScanState.class new file mode 100644 index 0000000..3f02e22 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleScanState.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleWriteState.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleWriteState.class new file mode 100644 index 0000000..46d6969 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/data/BleWriteState.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/BleException.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/BleException.class new file mode 100644 index 0000000..8194904 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/BleException.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/ConnectException.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/ConnectException.class new file mode 100644 index 0000000..022b5e8 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/ConnectException.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/GattException.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/GattException.class new file mode 100644 index 0000000..0d1a068 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/GattException.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/OtherException.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/OtherException.class new file mode 100644 index 0000000..09f4849 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/OtherException.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/TimeoutException.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/TimeoutException.class new file mode 100644 index 0000000..ee2989f Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/exception/TimeoutException.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$1.class new file mode 100644 index 0000000..b8b5cdb Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$2.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$2.class new file mode 100644 index 0000000..068b643 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$2.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$3.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$3.class new file mode 100644 index 0000000..c9d1602 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$3.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$4.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$4.class new file mode 100644 index 0000000..ec8c486 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$4.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$5.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$5.class new file mode 100644 index 0000000..69ea040 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$5.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$6.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$6.class new file mode 100644 index 0000000..7fdc812 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$6.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class new file mode 100644 index 0000000..6596459 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter$ScanHandler.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter.class new file mode 100644 index 0000000..e1bd61f Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanPresenter.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanRuleConfig$Builder.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanRuleConfig$Builder.class new file mode 100644 index 0000000..b93ebc4 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanRuleConfig$Builder.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanRuleConfig.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanRuleConfig.class new file mode 100644 index 0000000..0c1232b Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanRuleConfig.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$1$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$1$1.class new file mode 100644 index 0000000..118cdbe Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$1$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$1.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$1.class new file mode 100644 index 0000000..5fa79ff Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$1.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$BleScannerHolder.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$BleScannerHolder.class new file mode 100644 index 0000000..50e2d94 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner$BleScannerHolder.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner.class new file mode 100644 index 0000000..4a53377 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/scan/BleScanner.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/BleLog.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/BleLog.class new file mode 100644 index 0000000..f91fd0e Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/BleLog.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/BleLruHashMap.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/BleLruHashMap.class new file mode 100644 index 0000000..ffd8f3d Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/BleLruHashMap.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/HexUtil.class b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/HexUtil.class new file mode 100644 index 0000000..6cd82ab Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_dir/debug/bundleLibRuntimeToDirDebug/com/clj/fastble/utils/HexUtil.class differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar b/FastBleLib/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar new file mode 100644 index 0000000..2bdaa48 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_jar/debug/bundleLibRuntimeToJarDebug/classes.jar differ diff --git a/FastBleLib/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar b/FastBleLib/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar new file mode 100644 index 0000000..2bdaa48 Binary files /dev/null and b/FastBleLib/build/intermediates/runtime_library_classes_jar/release/bundleLibRuntimeToJarRelease/classes.jar differ diff --git a/FastBleLib/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt b/FastBleLib/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt new file mode 100644 index 0000000..48a4452 --- /dev/null +++ b/FastBleLib/build/intermediates/symbol_list_with_package_name/debug/generateDebugRFile/package-aware-r.txt @@ -0,0 +1 @@ +com.clj.fastble diff --git a/FastBleLib/build/intermediates/symbol_list_with_package_name/release/generateReleaseRFile/package-aware-r.txt b/FastBleLib/build/intermediates/symbol_list_with_package_name/release/generateReleaseRFile/package-aware-r.txt new file mode 100644 index 0000000..48a4452 --- /dev/null +++ b/FastBleLib/build/intermediates/symbol_list_with_package_name/release/generateReleaseRFile/package-aware-r.txt @@ -0,0 +1 @@ +com.clj.fastble diff --git a/FastBleLib/build/outputs/logs/manifest-merger-debug-report.txt b/FastBleLib/build/outputs/logs/manifest-merger-debug-report.txt new file mode 100644 index 0000000..187eac0 --- /dev/null +++ b/FastBleLib/build/outputs/logs/manifest-merger-debug-report.txt @@ -0,0 +1,33 @@ +-- Merging decision tree log --- +manifest +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:1:1-9:12 +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:1:1-9:12 + package + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:2:5-30 + INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml + xmlns:android + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:1:11-69 +uses-permission#android.permission.BLUETOOTH +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:5-68 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:22-65 +uses-permission#android.permission.BLUETOOTH_ADMIN +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:5-74 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:22-71 +uses-permission#android.permission.ACCESS_COARSE_LOCATION +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:5-81 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:22-78 +uses-permission#android.permission.ACCESS_FINE_LOCATION +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:5-79 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:22-76 +uses-sdk +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml reason: use-sdk injection requested +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml + android:targetSdkVersion + INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml + android:minSdkVersion + INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml diff --git a/FastBleLib/build/outputs/logs/manifest-merger-release-report.txt b/FastBleLib/build/outputs/logs/manifest-merger-release-report.txt new file mode 100644 index 0000000..187eac0 --- /dev/null +++ b/FastBleLib/build/outputs/logs/manifest-merger-release-report.txt @@ -0,0 +1,33 @@ +-- Merging decision tree log --- +manifest +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:1:1-9:12 +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:1:1-9:12 + package + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:2:5-30 + INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml + xmlns:android + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:1:11-69 +uses-permission#android.permission.BLUETOOTH +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:5-68 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:4:22-65 +uses-permission#android.permission.BLUETOOTH_ADMIN +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:5-74 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:5:22-71 +uses-permission#android.permission.ACCESS_COARSE_LOCATION +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:5-81 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:6:22-78 +uses-permission#android.permission.ACCESS_FINE_LOCATION +ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:5-79 + android:name + ADDED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml:7:22-76 +uses-sdk +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml reason: use-sdk injection requested +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml +INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml + android:targetSdkVersion + INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml + android:minSdkVersion + INJECTED from E:\AndroidProject\LatestSdkProject\Tracker\FastBleLib\src\main\AndroidManifest.xml diff --git a/FastBleLib/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin b/FastBleLib/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin new file mode 100644 index 0000000..cd4b513 Binary files /dev/null and b/FastBleLib/build/tmp/compileDebugJavaWithJavac/previous-compilation-data.bin differ diff --git a/FastBleLib/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin b/FastBleLib/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin new file mode 100644 index 0000000..cd4b513 Binary files /dev/null and b/FastBleLib/build/tmp/compileReleaseJavaWithJavac/previous-compilation-data.bin differ diff --git a/FastBleLib/proguard-rules.pro b/FastBleLib/proguard-rules.pro new file mode 100644 index 0000000..00de6f8 --- /dev/null +++ b/FastBleLib/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in D:\development\AppData\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/FastBleLib/src/main/AndroidManifest.xml b/FastBleLib/src/main/AndroidManifest.xml new file mode 100644 index 0000000..3d3bdeb --- /dev/null +++ b/FastBleLib/src/main/AndroidManifest.xml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/FastBleLib/src/main/java/com/clj/fastble/BleManager.java b/FastBleLib/src/main/java/com/clj/fastble/BleManager.java new file mode 100644 index 0000000..a961b8c --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/BleManager.java @@ -0,0 +1,906 @@ +package com.clj.fastble; + +import android.annotation.TargetApi; +import android.app.Application; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattService; +import android.bluetooth.BluetoothManager; +import android.bluetooth.BluetoothProfile; +import android.bluetooth.le.ScanRecord; +import android.bluetooth.le.ScanResult; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Looper; + +import com.clj.fastble.bluetooth.BleBluetooth; +import com.clj.fastble.bluetooth.MultipleBluetoothController; +import com.clj.fastble.bluetooth.SplitWriter; +import com.clj.fastble.callback.BleGattCallback; +import com.clj.fastble.callback.BleIndicateCallback; +import com.clj.fastble.callback.BleMtuChangedCallback; +import com.clj.fastble.callback.BleNotifyCallback; +import com.clj.fastble.callback.BleReadCallback; +import com.clj.fastble.callback.BleRssiCallback; +import com.clj.fastble.callback.BleScanAndConnectCallback; +import com.clj.fastble.callback.BleScanCallback; +import com.clj.fastble.callback.BleWriteCallback; +import com.clj.fastble.data.BleDevice; +import com.clj.fastble.data.BleScanState; +import com.clj.fastble.exception.OtherException; +import com.clj.fastble.scan.BleScanRuleConfig; +import com.clj.fastble.scan.BleScanner; +import com.clj.fastble.utils.BleLog; + +import java.util.List; +import java.util.UUID; + +public class BleManager { + + private Application context; + private BleScanRuleConfig bleScanRuleConfig; + private BluetoothAdapter bluetoothAdapter; + private MultipleBluetoothController multipleBluetoothController; + private BluetoothManager bluetoothManager; + + public static final int DEFAULT_SCAN_TIME = 10000; + private static final int DEFAULT_MAX_MULTIPLE_DEVICE = 7; + private static final int DEFAULT_OPERATE_TIME = 5000; + private static final int DEFAULT_CONNECT_RETRY_COUNT = 0; + private static final int DEFAULT_CONNECT_RETRY_INTERVAL = 5000; + private static final int DEFAULT_MTU = 23; + private static final int DEFAULT_MAX_MTU = 512; + private static final int DEFAULT_WRITE_DATA_SPLIT_COUNT = 20; + private static final int DEFAULT_CONNECT_OVER_TIME = 10000; + + private int maxConnectCount = DEFAULT_MAX_MULTIPLE_DEVICE; + private int operateTimeout = DEFAULT_OPERATE_TIME; + private int reConnectCount = DEFAULT_CONNECT_RETRY_COUNT; + private long reConnectInterval = DEFAULT_CONNECT_RETRY_INTERVAL; + private int splitWriteNum = DEFAULT_WRITE_DATA_SPLIT_COUNT; + private long connectOverTime = DEFAULT_CONNECT_OVER_TIME; + + public static BleManager getInstance() { + return BleManagerHolder.sBleManager; + } + + private static class BleManagerHolder { + private static final BleManager sBleManager = new BleManager(); + } + + public void init(Application app) { + if (context == null && app != null) { + context = app; + if (isSupportBle()) { + bluetoothManager = + (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); + bluetoothAdapter = bluetoothManager.getAdapter(); + } else { + bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); + } + multipleBluetoothController = new MultipleBluetoothController(); + bleScanRuleConfig = new BleScanRuleConfig(); + } + } + + /** + * Get the Context + * + * @return + */ + public Context getContext() { + return context; + } + + /** + * Get the BluetoothManager + * + * @return + */ + public BluetoothManager getBluetoothManager() { + return bluetoothManager; + } + + /** + * Get the BluetoothAdapter + * + * @return + */ + public BluetoothAdapter getBluetoothAdapter() { + return bluetoothAdapter; + } + + /** + * get the ScanRuleConfig + * + * @return + */ + public BleScanRuleConfig getScanRuleConfig() { + return bleScanRuleConfig; + } + + /** + * Get the multiple Bluetooth Controller + * + * @return + */ + public MultipleBluetoothController getMultipleBluetoothController() { + return multipleBluetoothController; + } + + /** + * Configure scan and connection properties + * + * @param config + */ + public void initScanRule(BleScanRuleConfig config) { + this.bleScanRuleConfig = config; + } + + /** + * Get the maximum number of connections + * + * @return + */ + public int getMaxConnectCount() { + return maxConnectCount; + } + + /** + * Set the maximum number of connections + * + * @param count + * @return BleManager + */ + public BleManager setMaxConnectCount(int count) { + if (count > DEFAULT_MAX_MULTIPLE_DEVICE) + count = DEFAULT_MAX_MULTIPLE_DEVICE; + this.maxConnectCount = count; + return this; + } + + /** + * Get operate timeout + * + * @return + */ + public int getOperateTimeout() { + return operateTimeout; + } + + /** + * Set operate timeout + * + * @param count + * @return BleManager + */ + public BleManager setOperateTimeout(int count) { + this.operateTimeout = count; + return this; + } + + /** + * Get connect retry count + * + * @return + */ + public int getReConnectCount() { + return reConnectCount; + } + + /** + * Get connect retry interval + * + * @return + */ + public long getReConnectInterval() { + return reConnectInterval; + } + + /** + * Set connect retry count and interval + * + * @param count + * @return BleManager + */ + public BleManager setReConnectCount(int count) { + return setReConnectCount(count, DEFAULT_CONNECT_RETRY_INTERVAL); + } + + /** + * Set connect retry count and interval + * + * @param count + * @return BleManager + */ + public BleManager setReConnectCount(int count, long interval) { + if (count > 10) + count = 10; + if (interval < 0) + interval = 0; + this.reConnectCount = count; + this.reConnectInterval = interval; + return this; + } + + + /** + * Get operate split Write Num + * + * @return + */ + public int getSplitWriteNum() { + return splitWriteNum; + } + + /** + * Set split Writ eNum + * + * @param num + * @return BleManager + */ + public BleManager setSplitWriteNum(int num) { + if (num > 0) { + this.splitWriteNum = num; + } + return this; + } + + /** + * Get operate connect Over Time + * + * @return + */ + public long getConnectOverTime() { + return connectOverTime; + } + + /** + * Set connect Over Time + * + * @param time + * @return BleManager + */ + public BleManager setConnectOverTime(long time) { + if (time <= 0) { + time = 100; + } + this.connectOverTime = time; + return this; + } + + /** + * print log? + * + * @param enable + * @return BleManager + */ + public BleManager enableLog(boolean enable) { + BleLog.isPrint = enable; + return this; + } + + /** + * scan device around + * + * @param callback + */ + public void scan(BleScanCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleScanCallback can not be Null!"); + } + + if (!isBlueEnable()) { + BleLog.e("Bluetooth not enable!"); + callback.onScanStarted(false); + return; + } + + UUID[] serviceUuids = bleScanRuleConfig.getServiceUuids(); + String[] deviceNames = bleScanRuleConfig.getDeviceNames(); + String deviceMac = bleScanRuleConfig.getDeviceMac(); + boolean fuzzy = bleScanRuleConfig.isFuzzy(); + long timeOut = bleScanRuleConfig.getScanTimeOut(); + + BleScanner.getInstance() + .scan(serviceUuids, deviceNames, deviceMac, fuzzy, timeOut, callback); + } + + /** + * scan device then connect + * + * @param callback + */ + public void scanAndConnect(BleScanAndConnectCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleScanAndConnectCallback can not be Null!"); + } + + if (!isBlueEnable()) { + BleLog.e("Bluetooth not enable!"); + callback.onScanStarted(false); + return; + } + + UUID[] serviceUuids = bleScanRuleConfig.getServiceUuids(); + String[] deviceNames = bleScanRuleConfig.getDeviceNames(); + String deviceMac = bleScanRuleConfig.getDeviceMac(); + boolean fuzzy = bleScanRuleConfig.isFuzzy(); + long timeOut = bleScanRuleConfig.getScanTimeOut(); + + BleScanner.getInstance() + .scanAndConnect(serviceUuids, deviceNames, deviceMac, fuzzy, timeOut, callback); + } + + /** + * connect a known device + * + * @param bleDevice + * @param bleGattCallback + * @return + */ + public BluetoothGatt connect(BleDevice bleDevice, BleGattCallback bleGattCallback) { + if (bleGattCallback == null) { + throw new IllegalArgumentException("BleGattCallback can not be Null!"); + } + + if (!isBlueEnable()) { + BleLog.e("Bluetooth not enable!"); + bleGattCallback.onConnectFail(bleDevice, new OtherException("Bluetooth not enable!")); + return null; + } + + if (Looper.myLooper() == null || Looper.myLooper() != Looper.getMainLooper()) { + BleLog.w("Be careful: currentThread is not MainThread!"); + } + + if (bleDevice == null || bleDevice.getDevice() == null) { + bleGattCallback.onConnectFail(bleDevice, new OtherException("Not Found Device " + + "Exception Occurred!")); + } else { + BleBluetooth bleBluetooth = multipleBluetoothController.buildConnectingBle(bleDevice); + boolean autoConnect = bleScanRuleConfig.isAutoConnect(); + return bleBluetooth.connect(bleDevice, autoConnect, bleGattCallback); + } + + return null; + } + + /** + * connect a device through its mac without scan,whether or not it has been connected + * + * @param mac + * @param bleGattCallback + * @return + */ + public BluetoothGatt connect(String mac, BleGattCallback bleGattCallback) { + BluetoothDevice bluetoothDevice = getBluetoothAdapter().getRemoteDevice(mac); + BleDevice bleDevice = new BleDevice(bluetoothDevice, 0, null, 0); + return connect(bleDevice, bleGattCallback); + } + + + /** + * Cancel scan + */ + public void cancelScan() { + BleScanner.getInstance().stopLeScan(); + } + + /** + * notify + * + * @param bleDevice + * @param uuid_service + * @param uuid_notify + * @param callback + */ + public void notify(BleDevice bleDevice, String uuid_service, String uuid_notify, + BleNotifyCallback callback) { + notify(bleDevice, uuid_service, uuid_notify, false, callback); + } + + /** + * notify + * + * @param bleDevice + * @param uuid_service + * @param uuid_notify + * @param useCharacteristicDescriptor + * @param callback + */ + public void notify(BleDevice bleDevice, String uuid_service, String uuid_notify, + boolean useCharacteristicDescriptor, BleNotifyCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleNotifyCallback can not be Null!"); + } + + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onNotifyFailure(new OtherException("This device not connect!")); + } else { + bleBluetooth.newBleConnector().withUUIDString(uuid_service, uuid_notify) + .enableCharacteristicNotify(callback, uuid_notify, useCharacteristicDescriptor); + } + } + + /** + * indicate + * + * @param bleDevice + * @param uuid_service + * @param uuid_indicate + * @param callback + */ + public void indicate(BleDevice bleDevice, String uuid_service, String uuid_indicate, + BleIndicateCallback callback) { + indicate(bleDevice, uuid_service, uuid_indicate, false, callback); + } + + /** + * indicate + * + * @param bleDevice + * @param uuid_service + * @param uuid_indicate + * @param useCharacteristicDescriptor + * @param callback + */ + public void indicate(BleDevice bleDevice, String uuid_service, String uuid_indicate, + boolean useCharacteristicDescriptor, BleIndicateCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleIndicateCallback can not be Null!"); + } + + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onIndicateFailure(new OtherException("This device not connect!")); + } else { + bleBluetooth.newBleConnector().withUUIDString(uuid_service, uuid_indicate) + .enableCharacteristicIndicate(callback, uuid_indicate, + useCharacteristicDescriptor); + } + } + + /** + * stop notify, remove callback + * + * @param bleDevice + * @param uuid_service + * @param uuid_notify + * @return + */ + public boolean stopNotify(BleDevice bleDevice, String uuid_service, String uuid_notify) { + return stopNotify(bleDevice, uuid_service, uuid_notify, false); + } + + /** + * stop notify, remove callback + * + * @param bleDevice + * @param uuid_service + * @param uuid_notify + * @param useCharacteristicDescriptor + * @return + */ + public boolean stopNotify(BleDevice bleDevice, String uuid_service, String uuid_notify, + boolean useCharacteristicDescriptor) { + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + return false; + } + boolean success = bleBluetooth.newBleConnector().withUUIDString(uuid_service, uuid_notify) + .disableCharacteristicNotify(useCharacteristicDescriptor); + if (success) { + bleBluetooth.removeNotifyCallback(uuid_notify); + } + return success; + } + + /** + * stop indicate, remove callback + * + * @param bleDevice + * @param uuid_service + * @param uuid_indicate + * @return + */ + public boolean stopIndicate(BleDevice bleDevice, String uuid_service, String uuid_indicate) { + return stopIndicate(bleDevice, uuid_service, uuid_indicate, false); + } + + /** + * stop indicate, remove callback + * + * @param bleDevice + * @param uuid_service + * @param uuid_indicate + * @param useCharacteristicDescriptor + * @return + */ + public boolean stopIndicate(BleDevice bleDevice, String uuid_service, String uuid_indicate, + boolean useCharacteristicDescriptor) { + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + return false; + } + boolean success = bleBluetooth.newBleConnector().withUUIDString(uuid_service, uuid_indicate) + .disableCharacteristicIndicate(useCharacteristicDescriptor); + if (success) { + bleBluetooth.removeIndicateCallback(uuid_indicate); + } + return success; + } + + /** + * write + * + * @param bleDevice + * @param uuid_service + * @param uuid_write + * @param data + * @param callback + */ + public void write(BleDevice bleDevice, String uuid_service, String uuid_write, byte[] data, + BleWriteCallback callback) { + write(bleDevice, uuid_service, uuid_write, data, true, callback); + } + + /** + * write + * + * @param bleDevice + * @param uuid_service + * @param uuid_write + * @param data + * @param split + * @param callback + */ + public void write(BleDevice bleDevice, String uuid_service, String uuid_write, byte[] data, + boolean split, BleWriteCallback callback) { + + write(bleDevice, uuid_service, uuid_write, data, split, true, 0, callback); + } + + /** + * write + * + * @param bleDevice + * @param uuid_service + * @param uuid_write + * @param data + * @param split + * @param sendNextWhenLastSuccess + * @param intervalBetweenTwoPackage + * @param callback + */ + public void write(BleDevice bleDevice, String uuid_service, String uuid_write, byte[] data, + boolean split, boolean sendNextWhenLastSuccess, + long intervalBetweenTwoPackage, BleWriteCallback callback) { + + if (callback == null) { + throw new IllegalArgumentException("BleWriteCallback can not be Null!"); + } + + if (data == null) { + BleLog.e("data is Null!"); + callback.onWriteFailure(new OtherException("data is Null!")); + return; + } + + if (data.length > 20 && !split) { + BleLog.w("Be careful: data's length beyond 20! Ensure MTU higher than 23, or use " + + "spilt write!"); + } + + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onWriteFailure(new OtherException("This device not connect!")); + } else { + if (split && data.length > getSplitWriteNum()) { + new SplitWriter().splitWrite(bleBluetooth, uuid_service, uuid_write, data, + sendNextWhenLastSuccess, intervalBetweenTwoPackage, callback); + } else { + bleBluetooth.newBleConnector().withUUIDString(uuid_service, uuid_write) + .writeCharacteristic(data, callback, uuid_write); + } + } + } + + /** + * read + * + * @param bleDevice + * @param uuid_service + * @param uuid_read + * @param callback + */ + public void read(BleDevice bleDevice, String uuid_service, String uuid_read, + BleReadCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleReadCallback can not be Null!"); + } + + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onReadFailure(new OtherException("This device is not connected!")); + } else { + bleBluetooth.newBleConnector().withUUIDString(uuid_service, uuid_read) + .readCharacteristic(callback, uuid_read); + } + } + + /** + * read Rssi + * + * @param bleDevice + * @param callback + */ + public void readRssi(BleDevice bleDevice, BleRssiCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleRssiCallback can not be Null!"); + } + + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onRssiFailure(new OtherException("This device is not connected!")); + } else { + bleBluetooth.newBleConnector().readRemoteRssi(callback); + } + } + + /** + * set Mtu + * + * @param bleDevice + * @param mtu + * @param callback + */ + public void setMtu(BleDevice bleDevice, int mtu, BleMtuChangedCallback callback) { + if (callback == null) { + throw new IllegalArgumentException("BleMtuChangedCallback can not be Null!"); + } + + if (mtu > DEFAULT_MAX_MTU) { + BleLog.e("requiredMtu should lower than 512 !"); + callback.onSetMTUFailure(new OtherException("requiredMtu should lower than 512 !")); + return; + } + + if (mtu < DEFAULT_MTU) { + BleLog.e("requiredMtu should higher than 23 !"); + callback.onSetMTUFailure(new OtherException("requiredMtu should higher than 23 !")); + return; + } + + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + callback.onSetMTUFailure(new OtherException("This device is not connected!")); + } else { + bleBluetooth.newBleConnector().setMtu(mtu, callback); + } + } + + /** + * requestConnectionPriority + * + * @param connectionPriority Request a specific connection priority. Must be one of + * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, + * {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} + * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. + * @throws IllegalArgumentException If the parameters are outside of their + * specified range. + */ + public boolean requestConnectionPriority(BleDevice bleDevice, int connectionPriority) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + BleBluetooth bleBluetooth = multipleBluetoothController.getBleBluetooth(bleDevice); + if (bleBluetooth == null) { + return false; + } else { + return bleBluetooth.newBleConnector().requestConnectionPriority(connectionPriority); + } + } + return false; + } + + /** + * is support ble? + * + * @return + */ + public boolean isSupportBle() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && context.getApplicationContext() + .getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE); + } + + /** + * Open bluetooth + */ + public void enableBluetooth() { + if (bluetoothAdapter != null) { + bluetoothAdapter.enable(); + } + } + + /** + * Disable bluetooth + */ + public void disableBluetooth() { + if (bluetoothAdapter != null) { + if (bluetoothAdapter.isEnabled()) + bluetoothAdapter.disable(); + } + } + + /** + * judge Bluetooth is enable + * + * @return + */ + public boolean isBlueEnable() { + return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); + } + + + public BleDevice convertBleDevice(BluetoothDevice bluetoothDevice) { + return new BleDevice(bluetoothDevice); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public BleDevice convertBleDevice(ScanResult scanResult) { + if (scanResult == null) { + throw new IllegalArgumentException("scanResult can not be Null!"); + } + BluetoothDevice bluetoothDevice = scanResult.getDevice(); + int rssi = scanResult.getRssi(); + ScanRecord scanRecord = scanResult.getScanRecord(); + byte[] bytes = null; + if (scanRecord != null) + bytes = scanRecord.getBytes(); + long timestampNanos = scanResult.getTimestampNanos(); + return new BleDevice(bluetoothDevice, rssi, bytes, timestampNanos); + } + + public BleBluetooth getBleBluetooth(BleDevice bleDevice) { + if (multipleBluetoothController != null) { + return multipleBluetoothController.getBleBluetooth(bleDevice); + } + return null; + } + + public BluetoothGatt getBluetoothGatt(BleDevice bleDevice) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + return bleBluetooth.getBluetoothGatt(); + return null; + } + + public List getBluetoothGattServices(BleDevice bleDevice) { + BluetoothGatt gatt = getBluetoothGatt(bleDevice); + if (gatt != null) { + return gatt.getServices(); + } + return null; + } + + public List getBluetoothGattCharacteristics(BluetoothGattService service) { + return service.getCharacteristics(); + } + + public void removeConnectGattCallback(BleDevice bleDevice) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeConnectGattCallback(); + } + + public void removeRssiCallback(BleDevice bleDevice) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeRssiCallback(); + } + + public void removeMtuChangedCallback(BleDevice bleDevice) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeMtuChangedCallback(); + } + + public void removeNotifyCallback(BleDevice bleDevice, String uuid_notify) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeNotifyCallback(uuid_notify); + } + + public void removeIndicateCallback(BleDevice bleDevice, String uuid_indicate) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeIndicateCallback(uuid_indicate); + } + + public void removeWriteCallback(BleDevice bleDevice, String uuid_write) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeWriteCallback(uuid_write); + } + + public void removeReadCallback(BleDevice bleDevice, String uuid_read) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.removeReadCallback(uuid_read); + } + + public void clearCharacterCallback(BleDevice bleDevice) { + BleBluetooth bleBluetooth = getBleBluetooth(bleDevice); + if (bleBluetooth != null) + bleBluetooth.clearCharacterCallback(); + } + + public BleScanState getScanSate() { + return BleScanner.getInstance().getScanState(); + } + + public List getAllConnectedDevice() { + if (multipleBluetoothController == null) + return null; + return multipleBluetoothController.getDeviceList(); + } + + /** + * @param bleDevice + * @return State of the profile connection. One of + * {@link BluetoothProfile#STATE_CONNECTED}, + * {@link BluetoothProfile#STATE_CONNECTING}, + * {@link BluetoothProfile#STATE_DISCONNECTED}, + * {@link BluetoothProfile#STATE_DISCONNECTING} + */ + public int getConnectState(BleDevice bleDevice) { + if (bleDevice != null) { + return bluetoothManager.getConnectionState(bleDevice.getDevice(), + BluetoothProfile.GATT); + } else { + return BluetoothProfile.STATE_DISCONNECTED; + } + } + + public boolean isConnected(BleDevice bleDevice) { + if (!isBlueEnable()) + return false; + return getConnectState(bleDevice) == BluetoothProfile.STATE_CONNECTED; + } + + public boolean isConnected(String mac) { + if (!isBlueEnable()) + return false; + List list = getAllConnectedDevice(); + for (BleDevice bleDevice : list) { + if (bleDevice != null) { + if (bleDevice.getMac().equals(mac)) { + return true; + } + } + } + return false; + } + + public void disconnect(BleDevice bleDevice) { + if (multipleBluetoothController != null) { + multipleBluetoothController.disconnect(bleDevice); + } + } + + public void disconnectAllDevice() { + if (multipleBluetoothController != null) { + multipleBluetoothController.disconnectAllDevice(); + } + } + + public void destroy() { + if (multipleBluetoothController != null) { + multipleBluetoothController.destroy(); + } + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/bluetooth/BleBluetooth.java b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/BleBluetooth.java new file mode 100644 index 0000000..8c13ff4 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/BleBluetooth.java @@ -0,0 +1,612 @@ +package com.clj.fastble.bluetooth; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothProfile; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.clj.fastble.BleManager; +import com.clj.fastble.callback.BleGattCallback; +import com.clj.fastble.callback.BleIndicateCallback; +import com.clj.fastble.callback.BleMtuChangedCallback; +import com.clj.fastble.callback.BleNotifyCallback; +import com.clj.fastble.callback.BleReadCallback; +import com.clj.fastble.callback.BleRssiCallback; +import com.clj.fastble.callback.BleWriteCallback; +import com.clj.fastble.data.BleConnectStateParameter; +import com.clj.fastble.data.BleDevice; +import com.clj.fastble.data.BleMsg; +import com.clj.fastble.exception.ConnectException; +import com.clj.fastble.exception.OtherException; +import com.clj.fastble.exception.TimeoutException; +import com.clj.fastble.utils.BleLog; + +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import static android.bluetooth.BluetoothDevice.TRANSPORT_LE; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class BleBluetooth { + + private BleGattCallback bleGattCallback; + private BleRssiCallback bleRssiCallback; + private BleMtuChangedCallback bleMtuChangedCallback; + private final HashMap bleNotifyCallbackHashMap = new HashMap<>(); + private final HashMap bleIndicateCallbackHashMap = new HashMap<>(); + private final HashMap bleWriteCallbackHashMap = new HashMap<>(); + private final HashMap bleReadCallbackHashMap = new HashMap<>(); + + private LastState lastState; + private boolean isActiveDisconnect = false; + private final BleDevice bleDevice; + private BluetoothGatt bluetoothGatt; + private final MainHandler mainHandler = new MainHandler(Looper.getMainLooper()); + private int connectRetryCount = 0; + + public BleBluetooth(BleDevice bleDevice) { + this.bleDevice = bleDevice; + } + + public BleConnector newBleConnector() { + return new BleConnector(this); + } + + public synchronized void addConnectGattCallback(BleGattCallback callback) { + bleGattCallback = callback; + } + + public synchronized void removeConnectGattCallback() { + bleGattCallback = null; + } + + public synchronized void addNotifyCallback(String uuid, BleNotifyCallback bleNotifyCallback) { + bleNotifyCallbackHashMap.put(uuid, bleNotifyCallback); + } + + public synchronized void addIndicateCallback(String uuid, BleIndicateCallback bleIndicateCallback) { + bleIndicateCallbackHashMap.put(uuid, bleIndicateCallback); + } + + public synchronized void addWriteCallback(String uuid, BleWriteCallback bleWriteCallback) { + bleWriteCallbackHashMap.put(uuid, bleWriteCallback); + } + + public synchronized void addReadCallback(String uuid, BleReadCallback bleReadCallback) { + bleReadCallbackHashMap.put(uuid, bleReadCallback); + } + + public synchronized void removeNotifyCallback(String uuid) { + if (bleNotifyCallbackHashMap.containsKey(uuid)) + bleNotifyCallbackHashMap.remove(uuid); + } + + public synchronized void removeIndicateCallback(String uuid) { + if (bleIndicateCallbackHashMap.containsKey(uuid)) + bleIndicateCallbackHashMap.remove(uuid); + } + + public synchronized void removeWriteCallback(String uuid) { + if (bleWriteCallbackHashMap.containsKey(uuid)) + bleWriteCallbackHashMap.remove(uuid); + } + + public synchronized void removeReadCallback(String uuid) { + if (bleReadCallbackHashMap.containsKey(uuid)) + bleReadCallbackHashMap.remove(uuid); + } + + public synchronized void clearCharacterCallback() { + bleNotifyCallbackHashMap.clear(); + bleIndicateCallbackHashMap.clear(); + bleWriteCallbackHashMap.clear(); + bleReadCallbackHashMap.clear(); + } + + public synchronized void addRssiCallback(BleRssiCallback callback) { + bleRssiCallback = callback; + } + + public synchronized void removeRssiCallback() { + bleRssiCallback = null; + } + + public synchronized void addMtuChangedCallback(BleMtuChangedCallback callback) { + bleMtuChangedCallback = callback; + } + + public synchronized void removeMtuChangedCallback() { + bleMtuChangedCallback = null; + } + + + public String getDeviceKey() { + return bleDevice.getKey(); + } + + public BleDevice getDevice() { + return bleDevice; + } + + public BluetoothGatt getBluetoothGatt() { + return bluetoothGatt; + } + + public synchronized BluetoothGatt connect(BleDevice bleDevice, + boolean autoConnect, + BleGattCallback callback) { + return connect(bleDevice, autoConnect, callback, 0); + } + + public synchronized BluetoothGatt connect(BleDevice bleDevice, + boolean autoConnect, + BleGattCallback callback, + int connectRetryCount) { + BleLog.i("connect device: " + bleDevice.getName() + + "\nmac: " + bleDevice.getMac() + + "\nautoConnect: " + autoConnect + + "\ncurrentThread: " + Thread.currentThread().getId() + + "\nconnectCount:" + (connectRetryCount + 1)); + if (connectRetryCount == 0) { + this.connectRetryCount = 0; + } + + addConnectGattCallback(callback); + + lastState = LastState.CONNECT_CONNECTING; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + bluetoothGatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(), + autoConnect, coreGattCallback, TRANSPORT_LE); + } else { + bluetoothGatt = bleDevice.getDevice().connectGatt(BleManager.getInstance().getContext(), + autoConnect, coreGattCallback); + } + if (bluetoothGatt != null) { + if (bleGattCallback != null) { + bleGattCallback.onStartConnect(); + } + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_CONNECT_OVER_TIME; + mainHandler.sendMessageDelayed(message, BleManager.getInstance().getConnectOverTime()); + + } else { + disconnectGatt(); + refreshDeviceCache(); + closeBluetoothGatt(); + lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(BleBluetooth.this); + if (bleGattCallback != null) + bleGattCallback.onConnectFail(bleDevice, new OtherException("GATT connect exception occurred!")); + + } + return bluetoothGatt; + } + + public synchronized void disconnect() { + isActiveDisconnect = true; + disconnectGatt(); + } + + public synchronized void destroy() { + lastState = LastState.CONNECT_IDLE; + disconnectGatt(); + refreshDeviceCache(); + closeBluetoothGatt(); + removeConnectGattCallback(); + removeRssiCallback(); + removeMtuChangedCallback(); + clearCharacterCallback(); + mainHandler.removeCallbacksAndMessages(null); + } + + private synchronized void disconnectGatt() { + if (bluetoothGatt != null) { + bluetoothGatt.disconnect(); + } + } + + private synchronized void refreshDeviceCache() { + try { + final Method refresh = BluetoothGatt.class.getMethod("refresh"); + if (refresh != null && bluetoothGatt != null) { + boolean success = (Boolean) refresh.invoke(bluetoothGatt); + BleLog.i("refreshDeviceCache, is success: " + success); + } + } catch (Exception e) { + BleLog.i("exception occur while refreshing device: " + e.getMessage()); + e.printStackTrace(); + } + } + + private synchronized void closeBluetoothGatt() { + if (bluetoothGatt != null) { + bluetoothGatt.close(); + } + } + + private final class MainHandler extends Handler { + + MainHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case BleMsg.MSG_CONNECT_FAIL: { + disconnectGatt(); + refreshDeviceCache(); + closeBluetoothGatt(); + + if (connectRetryCount < BleManager.getInstance().getReConnectCount()) { + BleLog.e("Connect fail, try reconnect " + BleManager.getInstance().getReConnectInterval() + " millisecond later"); + ++connectRetryCount; + + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_RECONNECT; + mainHandler.sendMessageDelayed(message, BleManager.getInstance().getReConnectInterval()); + } else { + lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(BleBluetooth.this); + + BleConnectStateParameter para = (BleConnectStateParameter) msg.obj; + int status = para.getStatus(); + if (bleGattCallback != null) + bleGattCallback.onConnectFail(bleDevice, new ConnectException(bluetoothGatt, status)); + } + } + break; + + case BleMsg.MSG_DISCONNECTED: { + lastState = LastState.CONNECT_DISCONNECT; + BleManager.getInstance().getMultipleBluetoothController().removeBleBluetooth(BleBluetooth.this); + + disconnect(); + refreshDeviceCache(); + closeBluetoothGatt(); + removeRssiCallback(); + removeMtuChangedCallback(); + clearCharacterCallback(); + mainHandler.removeCallbacksAndMessages(null); + + BleConnectStateParameter para = (BleConnectStateParameter) msg.obj; + boolean isActive = para.isActive(); + int status = para.getStatus(); + if (bleGattCallback != null) + bleGattCallback.onDisConnected(isActive, bleDevice, bluetoothGatt, status); + } + break; + + case BleMsg.MSG_RECONNECT: { + connect(bleDevice, false, bleGattCallback, connectRetryCount); + } + break; + + case BleMsg.MSG_CONNECT_OVER_TIME: { + disconnectGatt(); + refreshDeviceCache(); + closeBluetoothGatt(); + + lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(BleBluetooth.this); + + if (bleGattCallback != null) + bleGattCallback.onConnectFail(bleDevice, new TimeoutException()); + } + break; + + case BleMsg.MSG_DISCOVER_SERVICES: { + if (bluetoothGatt != null) { + boolean discoverServiceResult = bluetoothGatt.discoverServices(); + if (!discoverServiceResult) { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_FAIL; + mainHandler.sendMessage(message); + } + } else { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_FAIL; + mainHandler.sendMessage(message); + } + } + break; + + case BleMsg.MSG_DISCOVER_FAIL: { + disconnectGatt(); + refreshDeviceCache(); + closeBluetoothGatt(); + + lastState = LastState.CONNECT_FAILURE; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(BleBluetooth.this); + + if (bleGattCallback != null) + bleGattCallback.onConnectFail(bleDevice, + new OtherException("GATT discover services exception occurred!")); + } + break; + + case BleMsg.MSG_DISCOVER_SUCCESS: { + lastState = LastState.CONNECT_CONNECTED; + isActiveDisconnect = false; + BleManager.getInstance().getMultipleBluetoothController().removeConnectingBle(BleBluetooth.this); + BleManager.getInstance().getMultipleBluetoothController().addBleBluetooth(BleBluetooth.this); + + BleConnectStateParameter para = (BleConnectStateParameter) msg.obj; + int status = para.getStatus(); + if (bleGattCallback != null) + bleGattCallback.onConnectSuccess(bleDevice, bluetoothGatt, status); + } + break; + + default: + super.handleMessage(msg); + break; + } + } + } + + private BluetoothGattCallback coreGattCallback = new BluetoothGattCallback() { + + @Override + public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { + super.onConnectionStateChange(gatt, status, newState); + BleLog.i("BluetoothGattCallback:onConnectionStateChange " + + '\n' + "status: " + status + + '\n' + "newState: " + newState + + '\n' + "currentThread: " + Thread.currentThread().getId()); + + bluetoothGatt = gatt; + + mainHandler.removeMessages(BleMsg.MSG_CONNECT_OVER_TIME); + + if (newState == BluetoothProfile.STATE_CONNECTED) { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_SERVICES; + mainHandler.sendMessageDelayed(message, 500); + + } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { + if (lastState == LastState.CONNECT_CONNECTING) { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_CONNECT_FAIL; + message.obj = new BleConnectStateParameter(status); + mainHandler.sendMessage(message); + + } else if (lastState == LastState.CONNECT_CONNECTED) { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCONNECTED; + BleConnectStateParameter para = new BleConnectStateParameter(status); + para.setActive(isActiveDisconnect); + message.obj = para; + mainHandler.sendMessage(message); + } + } + } + + @Override + public void onServicesDiscovered(BluetoothGatt gatt, int status) { + super.onServicesDiscovered(gatt, status); + BleLog.i("BluetoothGattCallback:onServicesDiscovered " + + '\n' + "status: " + status + + '\n' + "currentThread: " + Thread.currentThread().getId()); + + bluetoothGatt = gatt; + + if (status == BluetoothGatt.GATT_SUCCESS) { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_SUCCESS; + message.obj = new BleConnectStateParameter(status); + mainHandler.sendMessage(message); + + } else { + Message message = mainHandler.obtainMessage(); + message.what = BleMsg.MSG_DISCOVER_FAIL; + mainHandler.sendMessage(message); + } + } + + @Override + public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { + super.onCharacteristicChanged(gatt, characteristic); + + Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + Object callback = entry.getValue(); + if (callback instanceof BleNotifyCallback) { + BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback; + if (characteristic.getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) { + Handler handler = bleNotifyCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE; + message.obj = bleNotifyCallback; + Bundle bundle = new Bundle(); + bundle.putByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE, characteristic.getValue()); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + + iterator = bleIndicateCallbackHashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + Object callback = entry.getValue(); + if (callback instanceof BleIndicateCallback) { + BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback; + if (characteristic.getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) { + Handler handler = bleIndicateCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_INDICATE_DATA_CHANGE; + message.obj = bleIndicateCallback; + Bundle bundle = new Bundle(); + bundle.putByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE, characteristic.getValue()); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + @Override + public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { + super.onDescriptorWrite(gatt, descriptor, status); + + Iterator iterator = bleNotifyCallbackHashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + Object callback = entry.getValue(); + if (callback instanceof BleNotifyCallback) { + BleNotifyCallback bleNotifyCallback = (BleNotifyCallback) callback; + if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleNotifyCallback.getKey())) { + Handler handler = bleNotifyCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_NOTIFY_RESULT; + message.obj = bleNotifyCallback; + Bundle bundle = new Bundle(); + bundle.putInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS, status); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + + iterator = bleIndicateCallbackHashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + Object callback = entry.getValue(); + if (callback instanceof BleIndicateCallback) { + BleIndicateCallback bleIndicateCallback = (BleIndicateCallback) callback; + if (descriptor.getCharacteristic().getUuid().toString().equalsIgnoreCase(bleIndicateCallback.getKey())) { + Handler handler = bleIndicateCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_INDICATE_RESULT; + message.obj = bleIndicateCallback; + Bundle bundle = new Bundle(); + bundle.putInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS, status); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + @Override + public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicWrite(gatt, characteristic, status); + + Iterator iterator = bleWriteCallbackHashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + Object callback = entry.getValue(); + if (callback instanceof BleWriteCallback) { + BleWriteCallback bleWriteCallback = (BleWriteCallback) callback; + if (characteristic.getUuid().toString().equalsIgnoreCase(bleWriteCallback.getKey())) { + Handler handler = bleWriteCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_WRITE_RESULT; + message.obj = bleWriteCallback; + Bundle bundle = new Bundle(); + bundle.putInt(BleMsg.KEY_WRITE_BUNDLE_STATUS, status); + bundle.putByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE, characteristic.getValue()); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + @Override + public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { + super.onCharacteristicRead(gatt, characteristic, status); + + Iterator iterator = bleReadCallbackHashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + Object callback = entry.getValue(); + if (callback instanceof BleReadCallback) { + BleReadCallback bleReadCallback = (BleReadCallback) callback; + if (characteristic.getUuid().toString().equalsIgnoreCase(bleReadCallback.getKey())) { + Handler handler = bleReadCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_CHA_READ_RESULT; + message.obj = bleReadCallback; + Bundle bundle = new Bundle(); + bundle.putInt(BleMsg.KEY_READ_BUNDLE_STATUS, status); + bundle.putByteArray(BleMsg.KEY_READ_BUNDLE_VALUE, characteristic.getValue()); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + } + } + + @Override + public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) { + super.onReadRemoteRssi(gatt, rssi, status); + + if (bleRssiCallback != null) { + Handler handler = bleRssiCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_READ_RSSI_RESULT; + message.obj = bleRssiCallback; + Bundle bundle = new Bundle(); + bundle.putInt(BleMsg.KEY_READ_RSSI_BUNDLE_STATUS, status); + bundle.putInt(BleMsg.KEY_READ_RSSI_BUNDLE_VALUE, rssi); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + + @Override + public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) { + super.onMtuChanged(gatt, mtu, status); + + if (bleMtuChangedCallback != null) { + Handler handler = bleMtuChangedCallback.getHandler(); + if (handler != null) { + Message message = handler.obtainMessage(); + message.what = BleMsg.MSG_SET_MTU_RESULT; + message.obj = bleMtuChangedCallback; + Bundle bundle = new Bundle(); + bundle.putInt(BleMsg.KEY_SET_MTU_BUNDLE_STATUS, status); + bundle.putInt(BleMsg.KEY_SET_MTU_BUNDLE_VALUE, mtu); + message.setData(bundle); + handler.sendMessage(message); + } + } + } + }; + + enum LastState { + CONNECT_IDLE, + CONNECT_CONNECTING, + CONNECT_CONNECTED, + CONNECT_FAILURE, + CONNECT_DISCONNECT + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/bluetooth/BleConnector.java b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/BleConnector.java new file mode 100644 index 0000000..13f29c2 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/BleConnector.java @@ -0,0 +1,608 @@ + +package com.clj.fastble.bluetooth; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCharacteristic; +import android.bluetooth.BluetoothGattDescriptor; +import android.bluetooth.BluetoothGattService; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import com.clj.fastble.BleManager; +import com.clj.fastble.callback.BleIndicateCallback; +import com.clj.fastble.callback.BleMtuChangedCallback; +import com.clj.fastble.callback.BleNotifyCallback; +import com.clj.fastble.callback.BleReadCallback; +import com.clj.fastble.callback.BleRssiCallback; +import com.clj.fastble.callback.BleWriteCallback; +import com.clj.fastble.data.BleMsg; +import com.clj.fastble.data.BleWriteState; +import com.clj.fastble.exception.GattException; +import com.clj.fastble.exception.OtherException; +import com.clj.fastble.exception.TimeoutException; + +import java.util.UUID; + + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class BleConnector { + + private static final String UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR = "00002902-0000-1000-8000-00805f9b34fb"; + + private BluetoothGatt mBluetoothGatt; + private BluetoothGattService mGattService; + private BluetoothGattCharacteristic mCharacteristic; + private BleBluetooth mBleBluetooth; + private Handler mHandler; + + BleConnector(BleBluetooth bleBluetooth) { + this.mBleBluetooth = bleBluetooth; + this.mBluetoothGatt = bleBluetooth.getBluetoothGatt(); + this.mHandler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + switch (msg.what) { + + case BleMsg.MSG_CHA_NOTIFY_START: { + BleNotifyCallback notifyCallback = (BleNotifyCallback) msg.obj; + if (notifyCallback != null) + notifyCallback.onNotifyFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_CHA_NOTIFY_RESULT: { + notifyMsgInit(); + + BleNotifyCallback notifyCallback = (BleNotifyCallback) msg.obj; + Bundle bundle = msg.getData(); + int status = bundle.getInt(BleMsg.KEY_NOTIFY_BUNDLE_STATUS); + if (notifyCallback != null) { + if (status == BluetoothGatt.GATT_SUCCESS) { + notifyCallback.onNotifySuccess(); + } else { + notifyCallback.onNotifyFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_CHA_NOTIFY_DATA_CHANGE: { + BleNotifyCallback notifyCallback = (BleNotifyCallback) msg.obj; + Bundle bundle = msg.getData(); + byte[] value = bundle.getByteArray(BleMsg.KEY_NOTIFY_BUNDLE_VALUE); + if (notifyCallback != null) { + notifyCallback.onCharacteristicChanged(value); + } + break; + } + + case BleMsg.MSG_CHA_INDICATE_START: { + BleIndicateCallback indicateCallback = (BleIndicateCallback) msg.obj; + if (indicateCallback != null) + indicateCallback.onIndicateFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_CHA_INDICATE_RESULT: { + indicateMsgInit(); + + BleIndicateCallback indicateCallback = (BleIndicateCallback) msg.obj; + Bundle bundle = msg.getData(); + int status = bundle.getInt(BleMsg.KEY_INDICATE_BUNDLE_STATUS); + if (indicateCallback != null) { + if (status == BluetoothGatt.GATT_SUCCESS) { + indicateCallback.onIndicateSuccess(); + } else { + indicateCallback.onIndicateFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_CHA_INDICATE_DATA_CHANGE: { + BleIndicateCallback indicateCallback = (BleIndicateCallback) msg.obj; + Bundle bundle = msg.getData(); + byte[] value = bundle.getByteArray(BleMsg.KEY_INDICATE_BUNDLE_VALUE); + if (indicateCallback != null) { + indicateCallback.onCharacteristicChanged(value); + } + break; + } + + case BleMsg.MSG_CHA_WRITE_START: { + BleWriteCallback writeCallback = (BleWriteCallback) msg.obj; + if (writeCallback != null) { + writeCallback.onWriteFailure(new TimeoutException()); + } + break; + } + + case BleMsg.MSG_CHA_WRITE_RESULT: { + writeMsgInit(); + + BleWriteCallback writeCallback = (BleWriteCallback) msg.obj; + Bundle bundle = msg.getData(); + int status = bundle.getInt(BleMsg.KEY_WRITE_BUNDLE_STATUS); + byte[] value = bundle.getByteArray(BleMsg.KEY_WRITE_BUNDLE_VALUE); + if (writeCallback != null) { + if (status == BluetoothGatt.GATT_SUCCESS) { + writeCallback.onWriteSuccess(BleWriteState.DATA_WRITE_SINGLE, BleWriteState.DATA_WRITE_SINGLE, value); + } else { + writeCallback.onWriteFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_CHA_READ_START: { + BleReadCallback readCallback = (BleReadCallback) msg.obj; + if (readCallback != null) + readCallback.onReadFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_CHA_READ_RESULT: { + readMsgInit(); + + BleReadCallback readCallback = (BleReadCallback) msg.obj; + Bundle bundle = msg.getData(); + int status = bundle.getInt(BleMsg.KEY_READ_BUNDLE_STATUS); + byte[] value = bundle.getByteArray(BleMsg.KEY_READ_BUNDLE_VALUE); + if (readCallback != null) { + if (status == BluetoothGatt.GATT_SUCCESS) { + readCallback.onReadSuccess(value); + } else { + readCallback.onReadFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_READ_RSSI_START: { + BleRssiCallback rssiCallback = (BleRssiCallback) msg.obj; + if (rssiCallback != null) + rssiCallback.onRssiFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_READ_RSSI_RESULT: { + rssiMsgInit(); + + BleRssiCallback rssiCallback = (BleRssiCallback) msg.obj; + Bundle bundle = msg.getData(); + int status = bundle.getInt(BleMsg.KEY_READ_RSSI_BUNDLE_STATUS); + int value = bundle.getInt(BleMsg.KEY_READ_RSSI_BUNDLE_VALUE); + if (rssiCallback != null) { + if (status == BluetoothGatt.GATT_SUCCESS) { + rssiCallback.onRssiSuccess(value); + } else { + rssiCallback.onRssiFailure(new GattException(status)); + } + } + break; + } + + case BleMsg.MSG_SET_MTU_START: { + BleMtuChangedCallback mtuChangedCallback = (BleMtuChangedCallback) msg.obj; + if (mtuChangedCallback != null) + mtuChangedCallback.onSetMTUFailure(new TimeoutException()); + break; + } + + case BleMsg.MSG_SET_MTU_RESULT: { + mtuChangedMsgInit(); + + BleMtuChangedCallback mtuChangedCallback = (BleMtuChangedCallback) msg.obj; + Bundle bundle = msg.getData(); + int status = bundle.getInt(BleMsg.KEY_SET_MTU_BUNDLE_STATUS); + int value = bundle.getInt(BleMsg.KEY_SET_MTU_BUNDLE_VALUE); + if (mtuChangedCallback != null) { + if (status == BluetoothGatt.GATT_SUCCESS) { + mtuChangedCallback.onMtuChanged(value); + } else { + mtuChangedCallback.onSetMTUFailure(new GattException(status)); + } + } + break; + } + } + } + }; + + } + + private BleConnector withUUID(UUID serviceUUID, UUID characteristicUUID) { + if (serviceUUID != null && mBluetoothGatt != null) { + mGattService = mBluetoothGatt.getService(serviceUUID); + } + if (mGattService != null && characteristicUUID != null) { + mCharacteristic = mGattService.getCharacteristic(characteristicUUID); + } + return this; + } + + public BleConnector withUUIDString(String serviceUUID, String characteristicUUID) { + return withUUID(formUUID(serviceUUID), formUUID(characteristicUUID)); + } + + private UUID formUUID(String uuid) { + return uuid == null ? null : UUID.fromString(uuid); + } + + + /*------------------------------- main operation ----------------------------------- */ + + + /** + * notify + */ + public void enableCharacteristicNotify(BleNotifyCallback bleNotifyCallback, String uuid_notify, + boolean userCharacteristicDescriptor) { + if (mCharacteristic != null + && (mCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + + handleCharacteristicNotifyCallback(bleNotifyCallback, uuid_notify); + setCharacteristicNotification(mBluetoothGatt, mCharacteristic, userCharacteristicDescriptor, true, bleNotifyCallback); + } else { + if (bleNotifyCallback != null) + bleNotifyCallback.onNotifyFailure(new OtherException("this characteristic not support notify!")); + } + } + + /** + * stop notify + */ + public boolean disableCharacteristicNotify(boolean useCharacteristicDescriptor) { + if (mCharacteristic != null + && (mCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + return setCharacteristicNotification(mBluetoothGatt, mCharacteristic, + useCharacteristicDescriptor, false, null); + } else { + return false; + } + } + + /** + * notify setting + */ + private boolean setCharacteristicNotification(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + boolean useCharacteristicDescriptor, + boolean enable, + BleNotifyCallback bleNotifyCallback) { + if (gatt == null || characteristic == null) { + notifyMsgInit(); + if (bleNotifyCallback != null) + bleNotifyCallback.onNotifyFailure(new OtherException("gatt or characteristic equal null")); + return false; + } + + boolean success1 = gatt.setCharacteristicNotification(characteristic, enable); + if (!success1) { + notifyMsgInit(); + if (bleNotifyCallback != null) + bleNotifyCallback.onNotifyFailure(new OtherException("gatt setCharacteristicNotification fail")); + return false; + } + + BluetoothGattDescriptor descriptor; + if (useCharacteristicDescriptor) { + descriptor = characteristic.getDescriptor(characteristic.getUuid()); + } else { + descriptor = characteristic.getDescriptor(formUUID(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR)); + } + if (descriptor == null) { + notifyMsgInit(); + if (bleNotifyCallback != null) + bleNotifyCallback.onNotifyFailure(new OtherException("descriptor equals null")); + return false; + } else { + descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE : + BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); + boolean success2 = gatt.writeDescriptor(descriptor); + if (!success2) { + notifyMsgInit(); + if (bleNotifyCallback != null) + bleNotifyCallback.onNotifyFailure(new OtherException("gatt writeDescriptor fail")); + } + return success2; + } + } + + /** + * indicate + */ + public void enableCharacteristicIndicate(BleIndicateCallback bleIndicateCallback, String uuid_indicate, + boolean useCharacteristicDescriptor) { + if (mCharacteristic != null + && (mCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + handleCharacteristicIndicateCallback(bleIndicateCallback, uuid_indicate); + setCharacteristicIndication(mBluetoothGatt, mCharacteristic, + useCharacteristicDescriptor, true, bleIndicateCallback); + } else { + if (bleIndicateCallback != null) + bleIndicateCallback.onIndicateFailure(new OtherException("this characteristic not support indicate!")); + } + } + + + /** + * stop indicate + */ + public boolean disableCharacteristicIndicate(boolean userCharacteristicDescriptor) { + if (mCharacteristic != null + && (mCharacteristic.getProperties() | BluetoothGattCharacteristic.PROPERTY_NOTIFY) > 0) { + return setCharacteristicIndication(mBluetoothGatt, mCharacteristic, + userCharacteristicDescriptor, false, null); + } else { + return false; + } + } + + /** + * indicate setting + */ + private boolean setCharacteristicIndication(BluetoothGatt gatt, + BluetoothGattCharacteristic characteristic, + boolean useCharacteristicDescriptor, + boolean enable, + BleIndicateCallback bleIndicateCallback) { + if (gatt == null || characteristic == null) { + indicateMsgInit(); + if (bleIndicateCallback != null) + bleIndicateCallback.onIndicateFailure(new OtherException("gatt or characteristic equal null")); + return false; + } + + boolean success1 = gatt.setCharacteristicNotification(characteristic, enable); + if (!success1) { + indicateMsgInit(); + if (bleIndicateCallback != null) + bleIndicateCallback.onIndicateFailure(new OtherException("gatt setCharacteristicNotification fail")); + return false; + } + + BluetoothGattDescriptor descriptor; + if (useCharacteristicDescriptor) { + descriptor = characteristic.getDescriptor(characteristic.getUuid()); + } else { + descriptor = characteristic.getDescriptor(formUUID(UUID_CLIENT_CHARACTERISTIC_CONFIG_DESCRIPTOR)); + } + if (descriptor == null) { + indicateMsgInit(); + if (bleIndicateCallback != null) + bleIndicateCallback.onIndicateFailure(new OtherException("descriptor equals null")); + return false; + } else { + descriptor.setValue(enable ? BluetoothGattDescriptor.ENABLE_INDICATION_VALUE : + BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE); + boolean success2 = gatt.writeDescriptor(descriptor); + if (!success2) { + indicateMsgInit(); + if (bleIndicateCallback != null) + bleIndicateCallback.onIndicateFailure(new OtherException("gatt writeDescriptor fail")); + } + return success2; + } + } + + /** + * write + */ + public void writeCharacteristic(byte[] data, BleWriteCallback bleWriteCallback, String uuid_write) { + if (data == null || data.length <= 0) { + if (bleWriteCallback != null) + bleWriteCallback.onWriteFailure(new OtherException("the data to be written is empty")); + return; + } + + if (mCharacteristic == null + || (mCharacteristic.getProperties() & (BluetoothGattCharacteristic.PROPERTY_WRITE | BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE)) == 0) { + if (bleWriteCallback != null) + bleWriteCallback.onWriteFailure(new OtherException("this characteristic not support write!")); + return; + } + + if (mCharacteristic.setValue(data)) { + handleCharacteristicWriteCallback(bleWriteCallback, uuid_write); + if (!mBluetoothGatt.writeCharacteristic(mCharacteristic)) { + writeMsgInit(); + if (bleWriteCallback != null) + bleWriteCallback.onWriteFailure(new OtherException("gatt writeCharacteristic fail")); + } + } else { + if (bleWriteCallback != null) + bleWriteCallback.onWriteFailure(new OtherException("Updates the locally stored value of this characteristic fail")); + } + } + + /** + * read + */ + public void readCharacteristic(BleReadCallback bleReadCallback, String uuid_read) { + if (mCharacteristic != null + && (mCharacteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_READ) > 0) { + + handleCharacteristicReadCallback(bleReadCallback, uuid_read); + if (!mBluetoothGatt.readCharacteristic(mCharacteristic)) { + readMsgInit(); + if (bleReadCallback != null) + bleReadCallback.onReadFailure(new OtherException("gatt readCharacteristic fail")); + } + } else { + if (bleReadCallback != null) + bleReadCallback.onReadFailure(new OtherException("this characteristic not support read!")); + } + } + + /** + * rssi + */ + public void readRemoteRssi(BleRssiCallback bleRssiCallback) { + handleRSSIReadCallback(bleRssiCallback); + if (!mBluetoothGatt.readRemoteRssi()) { + rssiMsgInit(); + if (bleRssiCallback != null) + bleRssiCallback.onRssiFailure(new OtherException("gatt readRemoteRssi fail")); + } + } + + /** + * set mtu + */ + public void setMtu(int requiredMtu, BleMtuChangedCallback bleMtuChangedCallback) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + handleSetMtuCallback(bleMtuChangedCallback); + if (!mBluetoothGatt.requestMtu(requiredMtu)) { + mtuChangedMsgInit(); + if (bleMtuChangedCallback != null) + bleMtuChangedCallback.onSetMTUFailure(new OtherException("gatt requestMtu fail")); + } + } else { + if (bleMtuChangedCallback != null) + bleMtuChangedCallback.onSetMTUFailure(new OtherException("API level lower than 21")); + } + } + + /** + * requestConnectionPriority + * + * @param connectionPriority Request a specific connection priority. Must be one of + * {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, + * {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH} + * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. + * @throws IllegalArgumentException If the parameters are outside of their + * specified range. + */ + public boolean requestConnectionPriority(int connectionPriority) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return mBluetoothGatt.requestConnectionPriority(connectionPriority); + } + return false; + } + + + /**************************************** Handle call back ******************************************/ + + /** + * notify + */ + private void handleCharacteristicNotifyCallback(BleNotifyCallback bleNotifyCallback, + String uuid_notify) { + if (bleNotifyCallback != null) { + notifyMsgInit(); + bleNotifyCallback.setKey(uuid_notify); + bleNotifyCallback.setHandler(mHandler); + mBleBluetooth.addNotifyCallback(uuid_notify, bleNotifyCallback); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BleMsg.MSG_CHA_NOTIFY_START, bleNotifyCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * indicate + */ + private void handleCharacteristicIndicateCallback(BleIndicateCallback bleIndicateCallback, + String uuid_indicate) { + if (bleIndicateCallback != null) { + indicateMsgInit(); + bleIndicateCallback.setKey(uuid_indicate); + bleIndicateCallback.setHandler(mHandler); + mBleBluetooth.addIndicateCallback(uuid_indicate, bleIndicateCallback); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BleMsg.MSG_CHA_INDICATE_START, bleIndicateCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * write + */ + private void handleCharacteristicWriteCallback(BleWriteCallback bleWriteCallback, + String uuid_write) { + if (bleWriteCallback != null) { + writeMsgInit(); + bleWriteCallback.setKey(uuid_write); + bleWriteCallback.setHandler(mHandler); + mBleBluetooth.addWriteCallback(uuid_write, bleWriteCallback); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BleMsg.MSG_CHA_WRITE_START, bleWriteCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * read + */ + private void handleCharacteristicReadCallback(BleReadCallback bleReadCallback, + String uuid_read) { + if (bleReadCallback != null) { + readMsgInit(); + bleReadCallback.setKey(uuid_read); + bleReadCallback.setHandler(mHandler); + mBleBluetooth.addReadCallback(uuid_read, bleReadCallback); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BleMsg.MSG_CHA_READ_START, bleReadCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * rssi + */ + private void handleRSSIReadCallback(BleRssiCallback bleRssiCallback) { + if (bleRssiCallback != null) { + rssiMsgInit(); + bleRssiCallback.setHandler(mHandler); + mBleBluetooth.addRssiCallback(bleRssiCallback); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BleMsg.MSG_READ_RSSI_START, bleRssiCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + /** + * set mtu + */ + private void handleSetMtuCallback(BleMtuChangedCallback bleMtuChangedCallback) { + if (bleMtuChangedCallback != null) { + mtuChangedMsgInit(); + bleMtuChangedCallback.setHandler(mHandler); + mBleBluetooth.addMtuChangedCallback(bleMtuChangedCallback); + mHandler.sendMessageDelayed( + mHandler.obtainMessage(BleMsg.MSG_SET_MTU_START, bleMtuChangedCallback), + BleManager.getInstance().getOperateTimeout()); + } + } + + public void notifyMsgInit() { + mHandler.removeMessages(BleMsg.MSG_CHA_NOTIFY_START); + } + + public void indicateMsgInit() { + mHandler.removeMessages(BleMsg.MSG_CHA_INDICATE_START); + } + + public void writeMsgInit() { + mHandler.removeMessages(BleMsg.MSG_CHA_WRITE_START); + } + + public void readMsgInit() { + mHandler.removeMessages(BleMsg.MSG_CHA_READ_START); + } + + public void rssiMsgInit() { + mHandler.removeMessages(BleMsg.MSG_READ_RSSI_START); + } + + public void mtuChangedMsgInit() { + mHandler.removeMessages(BleMsg.MSG_SET_MTU_START); + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/bluetooth/MultipleBluetoothController.java b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/MultipleBluetoothController.java new file mode 100644 index 0000000..2f8fe6d --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/MultipleBluetoothController.java @@ -0,0 +1,139 @@ +package com.clj.fastble.bluetooth; + + +import android.bluetooth.BluetoothDevice; +import android.os.Build; + +import com.clj.fastble.BleManager; +import com.clj.fastble.data.BleDevice; +import com.clj.fastble.utils.BleLruHashMap; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class MultipleBluetoothController { + + private final BleLruHashMap bleLruHashMap; + private final HashMap bleTempHashMap; + + public MultipleBluetoothController() { + bleLruHashMap = new BleLruHashMap<>(BleManager.getInstance().getMaxConnectCount()); + bleTempHashMap = new HashMap<>(); + } + + public synchronized BleBluetooth buildConnectingBle(BleDevice bleDevice) { + BleBluetooth bleBluetooth = new BleBluetooth(bleDevice); + if (!bleTempHashMap.containsKey(bleBluetooth.getDeviceKey())) { + bleTempHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth); + } + return bleBluetooth; + } + + public synchronized void removeConnectingBle(BleBluetooth bleBluetooth) { + if (bleBluetooth == null) { + return; + } + if (bleTempHashMap.containsKey(bleBluetooth.getDeviceKey())) { + bleTempHashMap.remove(bleBluetooth.getDeviceKey()); + } + } + + public synchronized void addBleBluetooth(BleBluetooth bleBluetooth) { + if (bleBluetooth == null) { + return; + } + if (!bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) { + bleLruHashMap.put(bleBluetooth.getDeviceKey(), bleBluetooth); + } + } + + public synchronized void removeBleBluetooth(BleBluetooth bleBluetooth) { + if (bleBluetooth == null) { + return; + } + if (bleLruHashMap.containsKey(bleBluetooth.getDeviceKey())) { + bleLruHashMap.remove(bleBluetooth.getDeviceKey()); + } + } + + public synchronized boolean isContainDevice(BleDevice bleDevice) { + return bleDevice != null && bleLruHashMap.containsKey(bleDevice.getKey()); + } + + public synchronized boolean isContainDevice(BluetoothDevice bluetoothDevice) { + return bluetoothDevice != null && bleLruHashMap.containsKey(bluetoothDevice.getName() + bluetoothDevice.getAddress()); + } + + public synchronized BleBluetooth getBleBluetooth(BleDevice bleDevice) { + if (bleDevice != null) { + if (bleLruHashMap.containsKey(bleDevice.getKey())) { + return bleLruHashMap.get(bleDevice.getKey()); + } + } + return null; + } + + public synchronized void disconnect(BleDevice bleDevice) { + if (isContainDevice(bleDevice)) { + getBleBluetooth(bleDevice).disconnect(); + } + } + + public synchronized void disconnectAllDevice() { + for (Map.Entry stringBleBluetoothEntry : bleLruHashMap.entrySet()) { + stringBleBluetoothEntry.getValue().disconnect(); + } + bleLruHashMap.clear(); + } + + public synchronized void destroy() { + for (Map.Entry stringBleBluetoothEntry : bleLruHashMap.entrySet()) { + stringBleBluetoothEntry.getValue().destroy(); + } + bleLruHashMap.clear(); + for (Map.Entry stringBleBluetoothEntry : bleTempHashMap.entrySet()) { + stringBleBluetoothEntry.getValue().destroy(); + } + bleTempHashMap.clear(); + } + + public synchronized List getBleBluetoothList() { + List bleBluetoothList = new ArrayList<>(bleLruHashMap.values()); + Collections.sort(bleBluetoothList, new Comparator() { + @Override + public int compare(BleBluetooth lhs, BleBluetooth rhs) { + return lhs.getDeviceKey().compareToIgnoreCase(rhs.getDeviceKey()); + } + }); + return bleBluetoothList; + } + + public synchronized List getDeviceList() { + refreshConnectedDevice(); + List deviceList = new ArrayList<>(); + for (BleBluetooth BleBluetooth : getBleBluetoothList()) { + if (BleBluetooth != null) { + deviceList.add(BleBluetooth.getDevice()); + } + } + return deviceList; + } + + public void refreshConnectedDevice() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + List bluetoothList = getBleBluetoothList(); + for (int i = 0; bluetoothList != null && i < bluetoothList.size(); i++) { + BleBluetooth bleBluetooth = bluetoothList.get(i); + if (!BleManager.getInstance().isConnected(bleBluetooth.getDevice())) { + removeBleBluetooth(bleBluetooth); + } + } + } + } + + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/bluetooth/SplitWriter.java b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/SplitWriter.java new file mode 100644 index 0000000..f567e75 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/bluetooth/SplitWriter.java @@ -0,0 +1,158 @@ +package com.clj.fastble.bluetooth; + + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; + +import com.clj.fastble.BleManager; +import com.clj.fastble.callback.BleWriteCallback; +import com.clj.fastble.data.BleMsg; +import com.clj.fastble.exception.BleException; +import com.clj.fastble.exception.OtherException; +import com.clj.fastble.utils.BleLog; + +import java.util.LinkedList; +import java.util.Queue; + +public class SplitWriter { + + private HandlerThread mHandlerThread; + private Handler mHandler; + + private BleBluetooth mBleBluetooth; + private String mUuid_service; + private String mUuid_write; + private byte[] mData; + private int mCount; + private boolean mSendNextWhenLastSuccess; + private long mIntervalBetweenTwoPackage; + private BleWriteCallback mCallback; + private Queue mDataQueue; + private int mTotalNum; + + public SplitWriter() { + mHandlerThread = new HandlerThread("splitWriter"); + mHandlerThread.start(); + + mHandler = new Handler(mHandlerThread.getLooper()) { + @Override + public void handleMessage(Message msg) { + super.handleMessage(msg); + if (msg.what == BleMsg.MSG_SPLIT_WRITE_NEXT) { + write(); + } + } + }; + } + + public void splitWrite(BleBluetooth bleBluetooth, + String uuid_service, + String uuid_write, + byte[] data, + boolean sendNextWhenLastSuccess, + long intervalBetweenTwoPackage, + BleWriteCallback callback) { + mBleBluetooth = bleBluetooth; + mUuid_service = uuid_service; + mUuid_write = uuid_write; + mData = data; + mSendNextWhenLastSuccess = sendNextWhenLastSuccess; + mIntervalBetweenTwoPackage = intervalBetweenTwoPackage; + mCount = BleManager.getInstance().getSplitWriteNum(); + mCallback = callback; + + splitWrite(); + } + + private void splitWrite() { + if (mData == null) { + throw new IllegalArgumentException("data is Null!"); + } + if (mCount < 1) { + throw new IllegalArgumentException("split count should higher than 0!"); + } + mDataQueue = splitByte(mData, mCount); + mTotalNum = mDataQueue.size(); + write(); + } + + private void write() { + if (mDataQueue.peek() == null) { + release(); + return; + } + + byte[] data = mDataQueue.poll(); + mBleBluetooth.newBleConnector() + .withUUIDString(mUuid_service, mUuid_write) + .writeCharacteristic( + data, + new BleWriteCallback() { + @Override + public void onWriteSuccess(int current, int total, byte[] justWrite) { + int position = mTotalNum - mDataQueue.size(); + if (mCallback != null) { + mCallback.onWriteSuccess(position, mTotalNum, justWrite); + } + if (mSendNextWhenLastSuccess) { + Message message = mHandler.obtainMessage(BleMsg.MSG_SPLIT_WRITE_NEXT); + mHandler.sendMessageDelayed(message, mIntervalBetweenTwoPackage); + } + } + + @Override + public void onWriteFailure(BleException exception) { + if (mCallback != null) { + mCallback.onWriteFailure(new OtherException("exception occur while writing: " + exception.getDescription())); + } + if (mSendNextWhenLastSuccess) { + Message message = mHandler.obtainMessage(BleMsg.MSG_SPLIT_WRITE_NEXT); + mHandler.sendMessageDelayed(message, mIntervalBetweenTwoPackage); + } + } + }, + mUuid_write); + + if (!mSendNextWhenLastSuccess) { + Message message = mHandler.obtainMessage(BleMsg.MSG_SPLIT_WRITE_NEXT); + mHandler.sendMessageDelayed(message, mIntervalBetweenTwoPackage); + } + } + + private void release() { + mHandlerThread.quit(); + mHandler.removeCallbacksAndMessages(null); + } + + private static Queue splitByte(byte[] data, int count) { + if (count > 20) { + BleLog.w("Be careful: split count beyond 20! Ensure MTU higher than 23!"); + } + Queue byteQueue = new LinkedList<>(); + int pkgCount; + if (data.length % count == 0) { + pkgCount = data.length / count; + } else { + pkgCount = Math.round(data.length / count + 1); + } + + if (pkgCount > 0) { + for (int i = 0; i < pkgCount; i++) { + byte[] dataPkg; + int j; + if (pkgCount == 1 || i == pkgCount - 1) { + j = data.length % count == 0 ? count : data.length % count; + System.arraycopy(data, i * count, dataPkg = new byte[j], 0, j); + } else { + System.arraycopy(data, i * count, dataPkg = new byte[count], 0, count); + } + byteQueue.offer(dataPkg); + } + } + + return byteQueue; + } + + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleBaseCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleBaseCallback.java new file mode 100644 index 0000000..cec9551 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleBaseCallback.java @@ -0,0 +1,27 @@ +package com.clj.fastble.callback; + + +import android.os.Handler; + +public abstract class BleBaseCallback { + + private String key; + private Handler handler; + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Handler getHandler() { + return handler; + } + + public void setHandler(Handler handler) { + this.handler = handler; + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleGattCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleGattCallback.java new file mode 100644 index 0000000..f98d39c --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleGattCallback.java @@ -0,0 +1,24 @@ + +package com.clj.fastble.callback; + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothGatt; +import android.bluetooth.BluetoothGattCallback; +import android.os.Build; + +import com.clj.fastble.data.BleDevice; +import com.clj.fastble.exception.BleException; + + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public abstract class BleGattCallback extends BluetoothGattCallback { + + public abstract void onStartConnect(); + + public abstract void onConnectFail(BleDevice bleDevice, BleException exception); + + public abstract void onConnectSuccess(BleDevice bleDevice, BluetoothGatt gatt, int status); + + public abstract void onDisConnected(boolean isActiveDisConnected, BleDevice device, BluetoothGatt gatt, int status); + +} \ No newline at end of file diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleIndicateCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleIndicateCallback.java new file mode 100644 index 0000000..7265152 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleIndicateCallback.java @@ -0,0 +1,13 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.exception.BleException; + +public abstract class BleIndicateCallback extends BleBaseCallback{ + + public abstract void onIndicateSuccess(); + + public abstract void onIndicateFailure(BleException exception); + + public abstract void onCharacteristicChanged(byte[] data); +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleMtuChangedCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleMtuChangedCallback.java new file mode 100644 index 0000000..4e83dc5 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleMtuChangedCallback.java @@ -0,0 +1,12 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.exception.BleException; + +public abstract class BleMtuChangedCallback extends BleBaseCallback { + + public abstract void onSetMTUFailure(BleException exception); + + public abstract void onMtuChanged(int mtu); + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleNotifyCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleNotifyCallback.java new file mode 100644 index 0000000..98012f1 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleNotifyCallback.java @@ -0,0 +1,14 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.exception.BleException; + +public abstract class BleNotifyCallback extends BleBaseCallback { + + public abstract void onNotifySuccess(); + + public abstract void onNotifyFailure(BleException exception); + + public abstract void onCharacteristicChanged(byte[] data); + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleReadCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleReadCallback.java new file mode 100644 index 0000000..06a4aed --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleReadCallback.java @@ -0,0 +1,12 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.exception.BleException; + +public abstract class BleReadCallback extends BleBaseCallback { + + public abstract void onReadSuccess(byte[] data); + + public abstract void onReadFailure(BleException exception); + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleRssiCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleRssiCallback.java new file mode 100644 index 0000000..6bcea1b --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleRssiCallback.java @@ -0,0 +1,12 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.exception.BleException; + +public abstract class BleRssiCallback extends BleBaseCallback{ + + public abstract void onRssiFailure(BleException exception); + + public abstract void onRssiSuccess(int rssi); + +} \ No newline at end of file diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanAndConnectCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanAndConnectCallback.java new file mode 100644 index 0000000..9e0b08b --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanAndConnectCallback.java @@ -0,0 +1,13 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.data.BleDevice; + +public abstract class BleScanAndConnectCallback extends BleGattCallback implements BleScanPresenterImp { + + public abstract void onScanFinished(BleDevice scanResult); + + public void onLeScan(BleDevice bleDevice) { + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanCallback.java new file mode 100644 index 0000000..cf8c2b8 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanCallback.java @@ -0,0 +1,14 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.data.BleDevice; + +import java.util.List; + +public abstract class BleScanCallback implements BleScanPresenterImp { + + public abstract void onScanFinished(List scanResultList); + + public void onLeScan(BleDevice bleDevice) { + } +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanPresenterImp.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanPresenterImp.java new file mode 100644 index 0000000..03cc2b5 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleScanPresenterImp.java @@ -0,0 +1,11 @@ +package com.clj.fastble.callback; + +import com.clj.fastble.data.BleDevice; + +public interface BleScanPresenterImp { + + void onScanStarted(boolean success); + + void onScanning(BleDevice bleDevice); + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/callback/BleWriteCallback.java b/FastBleLib/src/main/java/com/clj/fastble/callback/BleWriteCallback.java new file mode 100644 index 0000000..decf690 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/callback/BleWriteCallback.java @@ -0,0 +1,12 @@ +package com.clj.fastble.callback; + + +import com.clj.fastble.exception.BleException; + +public abstract class BleWriteCallback extends BleBaseCallback{ + + public abstract void onWriteSuccess(int current, int total, byte[] justWrite); + + public abstract void onWriteFailure(BleException exception); + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/data/BleConnectStateParameter.java b/FastBleLib/src/main/java/com/clj/fastble/data/BleConnectStateParameter.java new file mode 100644 index 0000000..880cd6d --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/data/BleConnectStateParameter.java @@ -0,0 +1,29 @@ +package com.clj.fastble.data; + + +public class BleConnectStateParameter { + + private int status; + private boolean isActive; + + public BleConnectStateParameter(int status) { + this.status = status; + } + + public int getStatus() { + return status; + } + + public void setStatus(int status) { + this.status = status; + } + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean active) { + isActive = active; + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/data/BleDevice.java b/FastBleLib/src/main/java/com/clj/fastble/data/BleDevice.java new file mode 100644 index 0000000..03fcd0e --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/data/BleDevice.java @@ -0,0 +1,112 @@ +package com.clj.fastble.data; + + +import android.bluetooth.BluetoothDevice; +import android.os.Parcel; +import android.os.Parcelable; + + +public class BleDevice implements Parcelable { + + private BluetoothDevice mDevice; + private byte[] mScanRecord; + private int mRssi; + private long mTimestampNanos; + + public BleDevice(BluetoothDevice device) { + mDevice = device; + } + + public BleDevice(BluetoothDevice device, int rssi, byte[] scanRecord, long timestampNanos) { + mDevice = device; + mScanRecord = scanRecord; + mRssi = rssi; + mTimestampNanos = timestampNanos; + } + + protected BleDevice(Parcel in) { + mDevice = in.readParcelable(BluetoothDevice.class.getClassLoader()); + mScanRecord = in.createByteArray(); + mRssi = in.readInt(); + mTimestampNanos = in.readLong(); + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(mDevice, flags); + dest.writeByteArray(mScanRecord); + dest.writeInt(mRssi); + dest.writeLong(mTimestampNanos); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public BleDevice createFromParcel(Parcel in) { + return new BleDevice(in); + } + + @Override + public BleDevice[] newArray(int size) { + return new BleDevice[size]; + } + }; + + public String getName() { + if (mDevice != null) { + return mDevice.getName(); + } + return null; + } + + public String getMac() { + if (mDevice != null) { + return mDevice.getAddress(); + } + return null; + } + + public String getKey() { + if (mDevice != null) { + return mDevice.getName() + mDevice.getAddress(); + } + return ""; + } + + public BluetoothDevice getDevice() { + return mDevice; + } + + public void setDevice(BluetoothDevice device) { + this.mDevice = device; + } + + public byte[] getScanRecord() { + return mScanRecord; + } + + public void setScanRecord(byte[] scanRecord) { + this.mScanRecord = scanRecord; + } + + public int getRssi() { + return mRssi; + } + + public void setRssi(int rssi) { + this.mRssi = rssi; + } + + public long getTimestampNanos() { + return mTimestampNanos; + } + + public void setTimestampNanos(long timestampNanos) { + this.mTimestampNanos = timestampNanos; + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/data/BleMsg.java b/FastBleLib/src/main/java/com/clj/fastble/data/BleMsg.java new file mode 100644 index 0000000..c7b533c --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/data/BleMsg.java @@ -0,0 +1,60 @@ +package com.clj.fastble.data; + + + +public class BleMsg { + + // Scan + public static final int MSG_SCAN_DEVICE = 0X00; + + // Connect + public static final int MSG_CONNECT_FAIL = 0x01; + public static final int MSG_DISCONNECTED = 0x02; + public static final int MSG_RECONNECT = 0x03; + public static final int MSG_DISCOVER_SERVICES = 0x04; + public static final int MSG_DISCOVER_FAIL = 0x05; + public static final int MSG_DISCOVER_SUCCESS = 0x06; + public static final int MSG_CONNECT_OVER_TIME = 0x07; + + // Notify + public static final int MSG_CHA_NOTIFY_START = 0x11; + public static final int MSG_CHA_NOTIFY_RESULT = 0x12; + public static final int MSG_CHA_NOTIFY_DATA_CHANGE = 0x13; + public static final String KEY_NOTIFY_BUNDLE_STATUS = "notify_status"; + public static final String KEY_NOTIFY_BUNDLE_VALUE = "notify_value"; + + // Indicate + public static final int MSG_CHA_INDICATE_START = 0x21; + public static final int MSG_CHA_INDICATE_RESULT = 0x22; + public static final int MSG_CHA_INDICATE_DATA_CHANGE = 0x23; + public static final String KEY_INDICATE_BUNDLE_STATUS = "indicate_status"; + public static final String KEY_INDICATE_BUNDLE_VALUE = "indicate_value"; + + // Write + public static final int MSG_CHA_WRITE_START = 0x31; + public static final int MSG_CHA_WRITE_RESULT = 0x32; + public static final int MSG_SPLIT_WRITE_NEXT = 0x33; + public static final String KEY_WRITE_BUNDLE_STATUS = "write_status"; + public static final String KEY_WRITE_BUNDLE_VALUE = "write_value"; + + // Read + public static final int MSG_CHA_READ_START = 0x41; + public static final int MSG_CHA_READ_RESULT = 0x42; + public static final String KEY_READ_BUNDLE_STATUS = "read_status"; + public static final String KEY_READ_BUNDLE_VALUE = "read_value"; + + // Rssi + public static final int MSG_READ_RSSI_START = 0x51; + public static final int MSG_READ_RSSI_RESULT = 0x52; + public static final String KEY_READ_RSSI_BUNDLE_STATUS = "rssi_status"; + public static final String KEY_READ_RSSI_BUNDLE_VALUE = "rssi_value"; + + // Mtu + public static final int MSG_SET_MTU_START = 0x61; + public static final int MSG_SET_MTU_RESULT = 0x62; + public static final String KEY_SET_MTU_BUNDLE_STATUS = "mtu_status"; + public static final String KEY_SET_MTU_BUNDLE_VALUE = "mtu_value"; + + + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/data/BleScanState.java b/FastBleLib/src/main/java/com/clj/fastble/data/BleScanState.java new file mode 100644 index 0000000..149d080 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/data/BleScanState.java @@ -0,0 +1,18 @@ +package com.clj.fastble.data; + + +public enum BleScanState { + + STATE_IDLE(-1), + STATE_SCANNING(0X01); + + private int code; + + BleScanState(int code) { + this.code = code; + } + + public int getCode() { + return code; + } +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/data/BleWriteState.java b/FastBleLib/src/main/java/com/clj/fastble/data/BleWriteState.java new file mode 100644 index 0000000..adae319 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/data/BleWriteState.java @@ -0,0 +1,8 @@ +package com.clj.fastble.data; + + + +public class BleWriteState { + + public static final int DATA_WRITE_SINGLE = 1; +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/exception/BleException.java b/FastBleLib/src/main/java/com/clj/fastble/exception/BleException.java new file mode 100644 index 0000000..c9caf4d --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/exception/BleException.java @@ -0,0 +1,47 @@ +package com.clj.fastble.exception; + +import java.io.Serializable; + + +public abstract class BleException implements Serializable { + + private static final long serialVersionUID = 8004414918500865564L; + + public static final int ERROR_CODE_TIMEOUT = 100; + public static final int ERROR_CODE_GATT = 101; + public static final int ERROR_CODE_OTHER = 102; + + private int code; + private String description; + + public BleException(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public BleException setCode(int code) { + this.code = code; + return this; + } + + public String getDescription() { + return description; + } + + public BleException setDescription(String description) { + this.description = description; + return this; + } + + @Override + public String toString() { + return "BleException { " + + "code=" + code + + ", description='" + description + '\'' + + '}'; + } +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/exception/ConnectException.java b/FastBleLib/src/main/java/com/clj/fastble/exception/ConnectException.java new file mode 100644 index 0000000..d1244d9 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/exception/ConnectException.java @@ -0,0 +1,42 @@ +package com.clj.fastble.exception; + +import android.bluetooth.BluetoothGatt; + + +public class ConnectException extends BleException { + + private BluetoothGatt bluetoothGatt; + private int gattStatus; + + public ConnectException(BluetoothGatt bluetoothGatt, int gattStatus) { + super(ERROR_CODE_GATT, "Gatt Exception Occurred! "); + this.bluetoothGatt = bluetoothGatt; + this.gattStatus = gattStatus; + } + + public int getGattStatus() { + return gattStatus; + } + + public ConnectException setGattStatus(int gattStatus) { + this.gattStatus = gattStatus; + return this; + } + + public BluetoothGatt getBluetoothGatt() { + return bluetoothGatt; + } + + public ConnectException setBluetoothGatt(BluetoothGatt bluetoothGatt) { + this.bluetoothGatt = bluetoothGatt; + return this; + } + + @Override + public String toString() { + return "ConnectException{" + + "gattStatus=" + gattStatus + + ", bluetoothGatt=" + bluetoothGatt + + "} " + super.toString(); + } +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/exception/GattException.java b/FastBleLib/src/main/java/com/clj/fastble/exception/GattException.java new file mode 100644 index 0000000..7e77f6c --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/exception/GattException.java @@ -0,0 +1,28 @@ +package com.clj.fastble.exception; + + +public class GattException extends BleException { + + private int gattStatus; + + public GattException(int gattStatus) { + super(ERROR_CODE_GATT, "Gatt Exception Occurred! "); + this.gattStatus = gattStatus; + } + + public int getGattStatus() { + return gattStatus; + } + + public GattException setGattStatus(int gattStatus) { + this.gattStatus = gattStatus; + return this; + } + + @Override + public String toString() { + return "GattException{" + + "gattStatus=" + gattStatus + + "} " + super.toString(); + } +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/exception/OtherException.java b/FastBleLib/src/main/java/com/clj/fastble/exception/OtherException.java new file mode 100644 index 0000000..63cc5b2 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/exception/OtherException.java @@ -0,0 +1,10 @@ +package com.clj.fastble.exception; + + +public class OtherException extends BleException { + + public OtherException(String description) { + super(ERROR_CODE_OTHER, description); + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/exception/TimeoutException.java b/FastBleLib/src/main/java/com/clj/fastble/exception/TimeoutException.java new file mode 100644 index 0000000..668bdc2 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/exception/TimeoutException.java @@ -0,0 +1,10 @@ +package com.clj.fastble.exception; + + +public class TimeoutException extends BleException { + + public TimeoutException() { + super(ERROR_CODE_TIMEOUT, "Timeout Exception Occurred!"); + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanPresenter.java b/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanPresenter.java new file mode 100644 index 0000000..c788a5e --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanPresenter.java @@ -0,0 +1,229 @@ +package com.clj.fastble.scan; + + +import android.annotation.TargetApi; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.text.TextUtils; + +import com.clj.fastble.callback.BleScanPresenterImp; +import com.clj.fastble.data.BleDevice; +import com.clj.fastble.data.BleMsg; +import com.clj.fastble.utils.BleLog; +import com.clj.fastble.utils.HexUtil; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public abstract class BleScanPresenter implements BluetoothAdapter.LeScanCallback { + + private String[] mDeviceNames; + private String mDeviceMac; + private boolean mFuzzy; + private boolean mNeedConnect; + private long mScanTimeout; + private BleScanPresenterImp mBleScanPresenterImp; + + private final List mBleDeviceList = new ArrayList<>(); + + private final Handler mMainHandler = new Handler(Looper.getMainLooper()); + private HandlerThread mHandlerThread; + private Handler mHandler; + private boolean mHandling; + + private static final class ScanHandler extends Handler { + + private final WeakReference mBleScanPresenter; + + ScanHandler(Looper looper, BleScanPresenter bleScanPresenter) { + super(looper); + mBleScanPresenter = new WeakReference<>(bleScanPresenter); + } + + @Override + public void handleMessage(Message msg) { + BleScanPresenter bleScanPresenter = mBleScanPresenter.get(); + if (bleScanPresenter != null) { + if (msg.what == BleMsg.MSG_SCAN_DEVICE) { + final BleDevice bleDevice = (BleDevice) msg.obj; + if (bleDevice != null) { + bleScanPresenter.handleResult(bleDevice); + } + } + } + } + } + + private void handleResult(final BleDevice bleDevice) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + onLeScan(bleDevice); + } + }); + checkDevice(bleDevice); + } + + public void prepare(String[] names, String mac, boolean fuzzy, boolean needConnect, + long timeOut, BleScanPresenterImp bleScanPresenterImp) { + mDeviceNames = names; + mDeviceMac = mac; + mFuzzy = fuzzy; + mNeedConnect = needConnect; + mScanTimeout = timeOut; + mBleScanPresenterImp = bleScanPresenterImp; + + mHandlerThread = new HandlerThread(BleScanPresenter.class.getSimpleName()); + mHandlerThread.start(); + mHandler = new ScanHandler(mHandlerThread.getLooper(), this); + mHandling = true; + } + + public boolean ismNeedConnect() { + return mNeedConnect; + } + + public BleScanPresenterImp getBleScanPresenterImp() { + return mBleScanPresenterImp; + } + + @Override + public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) { + if (device == null) + return; + + if (!mHandling) + return; + + Message message = mHandler.obtainMessage(); + message.what = BleMsg.MSG_SCAN_DEVICE; + message.obj = new BleDevice(device, rssi, scanRecord, System.currentTimeMillis()); + mHandler.sendMessage(message); + } + + private void checkDevice(BleDevice bleDevice) { + if (TextUtils.isEmpty(mDeviceMac) && (mDeviceNames == null || mDeviceNames.length < 1)) { + correctDeviceAndNextStep(bleDevice); + return; + } + + if (!TextUtils.isEmpty(mDeviceMac)) { + if (!mDeviceMac.equalsIgnoreCase(bleDevice.getMac())) + return; + } + + if (mDeviceNames != null && mDeviceNames.length > 0) { + AtomicBoolean equal = new AtomicBoolean(false); + for (String name : mDeviceNames) { + String remoteName = bleDevice.getName(); + if (remoteName == null) + remoteName = ""; + if (mFuzzy ? remoteName.contains(name) : remoteName.equals(name)) { + equal.set(true); + } + } + if (!equal.get()) { + return; + } + } + + correctDeviceAndNextStep(bleDevice); + } + + + private void correctDeviceAndNextStep(final BleDevice bleDevice) { + if (mNeedConnect) { + BleLog.i("devices detected ------" + + " name:" + bleDevice.getName() + + " mac:" + bleDevice.getMac() + + " Rssi:" + bleDevice.getRssi() + + " scanRecord:" + HexUtil.formatHexString(bleDevice.getScanRecord())); + + mBleDeviceList.add(bleDevice); + mMainHandler.post(new Runnable() { + @Override + public void run() { + BleScanner.getInstance().stopLeScan(); + } + }); + + } else { + AtomicBoolean hasFound = new AtomicBoolean(false); + for (BleDevice result : mBleDeviceList) { + if (result.getDevice().equals(bleDevice.getDevice())) { + hasFound.set(true); + } + } + if (!hasFound.get()) { + BleLog.i("device detected ------" + + " name: " + bleDevice.getName() + + " mac: " + bleDevice.getMac() + + " Rssi: " + bleDevice.getRssi() + + " scanRecord: " + HexUtil.formatHexString(bleDevice.getScanRecord(), true)); + + mBleDeviceList.add(bleDevice); + mMainHandler.post(new Runnable() { + @Override + public void run() { + onScanning(bleDevice); + } + }); + } + } + } + + public final void notifyScanStarted(final boolean success) { + mBleDeviceList.clear(); + + removeHandlerMsg(); + + if (success && mScanTimeout > 0) { + mMainHandler.postDelayed(new Runnable() { + @Override + public void run() { + BleScanner.getInstance().stopLeScan(); + } + }, mScanTimeout); + } + + mMainHandler.post(new Runnable() { + @Override + public void run() { + onScanStarted(success); + } + }); + } + + public final void notifyScanStopped() { + mHandling = false; + mHandlerThread.quit(); + removeHandlerMsg(); + mMainHandler.post(new Runnable() { + @Override + public void run() { + onScanFinished(mBleDeviceList); + } + }); + } + + public final void removeHandlerMsg() { + mMainHandler.removeCallbacksAndMessages(null); + mHandler.removeCallbacksAndMessages(null); + } + + public abstract void onScanStarted(boolean success); + + public abstract void onLeScan(BleDevice bleDevice); + + public abstract void onScanning(BleDevice bleDevice); + + public abstract void onScanFinished(List bleDeviceList); +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanRuleConfig.java b/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanRuleConfig.java new file mode 100644 index 0000000..e6d0eeb --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanRuleConfig.java @@ -0,0 +1,94 @@ +package com.clj.fastble.scan; + + +import com.clj.fastble.BleManager; + +import java.util.UUID; + +public class BleScanRuleConfig { + + private UUID[] mServiceUuids = null; + private String[] mDeviceNames = null; + private String mDeviceMac = null; + private boolean mAutoConnect = false; + private boolean mFuzzy = false; + private long mScanTimeOut = BleManager.DEFAULT_SCAN_TIME; + + public UUID[] getServiceUuids() { + return mServiceUuids; + } + + public String[] getDeviceNames() { + return mDeviceNames; + } + + public String getDeviceMac() { + return mDeviceMac; + } + + public boolean isAutoConnect() { + return mAutoConnect; + } + + public boolean isFuzzy() { + return mFuzzy; + } + + public long getScanTimeOut() { + return mScanTimeOut; + } + + public static class Builder { + + private UUID[] mServiceUuids = null; + private String[] mDeviceNames = null; + private String mDeviceMac = null; + private boolean mAutoConnect = false; + private boolean mFuzzy = false; + private long mTimeOut = BleManager.DEFAULT_SCAN_TIME; + + public Builder setServiceUuids(UUID[] uuids) { + this.mServiceUuids = uuids; + return this; + } + + public Builder setDeviceName(boolean fuzzy, String... name) { + this.mFuzzy = fuzzy; + this.mDeviceNames = name; + return this; + } + + public Builder setDeviceMac(String mac) { + this.mDeviceMac = mac; + return this; + } + + public Builder setAutoConnect(boolean autoConnect) { + this.mAutoConnect = autoConnect; + return this; + } + + public Builder setScanTimeOut(long timeOut) { + this.mTimeOut = timeOut; + return this; + } + + void applyConfig(BleScanRuleConfig config) { + config.mServiceUuids = this.mServiceUuids; + config.mDeviceNames = this.mDeviceNames; + config.mDeviceMac = this.mDeviceMac; + config.mAutoConnect = this.mAutoConnect; + config.mFuzzy = this.mFuzzy; + config.mScanTimeOut = this.mTimeOut; + } + + public BleScanRuleConfig build() { + BleScanRuleConfig config = new BleScanRuleConfig(); + applyConfig(config); + return config; + } + + } + + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanner.java b/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanner.java new file mode 100644 index 0000000..d9d47b9 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/scan/BleScanner.java @@ -0,0 +1,139 @@ +package com.clj.fastble.scan; + + +import android.annotation.TargetApi; +import android.os.Build; +import android.os.Handler; +import android.os.Looper; + +import com.clj.fastble.BleManager; +import com.clj.fastble.callback.BleScanAndConnectCallback; +import com.clj.fastble.callback.BleScanCallback; +import com.clj.fastble.callback.BleScanPresenterImp; +import com.clj.fastble.data.BleDevice; +import com.clj.fastble.data.BleScanState; +import com.clj.fastble.utils.BleLog; + +import java.util.List; +import java.util.UUID; + +@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2) +public class BleScanner { + + public static BleScanner getInstance() { + return BleScannerHolder.sBleScanner; + } + + private static class BleScannerHolder { + private static final BleScanner sBleScanner = new BleScanner(); + } + + private BleScanState mBleScanState = BleScanState.STATE_IDLE; + + private final BleScanPresenter mBleScanPresenter = new BleScanPresenter() { + + @Override + public void onScanStarted(boolean success) { + BleScanPresenterImp callback = mBleScanPresenter.getBleScanPresenterImp(); + if (callback != null) { + callback.onScanStarted(success); + } + } + + @Override + public void onLeScan(BleDevice bleDevice) { + if (mBleScanPresenter.ismNeedConnect()) { + BleScanAndConnectCallback callback = (BleScanAndConnectCallback) + mBleScanPresenter.getBleScanPresenterImp(); + if (callback != null) { + callback.onLeScan(bleDevice); + } + } else { + BleScanCallback callback = (BleScanCallback) mBleScanPresenter.getBleScanPresenterImp(); + if (callback != null) { + callback.onLeScan(bleDevice); + } + } + } + + @Override + public void onScanning(BleDevice result) { + BleScanPresenterImp callback = mBleScanPresenter.getBleScanPresenterImp(); + if (callback != null) { + callback.onScanning(result); + } + } + + @Override + public void onScanFinished(List bleDeviceList) { + if (mBleScanPresenter.ismNeedConnect()) { + final BleScanAndConnectCallback callback = (BleScanAndConnectCallback) + mBleScanPresenter.getBleScanPresenterImp(); + if (bleDeviceList == null || bleDeviceList.size() < 1) { + if (callback != null) { + callback.onScanFinished(null); + } + } else { + if (callback != null) { + callback.onScanFinished(bleDeviceList.get(0)); + } + final List list = bleDeviceList; + new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { + @Override + public void run() { + BleManager.getInstance().connect(list.get(0), callback); + } + }, 100); + } + } else { + BleScanCallback callback = (BleScanCallback) mBleScanPresenter.getBleScanPresenterImp(); + if (callback != null) { + callback.onScanFinished(bleDeviceList); + } + } + } + }; + + public void scan(UUID[] serviceUuids, String[] names, String mac, boolean fuzzy, + long timeOut, final BleScanCallback callback) { + + startLeScan(serviceUuids, names, mac, fuzzy, false, timeOut, callback); + } + + public void scanAndConnect(UUID[] serviceUuids, String[] names, String mac, boolean fuzzy, + long timeOut, BleScanAndConnectCallback callback) { + + startLeScan(serviceUuids, names, mac, fuzzy, true, timeOut, callback); + } + + private synchronized void startLeScan(UUID[] serviceUuids, String[] names, String mac, boolean fuzzy, + boolean needConnect, long timeOut, BleScanPresenterImp imp) { + + if (mBleScanState != BleScanState.STATE_IDLE) { + BleLog.w("scan action already exists, complete the previous scan action first"); + if (imp != null) { + imp.onScanStarted(false); + } + return; + } + + mBleScanPresenter.prepare(names, mac, fuzzy, needConnect, timeOut, imp); + + boolean success = BleManager.getInstance().getBluetoothAdapter() + .startLeScan(serviceUuids, mBleScanPresenter); + mBleScanState = success ? BleScanState.STATE_SCANNING : BleScanState.STATE_IDLE; + mBleScanPresenter.notifyScanStarted(success); + } + + public synchronized void stopLeScan() { + BleManager.getInstance().getBluetoothAdapter().stopLeScan(mBleScanPresenter); + mBleScanState = BleScanState.STATE_IDLE; + mBleScanPresenter.notifyScanStopped(); + } + + public BleScanState getScanState() { + return mBleScanState; + } + + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/utils/BleLog.java b/FastBleLib/src/main/java/com/clj/fastble/utils/BleLog.java new file mode 100644 index 0000000..38f17d6 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/utils/BleLog.java @@ -0,0 +1,31 @@ +package com.clj.fastble.utils; + + +import android.util.Log; + +public final class BleLog { + + public static boolean isPrint = true; + private static final String defaultTag = "FastBle"; + + public static void d(String msg) { + if (isPrint && msg != null) + Log.d(defaultTag, msg); + } + + public static void i(String msg) { + if (isPrint && msg != null) + Log.i(defaultTag, msg); + } + + public static void w(String msg) { + if (isPrint && msg != null) + Log.w(defaultTag, msg); + } + + public static void e(String msg) { + if (isPrint && msg != null) + Log.e(defaultTag, msg); + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/utils/BleLruHashMap.java b/FastBleLib/src/main/java/com/clj/fastble/utils/BleLruHashMap.java new file mode 100644 index 0000000..c273192 --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/utils/BleLruHashMap.java @@ -0,0 +1,34 @@ +package com.clj.fastble.utils; + + +import com.clj.fastble.bluetooth.BleBluetooth; + +import java.util.LinkedHashMap; + +public class BleLruHashMap extends LinkedHashMap { + + private final int MAX_SIZE; + + public BleLruHashMap(int saveSize) { + super((int) Math.ceil(saveSize / 0.75) + 1, 0.75f, true); + MAX_SIZE = saveSize; + } + + @Override + protected boolean removeEldestEntry(java.util.Map.Entry eldest) { + if (size() > MAX_SIZE && eldest.getValue() instanceof BleBluetooth) { + ((BleBluetooth) eldest.getValue()).disconnect(); + } + return size() > MAX_SIZE; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + for (Entry entry : entrySet()) { + sb.append(String.format("%s:%s ", entry.getKey(), entry.getValue())); + } + return sb.toString(); + } + +} diff --git a/FastBleLib/src/main/java/com/clj/fastble/utils/HexUtil.java b/FastBleLib/src/main/java/com/clj/fastble/utils/HexUtil.java new file mode 100644 index 0000000..b4cd41c --- /dev/null +++ b/FastBleLib/src/main/java/com/clj/fastble/utils/HexUtil.java @@ -0,0 +1,121 @@ +package com.clj.fastble.utils; + +public class HexUtil { + + private static final char[] DIGITS_LOWER = {'0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; + + private static final char[] DIGITS_UPPER = {'0', '1', '2', '3', '4', '5', + '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + public static char[] encodeHex(byte[] data) { + return encodeHex(data, true); + } + + public static char[] encodeHex(byte[] data, boolean toLowerCase) { + return encodeHex(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + protected static char[] encodeHex(byte[] data, char[] toDigits) { + if (data == null) + return null; + int l = data.length; + char[] out = new char[l << 1]; + for (int i = 0, j = 0; i < l; i++) { + out[j++] = toDigits[(0xF0 & data[i]) >>> 4]; + out[j++] = toDigits[0x0F & data[i]]; + } + return out; + } + + + public static String encodeHexStr(byte[] data) { + return encodeHexStr(data, true); + } + + public static String encodeHexStr(byte[] data, boolean toLowerCase) { + return encodeHexStr(data, toLowerCase ? DIGITS_LOWER : DIGITS_UPPER); + } + + + protected static String encodeHexStr(byte[] data, char[] toDigits) { + return new String(encodeHex(data, toDigits)); + } + + public static String formatHexString(byte[] data) { + return formatHexString(data, false); + } + + public static String formatHexString(byte[] data, boolean addSpace) { + if (data == null || data.length < 1) + return null; + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < data.length; i++) { + String hex = Integer.toHexString(data[i] & 0xFF); + if (hex.length() == 1) { + hex = '0' + hex; + } + sb.append(hex); + if (addSpace) + sb.append(" "); + } + return sb.toString().trim(); + } + + public static byte[] decodeHex(char[] data) { + int len = data.length; + + if ((len & 0x01) != 0) { + throw new RuntimeException("Odd number of characters."); + } + + byte[] out = new byte[len >> 1]; + + // two characters form the hex value. + for (int i = 0, j = 0; j < len; i++) { + int f = toDigit(data[j], j) << 4; + j++; + f = f | toDigit(data[j], j); + j++; + out[i] = (byte) (f & 0xFF); + } + + return out; + } + + + protected static int toDigit(char ch, int index) { + int digit = Character.digit(ch, 16); + if (digit == -1) { + throw new RuntimeException("Illegal hexadecimal character " + ch + + " at index " + index); + } + return digit; + } + + + public static byte[] hexStringToBytes(String hexString) { + if (hexString == null || hexString.equals("")) { + return null; + } + hexString = hexString.trim(); + hexString = hexString.toUpperCase(); + int length = hexString.length() / 2; + char[] hexChars = hexString.toCharArray(); + byte[] d = new byte[length]; + for (int i = 0; i < length; i++) { + int pos = i * 2; + d[i] = (byte) (charToByte(hexChars[pos]) << 4 | charToByte(hexChars[pos + 1])); + } + return d; + } + + public static byte charToByte(char c) { + return (byte) "0123456789ABCDEF".indexOf(c); + } + + public static String extractData(byte[] data, int position) { + return HexUtil.formatHexString(new byte[]{data[position]}); + } + +} diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/agconnect-services.json b/app/agconnect-services.json new file mode 100644 index 0000000..e69de29 diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..25b99c4 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,335 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-kapt' + id 'kotlin-parcelize' + id 'dagger.hilt.android.plugin' + //极光推送start +// id 'com.google.gms.google-services' +// id 'com.huawei.agconnect' + //极光推送end +} + +// Read values from gradle.properties or system environment variable(从gradle.properties里读取值) +def getStripeReleaseKey() { + return findProperty('STRIPE_RELEASE_KEY') ?: "" +} + +def getKey(def key) { + return findProperty(key) ?: "" +} + +android { + + compileSdkVersion 35 +// buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.abbidot.tracker" + minSdkVersion 23 + targetSdkVersion 35 + versionCode 2020 +// versionName "2.0.20" + versionName "2.0.20-Beta2" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + + ndk { + //选择要添加的对应 cpu 类型的 .so 库。 + abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64', 'mips', 'mips64' + } + + manifestPlaceholders = [ + GOOGLE_MAP_KEY : getKey("GOOGLE_MAP_KEY"), + BAIDU_MAP_KEY : getKey("BAIDU_MAP_KEY"), + STRIPE_RELEASE_KEY : getStripeReleaseKey(), + STRIPE_TEST_KEY : getKey("STRIPE_TEST_KEY"), + BUGLY_KEY : getKey("BUGLY_KEY"), + MAPBOX_ACCESS_TOKEN: getKey("MAPBOX_ACCESS_TOKEN"), + HTTP_REQUEST_KEY : getKey("HTTP_REQUEST_KEY"), + + JPUSH_PKGNAME : "${applicationId}", + JPUSH_APPKEY : "c968a96f76fc4f52e0f764ee", //JPush 上注册的包名对应的 Appkey. + JPUSH_CHANNEL : "developer-default", //暂时填写默认值即可. +// MEIZU_APPKEY : "MZ-魅族的APPKEY", +// MEIZU_APPID : "MZ-魅族的APPID", +// XIAOMI_APPID : "MI-小米的APPID", +// XIAOMI_APPKEY : "MI-小米的APPKEY", +// OPPO_APPKEY : "OP-oppo的APPKEY", +// OPPO_APPID : "OP-oppo的APPID", +// OPPO_APPSECRET: "OP-oppo的APPSECRET", +// VIVO_APPKEY : "vivo的APPKEY", +// VIVO_APPID : "vivo的APPID" + ] + } + + // 读取local.properties文件 + Properties properties = new Properties() + InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() + properties.load(inputStream) + // 读取签名文件 + def keyKeyStoreFile = file(properties.getProperty('keyStoreFile')) + // 读取签名文件别名、密码等信息 + def keyKeyStorePassword = properties.getProperty('keyStorePassword') + def keyKeyAlias = properties.getProperty('keyAlias') + def keyKeyAliasPassword = properties.getProperty('keyAliasPassword') + signingConfigs { + release { + storeFile keyKeyStoreFile + storePassword keyKeyStorePassword + keyAlias keyKeyAlias + keyPassword keyKeyAliasPassword + } + debug { + storeFile keyKeyStoreFile + storePassword keyKeyStorePassword + keyAlias keyKeyAlias + keyPassword keyKeyAliasPassword + } + } + buildTypes { +// release { +// minifyEnabled false +// proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' +// } + debug { +// multiDexKeepProguard file('multidex-config.pro') + debuggable true + jniDebuggable true + // 移除无用的资源文件 + shrinkResources false + // ZipAlign 优化 + zipAlignEnabled false + // 设置混淆 + minifyEnabled false + signingConfig signingConfigs.debug + } + + release { +// multiDexKeepProguard file('multidex-config.pro') + debuggable false + jniDebuggable false + // 移除无用的资源文件 + shrinkResources false + // ZipAlign 优化 + zipAlignEnabled true + // 设置混淆 + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig signingConfigs.release + } + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = '17' + } + + applicationVariants.all { variant -> + + // Apk 输出文件名配置 + variant.outputs.all { output -> + outputFileName = 'Tracker_V' + variant.versionName + '_' + new Date().format('yyyyMMddhhmmss') + '_' + variant.buildType.name +// if (variant.buildType.name == buildTypes.release.getName()) { +// outputFileName += '_' + new Date().format('yyyyMMddhhmmss') + '_' + variant.buildType.name +// } + outputFileName += '.apk' + } + } + + viewBinding { + enabled = true + } + lint { + abortOnError false + checkReleaseBuilds false + } + namespace 'com.abbidot.tracker' + buildFeatures { + viewBinding true + buildConfig = true + } + + //减少apk包的体积 + packagingOptions { + //压缩动态库(对应iniLibs的配置) + jniLibs { + useLegacyPackaging = true + } + //压缩dex(对应dex的配置) + dex { + useLegacyPackaging = true + } + } +} + +dependencies { + // 基类库 + implementation project(':baselibrary') + implementation project(':easyPhotos') + //蓝牙架包https://github.com/Jasonchenlijian/FastBle +// implementation 'com.github.Jasonchenlijian:FastBle:2.4.0' + implementation project(':FastBleLib') + //轻量级的Android端Socket框架https://github.com/jiusetian/EasySocket + implementation project(':easysocket') + +// implementation 'androidx.legacy:legacy-support-v4:1.0.0' +// implementation 'androidx.appcompat:appcompat:1.7.0' +// implementation 'com.google.android.material:material:1.10.0' +// implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + + // Android官方库 + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.arch.core:core-common:2.2.0' + implementation 'androidx.arch.core:core-runtime:2.2.0' + implementation 'androidx.activity:activity-ktx:1.9.3' + implementation 'androidx.fragment:fragment-ktx:1.5.6' + implementation 'androidx.annotation:annotation:1.9.1' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.7' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.7' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.7' + + implementation 'androidx.room:room-runtime:2.5.2' +// implementation 'androidx.room:room-rxjava2:2.3.0' + implementation 'androidx.room:room-ktx:2.5.2' + kapt 'androidx.room:room-compiler:2.5.2' + //hilt依赖注入https://mvnrepository.com/artifact/com.google.dagger/hilt-android-gradle-plugin + implementation "com.google.dagger:hilt-android:2.57" + kapt "com.google.dagger:hilt-android-compiler:2.57" + + //Android UI 开发效率的 UI 库https://github.com/Tencent/QMUI_Android + implementation 'com.qmuiteam:qmui:2.1.0' + implementation 'com.qmuiteam:arch:2.1.0' + kapt 'com.qmuiteam:arch-compiler:2.1.0' + + // 协程 + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2' + + // 图片框架 https://github.com/coil-kt/coil/blob/main/README-zh.md + implementation "io.coil-kt:coil:1.4.0" + //选择添加 + implementation "io.coil-kt:coil-gif:1.4.0"//支持GIF + + //lottie动画https://github.com/airbnb/lottie-android + implementation 'com.airbnb.android:lottie:6.6.7' + //https://github.com/CymChad/BaseRecyclerViewAdapterHelper + implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.11' + //升级recyclerview,因BaseRecyclerViewAdapterHelper item点击事件是使用1.2.0 + implementation 'androidx.recyclerview:recyclerview:1.3.1' + + //谷歌地图 + implementation 'com.google.android.gms:play-services-maps:19.2.0' + //地图操作工具类,添加标记等https://github.com/googlemaps/android-maps-utils +// implementation 'com.google.maps.android:android-maps-utils:2.3.0' +// implementation 'com.google.maps.android:maps-utils-ktx:3.2.1' + //谷歌地图places +// implementation 'com.google.android.libraries.places:places:2.5.0' + //连接谷歌服务定位,登录谷歌 +// implementation 'com.google.android.gms:play-services-auth:19.2.0' + //定位 + implementation 'com.google.android.gms:play-services-location:21.3.0' + + //动画https://github.com/daimajia/AndroidViewAnimations + implementation 'com.daimajia.androidanimations:library:2.4@aar' + + //mapbox地图https://github.com/mapbox/mapbox-maps-android +// implementation 'com.mapbox.maps:android:10.2.0' + //https://github.com/mapbox/mapbox-search-android + //用mapbox搜索SDK,地理编码 有搜索ui组件,已包括mapbox-search-android架包 + implementation "com.mapbox.search:mapbox-search-android-ui:2.5.1" +// mapbox搜索SDK,地理编码 没有搜索ui组件 +// implementation "com.mapbox.search:mapbox-search-android:1.0.0-beta.25" + //用于计算距离https://docs.mapbox.com/android/java/guides/turf/#available-methods + implementation 'com.mapbox.mapboxsdk:mapbox-sdk-turf:6.15.0' + //用于计算路线规划信息https://docs.mapbox.com/android/java/examples/dashed-directions-line/ + implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:6.3.0' + + // 权限请求框架:https://github.com/getActivity/XXPermissions + implementation 'com.github.getActivity:XXPermissions:25.2' + // 吐司框架:https://github.com/getActivity/ToastUtils + implementation 'com.github.getActivity:Toaster:13.2' + // 日志调试框架:https://github.com/getActivity/Logcat ,在debug模式下集成 + debugImplementation 'com.github.getActivity:Logcat:12.3' + + // PictureSelector图片选择器,需要compileSdkVersion=31 https://github.com/LuckSiege/PictureSelector +// implementation 'io.github.lucksiege:pictureselector:v3.0.3' + //拍照相册选择器、相册编辑添加水印 https://github.com/HuanTanSheng/EasyPhotos(没有适配Android13 相册) +// implementation 'com.github.HuanTanSheng:EasyPhotos:3.1.5' + //压缩图片 https://github.com/zetbaitsu/Compressor + implementation 'id.zelory:compressor:3.0.1' + //图片裁剪功能https://github.com/CanHub/Android-Image-Cropper + implementation 'com.vanniktech:android-image-cropper:4.6.0' + + //******************************************极光推送start***************************************************** +// https://docs.jiguang.cn/jpush/client/Android/android_guide +// implementation 'cn.jiguang.sdk:jcore:3.1.2' // 此处以JCore 2.7.2 版本为例。 +// implementation 'cn.jiguang.sdk:jpush:4.6.0' // 此处以JPush 4.0.0 版本为例 + implementation 'cn.jiguang.sdk:jcore-google:5.1.0' // 此处以JCore 2.7.2 版本为例。 + implementation 'cn.jiguang.sdk:jpush-google:5.8.0' // 此处以JPush 4.0.0 版本为例 + // 接入华为厂商 +// implementation 'com.huawei.hms:push:6.1.0.300' +// implementation 'cn.jiguang.sdk.plugin:huawei:4.0.0'// 极光厂商插件版本与接入 JPush 版本保持一致,下同 + // 接入 FCM 厂商 +// implementation 'com.google.firebase:firebase-messaging:23.0.0' +// implementation 'cn.jiguang.sdk.plugin:fcm:4.6.6' + // 接入魅族厂商 +// implementation 'cn.jiguang.sdk.plugin:meizu:4.6.0' + // 接入 VIVO 厂商 +// implementation 'cn.jiguang.sdk.plugin:vivo:4.6.0' + // 接入 OPPO 厂商 +// implementation 'cn.jiguang.sdk.plugin:oppo:4.6.0' + // 接入小米厂商 +// implementation 'cn.jiguang.sdk.plugin:xiaomi:4.6.0' + + //******************************************极光推送end******************************************************* + + //其中指代最新Bugly SDK版本号,也可以指定明确的版本号,例如4.0.0 + implementation 'com.tencent.bugly:crashreport:4.1.9.3' + //其中latest.release指代最新Bugly NDK版本号,也可以指定明确的版本号,例如3.9.2 +// implementation 'com.tencent.bugly:nativecrashreport:3.9.2' + + //braintree 信用卡支付https://github.com/braintree/braintree_android + implementation 'com.braintreepayments.api:card:4.31.0' + //braintree PayPal支付https://developer.paypal.com/braintree/docs/guides/client-sdk/migration/android/v4 + implementation 'com.braintreepayments.api:paypal:4.31.0' + //Stripe 支付https://github.com/stripe/stripe-android + implementation 'com.stripe:stripe-android:20.27.0' + + //百度地图基础定位组件 + implementation 'com.baidu.lbsyun:BaiduMapSDK_Location:9.6.5.1' + //地图组件 + implementation 'com.baidu.lbsyun:BaiduMapSDK_Map:7.5.2' + //检索组件 + implementation 'com.baidu.lbsyun:BaiduMapSDK_Search:7.5.2' + //工具组件 + implementation 'com.baidu.lbsyun:BaiduMapSDK_Util:7.5.2' + + //微信登录、支付 + implementation 'com.tencent.mm.opensdk:wechat-sdk-android:+' + //支付宝支付 + implementation 'com.alipay.sdk:alipaysdk-android:+@aar' + + //dfu升级https://github.com/NordicSemiconductor/Android-DFU-Library + implementation 'no.nordicsemi.android:dfu:2.9.0' + + //适配Android 12以下SplashScreen启动动画,闪屏图片 + implementation 'androidx.core:core-splashscreen:1.0.1' + //优雅、万能自定义日历https://github.com/huanghaibin-dev/CalendarView +// implementation 'com.haibin:calendarview:3.7.1' + //https://github.com/angcyo/CalendarView + implementation 'com.github.angcyo:CalendarView:3.7.1.43' + //Google出品的流式布局 flexbox-layout 支持RecyclerView https://gitcode.com/google/flexbox-layout?utm_source=csdn_github_accelerator + implementation 'com.google.android.flexbox:flexbox:3.0.0' + //facebook分享sdk +// implementation 'com.facebook.android:facebook-android-sdk:latest.release' + +} \ No newline at end of file diff --git a/app/google-services.json b/app/google-services.json new file mode 100644 index 0000000..e69de29 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/abbidot/tracker/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/abbidot/tracker/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..2da2670 --- /dev/null +++ b/app/src/androidTest/java/com/abbidot/tracker/ExampleInstrumentedTest.kt @@ -0,0 +1,23 @@ +package com.abbidot.tracker + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.abbidot.tracker", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..487038a --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,610 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/annual_protection_plan_en.html b/app/src/main/assets/annual_protection_plan_en.html new file mode 100644 index 0000000..41bcca3 --- /dev/null +++ b/app/src/main/assets/annual_protection_plan_en.html @@ -0,0 +1,42 @@ + + +
+ +

Annual Replacement Protection Plan - Terms & Conditions

+

1. Plan Coverage & Pricing

+

Annual Fee: $12.90 per year

+

Coverage: Provides one free replacement per year for a damaged ABBIDOT Tracker

+

Multi-Year Coverage:

+

Purchasing 1 year entitles you to 1 replacement claim.

+

Purchasing 2 years entitles you to 2 replacement claims (one per year).

+

This pattern continues for longer plans (e.g., 3 years = 3 claims, one per year).

+ +

2. Claim Process

+

To file a claim, submit the following via the ABBIDOT Support Portal:

+

Proof of damage/loss (e.g., photos, police report for lost devices).

+

Original purchase receipt and protection plan confirmation.

+

All claims are subject to verification. Fraudulent claims will result in plan termination without refund.

+ +

3.Cases Not Covered:

+

Intentional Damage - Verified cases of deliberate destruction

+

Unauthorized Modifications - Damage caused by self-disassembly or non-official alterations

+ +

4. Replacement Terms

+

Replacements may include refurbished or new devices (same or equivalent model).

+

Customer covers any applicable shipping fees unless otherwise stated.

+

Manual Refund (After 48 Hours): For subscriptions older than 48 hours but within 1 month, refunds require manual approval. Eligibility: Only applicable to annual (1-year) subscriptions.

+ +

5. Cancellation & Refunds

+

Full refunds available within 48 hours of purchase.

+

By purchasing this plan, you agree to these terms.

+ +

For claims, contact: support@abbidot.com.

+ + +
+ + + + + + \ No newline at end of file diff --git a/app/src/main/assets/find_device.mp3 b/app/src/main/assets/find_device.mp3 new file mode 100644 index 0000000..dcc4eed Binary files /dev/null and b/app/src/main/assets/find_device.mp3 differ diff --git a/app/src/main/assets/fonts/Credit.ttf b/app/src/main/assets/fonts/Credit.ttf new file mode 100644 index 0000000..00abec8 Binary files /dev/null and b/app/src/main/assets/fonts/Credit.ttf differ diff --git a/app/src/main/assets/fonts/DinCond_Bold.otf b/app/src/main/assets/fonts/DinCond_Bold.otf new file mode 100644 index 0000000..9b6cd9d Binary files /dev/null and b/app/src/main/assets/fonts/DinCond_Bold.otf differ diff --git a/app/src/main/assets/fonts/Roboto_Bold.ttf b/app/src/main/assets/fonts/Roboto_Bold.ttf new file mode 100644 index 0000000..91ec212 Binary files /dev/null and b/app/src/main/assets/fonts/Roboto_Bold.ttf differ diff --git a/app/src/main/assets/fonts/Roboto_Regular.ttf b/app/src/main/assets/fonts/Roboto_Regular.ttf new file mode 100644 index 0000000..7d9a6c4 Binary files /dev/null and b/app/src/main/assets/fonts/Roboto_Regular.ttf differ diff --git a/app/src/main/assets/lottie/active_dog.json b/app/src/main/assets/lottie/active_dog.json new file mode 100644 index 0000000..957744b --- /dev/null +++ b/app/src/main/assets/lottie/active_dog.json @@ -0,0 +1 @@ +{"v":"5.7.8","fr":50,"ip":0,"op":25,"w":900,"h":900,"nm":"dog","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[423.50000000000006,667,0],"ix":2,"l":2},"a":{"a":0,"k":[-42.5,230,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[-265,230],[180,230]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.2901960784313726,0.2901960784313726,0.2901960784313726,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":13,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.280000095741,0.280000095741,0.280000095741,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":13.001,"s":[10]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"t":25,"s":[10]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[90]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":13.001,"s":[90]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100]},{"t":25,"s":[90]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":455,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"ear","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[-14]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":13.001,"s":[-14]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[0]},{"t":25,"s":[-14]}],"ix":10},"p":{"a":0,"k":[356.247,24.591,0],"ix":2,"l":2},"a":{"a":0,"k":[86.762,16.905,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[6.047,30.334],[6.448,-4.934],[10.079,-11.171],[-24.71,-24.977],[-15.609,17.99]],"o":[[-2.904,-14.527],[-12.074,9.24],[-19.879,22.036],[20.808,21.034],[23.082,-26.608]],"v":[[40.358,-57.369],[10.962,-59.197],[-22.458,-26.687],[-33.818,50.862],[35.446,37.976]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2901960784313726,0.2901960784313726,0.2901960784313726,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[58.778,72.146],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":455,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"l 4","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[316.531,353.859,0],"ix":2,"l":2},"a":{"a":0,"k":[20.132,95.184,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":3.001,"s":[{"i":[[-0.27,-10.314],[-10.025,-30.105],[-7.439,0.15]],"o":[[-1.35,16.534],[6.761,20.302],[14.884,-0.3]],"v":[[0.132,-75.184],[18.197,26.052],[41.118,67.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[6.845,-41.332],[-31.078,-20.171],[23.303,-11.608]],"o":[[-6.585,39.76],[22.699,14.732],[-45.697,-11.74]],"v":[[0.757,-68.684],[63.973,0.344],[65.868,50.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":31.001,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":39,"s":[{"i":[[13.707,-39.589],[-21.812,-30.118],[27.944,-1.871]],"o":[[-6.105,17.632],[14.221,19.637],[-45.697,-11.74]],"v":[[0.757,-68.684],[62.79,19.37],[52.868,56.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":58.999,"s":[{"i":[[13.707,-39.589],[-21.812,-30.118],[27.944,-1.871]],"o":[[-6.105,17.632],[14.221,19.637],[-45.697,-11.74]],"v":[[0.757,-68.684],[62.79,19.37],[52.868,56.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":71,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":79,"s":[{"i":[[13.707,-39.589],[-21.812,-30.118],[27.944,-1.871]],"o":[[-6.105,17.632],[14.221,19.637],[-45.697,-11.74]],"v":[[0.757,-68.684],[62.79,19.37],[52.868,56.184]],"c":false}]},{"t":85,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.937254961799,0.678431372549,0.011764706817,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[20.131,95.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":455,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"l 3","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.531,349.898,0],"ix":2,"l":2},"a":{"a":0,"k":[20.132,95.184,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":3.001,"s":[{"i":[[-0.27,-10.314],[-10.025,-30.105],[-7.439,0.15]],"o":[[-1.35,16.534],[6.761,20.302],[14.884,-0.3]],"v":[[0.132,-75.184],[18.197,26.052],[41.118,67.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":19,"s":[{"i":[[6.845,-41.332],[-31.078,-20.171],[23.303,-11.608]],"o":[[-6.585,39.76],[22.699,14.732],[-45.697,-11.74]],"v":[[0.757,-68.684],[63.973,0.344],[65.868,50.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":31.001,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":35,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":39,"s":[{"i":[[13.707,-39.589],[-21.812,-30.118],[27.944,-1.871]],"o":[[-6.105,17.632],[14.221,19.637],[-45.697,-11.74]],"v":[[0.757,-68.684],[62.79,19.37],[52.868,56.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":45,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":51,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":55,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":58.999,"s":[{"i":[[13.707,-39.589],[-21.812,-30.118],[27.944,-1.871]],"o":[[-6.105,17.632],[14.221,19.637],[-45.697,-11.74]],"v":[[0.757,-68.684],[62.79,19.37],[52.868,56.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":65,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":71,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":75,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":79,"s":[{"i":[[13.707,-39.589],[-21.812,-30.118],[27.944,-1.871]],"o":[[-6.105,17.632],[14.221,19.637],[-45.697,-11.74]],"v":[[0.757,-68.684],[62.79,19.37],[52.868,56.184]],"c":false}]},{"t":85,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.937254961799,0.678431372549,0.011764706817,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[20.131,95.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":455,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"body","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[472.81700000000006,392.929,0],"to":[0,2.333,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":6,"s":[472.81700000000006,406.929,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":13.001,"s":[472.81700000000006,392.929,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":20,"s":[472.81700000000006,406.929,0],"to":[0,0,0],"ti":[0,2.333,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":25,"s":[472.81700000000006,392.929,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":33.001,"s":[472.81700000000006,392.929,0],"to":[0,2.333,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":36,"s":[472.81700000000006,406.929,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":40,"s":[472.81700000000006,392.929,0],"to":[0,0,0],"ti":[0,-2.333,0]},{"t":46.0009765625,"s":[472.81700000000006,406.929,0]}],"ix":2,"l":2},"a":{"a":0,"k":[267.02,184.77,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[8.555,0],[0,8.325]],"o":[[0,8.325],[-8.548,0],[0,0]],"v":[[15.488,-7.533],[-0.001,7.533],[-15.488,-7.533]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.092349317962,0.075073608698,0.078402522966,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[415.17,77.396],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.421,-3.502],[-4.835,-2.281],[-0.136,0.299],[-1.231,9.972],[-21.503,0],[22.783,-0.837],[4.539,-9.943],[0.084,-0.129],[3.291,1.014],[4.926,7.141]],"o":[[3.1,4.497],[0.051,-0.293],[4.11,-9.001],[-17.676,-5.407],[23.326,0],[-1.53,10.963],[-0.071,0.155],[1.521,2.249],[-8.328,-2.567],[-2.446,-3.542]],"v":[[-22.248,18.173],[-10.232,28.441],[-10.029,27.57],[1.125,-1.66],[6.857,-37.428],[7.656,-0.868],[-4.284,30.93],[-4.554,31.293],[-8.039,36.414],[-27.994,21.533]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2901960784313726,0.2901960784313726,0.2901960784313726,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[503.35,96.036],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-87.495,-9.771],[-16.843,0.916],[10.453,-1.794],[0,0],[5.137,8.279],[-7.464,-12.46]],"o":[[10.186,1.137],[16.851,-0.93],[-1.716,4.068],[-94.107,4.76],[-5.127,-8.314],[7.45,12.475]],"v":[[27.892,52.422],[83.377,52.124],[85.177,12.003],[82.043,25.516],[-74.066,-45.245],[-92.765,-24.181]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2901960784313726,0.2901960784313726,0.2901960784313726,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[336.116,158.467],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":2,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[26.417,2.651],[-89.601,18.369],[-22.648,-0.436],[-7.509,1.867],[-1.047,13.715],[-29.532,9.413],[-18.615,-17.337],[0,0],[98.273,-0.709],[25.429,-17.466],[4.244,0.605],[29.493,9.692]],"o":[[-49.64,-13.417],[21.621,-4.888],[7.736,0.149],[24.926,-6.195],[4.432,-57.988],[29.532,-9.416],[47.504,57.771],[0,0],[-19.611,125.548],[-54.196,29.538],[-30.727,-4.405],[-33.152,-10.891]],"v":[[-210.894,149.753],[-168.846,-1.502],[-69.847,-5.473],[-46.879,-8.056],[-4.482,-37.078],[76.472,-171.462],[135.534,-167.183],[257.081,-112.404],[162.26,-22.147],[81.781,154.982],[-39.346,174.857],[-130.154,153.813]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.935686178768,0.677187452129,0.012475915049,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[260.784,184.77],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 4","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":455,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"l 2","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[346.531,353.859,0],"ix":2,"l":2},"a":{"a":0,"k":[20.132,95.184,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":-10,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":-6.999,"s":[{"i":[[-0.27,-10.314],[-10.025,-30.105],[-7.439,0.15]],"o":[[-1.35,16.534],[6.761,20.302],[14.884,-0.3]],"v":[[0.132,-75.184],[18.197,26.052],[41.118,67.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-3.999,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8.999,"s":[{"i":[[6.845,-41.332],[-31.078,-20.171],[23.303,-11.608]],"o":[[-6.585,39.76],[22.699,14.732],[-45.697,-11.74]],"v":[[0.757,-68.684],[63.973,0.344],[65.868,50.184]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":15,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":17.999,"s":[{"i":[[-0.27,-10.314],[-10.025,-30.105],[-7.439,0.15]],"o":[[-1.35,16.534],[6.761,20.302],[14.884,-0.3]],"v":[[0.132,-75.184],[18.197,26.052],[41.118,67.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":21,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":34,"s":[{"i":[[6.845,-41.332],[-31.078,-20.171],[23.303,-11.608]],"o":[[-6.585,39.76],[22.699,14.732],[-45.697,-11.74]],"v":[[0.757,-68.684],[63.973,0.344],[65.868,50.184]],"c":false}]},{"t":40,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8901960784313725,0.5882352941176471,0.08627450980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[20.131,95.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":436,"st":-20,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"l","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[75.531,349.898,0],"ix":2,"l":2},"a":{"a":0,"k":[20.132,95.184,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":-20,"s":[{"i":[[-0.54,-20.628],[-16.404,-26.858],[-18.849,0.38]],"o":[[-2.23,27.314],[16.404,26.858],[29.767,-0.6]],"v":[[0.132,-75.184],[25.441,12.77],[82.368,59.184]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":-10,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":-6.999,"s":[{"i":[[-0.27,-10.314],[-10.025,-30.105],[-7.439,0.15]],"o":[[-1.35,16.534],[6.761,20.302],[14.884,-0.3]],"v":[[0.132,-75.184],[18.197,26.052],[41.118,67.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":-3.999,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":8.999,"s":[{"i":[[6.845,-41.332],[-31.078,-20.171],[23.303,-11.608]],"o":[[-6.585,39.76],[22.699,14.732],[-45.697,-11.74]],"v":[[0.757,-68.684],[63.973,0.344],[65.868,50.184]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":15,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.167,"y":0.167},"t":17.999,"s":[{"i":[[-0.27,-10.314],[-10.025,-30.105],[-7.439,0.15]],"o":[[-1.35,16.534],[6.761,20.302],[14.884,-0.3]],"v":[[0.132,-75.184],[18.197,26.052],[41.118,67.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":21,"s":[{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[0.132,-75.184],[-0.041,23.407],[-0.132,75.184]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":25,"s":[{"i":[[0,0],[9.466,-36.43],[16.992,2.263]],"o":[[0,0],[-6.172,23.752],[-43.154,-5.748]],"v":[[0.882,-73.309],[-8.478,10.886],[-41.882,51.309]],"c":false}]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":34,"s":[{"i":[[6.845,-41.332],[-31.078,-20.171],[23.303,-11.608]],"o":[[-6.585,39.76],[22.699,14.732],[-45.697,-11.74]],"v":[[0.757,-68.684],[63.973,0.344],[65.868,50.184]],"c":false}]},{"t":40,"s":[{"i":[[-0.54,-20.628],[-22.114,-28.266],[-14.878,0.3]],"o":[[-2.7,33.068],[14.418,18.429],[29.767,-0.6]],"v":[[0.132,-75.184],[36.435,28.698],[82.368,59.184]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8901960784313725,0.5882352941176471,0.08627450980392157,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[20.131,95.184],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":436,"st":-20,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"tail","parent":7,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-17]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":6,"s":[10]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":13.001,"s":[-17]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[10]},{"t":25,"s":[-17]}],"ix":10},"p":{"a":0,"k":[40.937,218.761,0],"ix":2,"l":2},"a":{"a":0,"k":[65.863,31.09,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-9.156,7.505],[-16.639,-7.872],[5.377,-6.122],[4.79,-0.871],[1.446,-0.978],[0.731,-0.835],[8.103,1.145],[7.288,6.21]],"o":[[15.08,-12.354],[8.114,3.844],[-2.411,2.742],[-0.845,1.215],[-0.413,1],[-4.334,4.932],[-11.063,-1.553],[-8.806,-7.506]],"v":[[-32.706,-11.486],[29.921,-11.431],[36.486,5.261],[24.912,10.96],[21.523,14.3],[19.85,17.091],[-0.816,22.695],[-32.553,12.168]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8901960784313725,0.5882352941176471,0.08627450980392157,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[42.112,24.09],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":455,"st":0,"bm":0}],"markers":[{"tm":0,"cm":"1","dr":0}]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/cat_and_ball.json b/app/src/main/assets/lottie/cat_and_ball.json new file mode 100644 index 0000000..29ccd9e --- /dev/null +++ b/app/src/main/assets/lottie/cat_and_ball.json @@ -0,0 +1 @@ +{"nm":"Lurking_Сat","ddd":0,"h":512,"w":512,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"Balls","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":true,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"masksProperties":[{"nm":"Mask 1","inv":false,"mode":"a","x":{"a":0,"k":0,"ix":4},"o":{"a":0,"k":100,"ix":3},"pt":{"a":0,"k":{"c":true,"i":[[141.385,0],[0,-39],[-12.348,-0.061],[-4.405,3.601],[-16.41,0],[-23.383,-0.074],[-8.519,-0.13],[0,5.211]],"o":[[-141.385,0],[0,4.625],[10.781,0.053],[25.845,0.226],[15.087,0],[5.492,3.426],[9.606,0.146],[0,-38]],"v":[[72,147],[-68,198],[-40.652,204.936],[-15.47,200.149],[70,200.375],[148.383,200.324],[173.144,205.104],[201,198]]},"ix":1}}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ball_1","ix":1,"cix":2,"np":3,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[14,14],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.7843,0.7843,0.7843],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[2,172],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 3","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[33,33],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.651,0.651,0.651],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,176.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":3,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[40,40],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.5216,0.5216,0.5216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,180],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[0,180],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-39.25,226.5],"t":0,"ti":[-0.583,4.708],"to":[0.208,-2.083]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-38,214],"t":1,"ti":[-1.25,4.667],"to":[0.583,-4.708]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-35.75,198.25],"t":2,"ti":[-2.042,2.875],"to":[1.25,-4.667]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-30.5,186],"t":3,"ti":[-3.083,1],"to":[2.042,-2.875]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":1},"s":[-23.5,181],"t":4,"ti":[-28.25,0.167],"to":[3.083,-1]},{"o":{"x":0.333,"y":0},"i":{"x":0.833,"y":0.833},"s":[-12,180],"t":5,"ti":[-27.833,0],"to":[28.25,-0.167]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[146,180],"t":34,"ti":[-2.291,0],"to":[4.721,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[158,183],"t":35,"ti":[0,0],"to":[11.216,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[167.5,193],"t":36,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[169.75,204.625],"t":37,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[170.875,216],"t":38,"ti":[0,0],"to":[0,0]},{"s":[172,227.75],"t":39}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ball_2","ix":2,"cix":2,"np":3,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[14,14],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.7843,0.7843,0.7843],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[2,172],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 3","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[33,33],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.651,0.651,0.651],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,176.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":3,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[40,40],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.5216,0.5216,0.5216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,180],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[0,180],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.419,"y":0.448},"i":{"x":0.833,"y":0.833},"s":[111.745,179.975],"t":0,"ti":[-7.256,0],"to":[14.708,0.014]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[146,180],"t":5,"ti":[-2.291,0],"to":[4.721,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[158,183],"t":6,"ti":[0,0],"to":[11.216,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[167.5,193],"t":7,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[169.75,204.625],"t":8,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[170.875,216],"t":9,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[172,227.75],"t":10,"ti":[35,2.292],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-39.25,226.5],"t":11,"ti":[-0.583,4.708],"to":[-35,-2.292]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-38,214],"t":12,"ti":[-1.25,4.667],"to":[0.583,-4.708]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-35.75,198.25],"t":13,"ti":[-2.042,2.875],"to":[1.25,-4.667]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-30.5,186],"t":14,"ti":[-3.083,1],"to":[2.042,-2.875]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":1},"s":[-23.5,181],"t":15,"ti":[-28.25,0.167],"to":[3.083,-1]},{"o":{"x":0.305,"y":0},"i":{"x":0.726,"y":0.611},"s":[-12,180],"t":16,"ti":[-41.71,-0.039],"to":[20.885,-0.123]},{"o":{"x":0.419,"y":0.448},"i":{"x":0.833,"y":0.833},"s":[111.745,179.975],"t":40,"ti":[-7.256,0],"to":[14.708,0.014]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[146,180],"t":45,"ti":[-2.291,0],"to":[4.721,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[158,183],"t":46,"ti":[0,0],"to":[11.216,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[167.5,193],"t":47,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[169.75,204.625],"t":48,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[170.875,216],"t":49,"ti":[0,0],"to":[0,0]},{"s":[172,227.75],"t":50}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ball_3","ix":3,"cix":2,"np":3,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[14,14],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.7843,0.7843,0.7843],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[2,172],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 3","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[33,33],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.651,0.651,0.651],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[1,176.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":3,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[40,40],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.5216,0.5216,0.5216],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,180],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[0,180],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.42,"y":0.368},"i":{"x":0.833,"y":0.833},"s":[36.081,179.926],"t":0,"ti":[-18.442,0],"to":[40.511,0.001]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[146,180],"t":16,"ti":[-2.291,0],"to":[4.721,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[158,183],"t":17,"ti":[0,0],"to":[11.216,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[167.5,193],"t":18,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[169.75,204.625],"t":19,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[170.875,216],"t":20,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[172,227.75],"t":21,"ti":[35,2.292],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-39.25,226.5],"t":22,"ti":[-0.583,4.708],"to":[-35,-2.292]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-38,214],"t":23,"ti":[-1.25,4.667],"to":[0.583,-4.708]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-35.75,198.25],"t":24,"ti":[-2.042,2.875],"to":[1.25,-4.667]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[-30.5,186],"t":25,"ti":[-3.083,1],"to":[2.042,-2.875]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":1},"s":[-23.5,181],"t":26,"ti":[-28.25,0.167],"to":[3.083,-1]},{"o":{"x":0.298,"y":0},"i":{"x":0.655,"y":0.439},"s":[-12,180],"t":27,"ti":[-20.63,0],"to":[9.532,-0.056]},{"o":{"x":0.42,"y":0.368},"i":{"x":0.833,"y":0.833},"s":[36.081,179.926],"t":40,"ti":[-18.442,0],"to":[40.511,0.001]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[146,180],"t":56,"ti":[-2.291,0],"to":[4.721,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[158,183],"t":57,"ti":[0,0],"to":[11.216,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[167.5,193],"t":58,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[169.75,204.625],"t":59,"ti":[0,0],"to":[0,0]},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[170.875,216],"t":60,"ti":[0,0],"to":[0,0]},{"s":[172,227.75],"t":61}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Burrow","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[54,14],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.4157,0.4157,0.4157],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[174,198],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[54,14],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.4157,0.4157,0.4157],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-41,198],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Cat_M","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[253.625,248.625,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":4,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 3","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[118.25,145.922],[116.375,157.625]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-23.75,-1],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 2","ix":2,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[118.375,144],[118.25,158.25]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-9.75,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":3,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[118.75,145.625],[120.375,157.375]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[3,-0.375],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Paw_L","ix":4,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-18,0],[0,-8.284],[13.807,0],[0,8.284]],"o":[[17.5,0],[0,8.284],[-13.807,0],[0,-8.284]],"v":[[-13,130.125],[12,150],[-13,158.875],[-38,150]]},"ix":2}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[-12,150],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.9019607843137255,0.9019607843137255,0.9019607843137255,0.5,0.615686274509804,0.615686274509804,0.615686274509804,1,0.5372549019607843,0.5372549019607843,0.5372549019607843],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[-12,120.5],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[122,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[109,144.5],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[109,144.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 2","ix":2,"cix":2,"np":4,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 3","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[118.25,145.922],[116.375,157.625]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-145.25,-1],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 2","ix":2,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[118.375,144],[118.25,158.25]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-131.25,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":3,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[118.75,145.625],[120.375,157.375]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-118.5,-0.375],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Paw_R","ix":4,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-18,0],[0,-8.284],[13.807,0],[0,8.284]],"o":[[17.5,0],[0,8.284],[-13.807,0],[0,-8.284]],"v":[[-13,130.125],[12,150],[-13,158.875],[-38,150]]},"ix":2}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[-12,150],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.9019607843137255,0.9019607843137255,0.9019607843137255,0.5,0.615686274509804,0.615686274509804,0.615686274509804,1,0.5372549019607843,0.5372549019607843,0.5372549019607843],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[-12,120.5],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[-13,144.5],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-13,144.5],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Head","ix":3,"cix":2,"np":8,"it":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Pupil_R","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[16,16],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.1725,0.1725,0.1176],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[119.617,119.617],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[12.674,117.014],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Pupil_L","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[16,16],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.1725,0.1725,0.1176],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[119.617,119.617],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[58.424,117.014],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Eye_R","ix":3,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[26,26],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[12.038,112.761],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Eye_L","ix":4,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[26,26],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[1,1,1],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[59.038,112.761],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Nous","ix":5,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[3.5,0],[-1.402,-1.816],[-1.275,-0.083],[-1.626,2.328]],"o":[[-3.5,0],[1.854,2.401],[1.138,0.074],[1.252,-1.793]],"v":[[51.25,124.375],[46.335,126.969],[51,131.125],[55.665,126.969]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.5137,0.5137,0.5137],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[51.032,127.445],"ix":1},"s":{"a":0,"k":[146.102,146.102],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[38.782,130.235],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ear_R","ix":6,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[6.019,3.225],[2.204,-6.427],[-8.575,-8.229],[-0.608,4.024]],"o":[[-4.063,-2.177],[-1.76,5.131],[6.787,6.513],[2.085,-13.805]],"v":[[61.573,58.281],[43.159,80.708],[56.204,93.339],[69.825,103.702]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.6471,0.6471,0.6471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[58.12,89.316],"ix":1},"s":{"a":0,"k":[-100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8.179,72.393],"ix":2},"r":{"a":0,"k":21.311,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ear_L","ix":7,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[6.019,3.225],[2.016,-6.488],[-8.515,-6.092],[-0.422,4.048]],"o":[[-4.063,-2.177],[-1.471,4.733],[8.391,6.002],[1.47,-14.115]],"v":[[60.805,57.44],[45.306,81.747],[56.107,91.948],[70.96,105.058]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.6471,0.6471,0.6471],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[57.265,86.752],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[50.449,70.288],"ix":2},"r":{"a":0,"k":-19.904,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Head_cont","ix":8,"cix":2,"np":5,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-31.127,-0.321],[-10.5,-19.5],[41.517,0.511],[0.221,27.321]],"o":[[28.215,0.291],[0.873,20.012],[-33.054,-0.407],[14.5,-21.5]],"v":[[-0.343,-39.62],[49,0],[0,36.5],[-52,0]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.4235,0.4235,0.4235],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[124.608,114.002],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[31,107],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tr","a":{"a":0,"k":[29.5,112.007],"ix":1},"s":{"a":0,"k":[120,120],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[42,112.007],"t":0,"ti":[0,0],"to":[-0.833,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[37,112.007],"t":20,"ti":[-0.833,0],"to":[0,0]},{"s":[42,112.007],"t":40}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Paw_2L","ix":4,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[{"c":false,"i":[[-2.621,13.477],[0,0],[0,0]],"o":[[1.75,-9],[0,0],[0,0]],"v":[[-20.875,143.125],[-23.375,120.875],[-71.875,60.875]]}],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[{"c":false,"i":[[-2.621,13.477],[0,0],[0,0]],"o":[[1.75,-9],[0,0],[0,0]],"v":[[-20.875,143.125],[-21.125,119.875],[-37.625,61.375]]}],"t":20},{"s":[{"c":false,"i":[[-2.621,13.477],[0,0],[0,0]],"o":[[1.75,-9],[0,0],[0,0]],"v":[[-20.875,143.125],[-23.375,120.875],[-71.875,60.875]]}],"t":40}],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"c":{"a":0,"k":[0.2902,0.2902,0.2627],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[136,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Paw_2R","ix":5,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[{"c":false,"i":[[13.336,3.263],[0,0],[0,0]],"o":[[-23.5,-5.75],[0,0],[0,0]],"v":[[-22.375,143.375],[-55.875,129.875],[-51.375,79.875]]}],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[{"c":false,"i":[[13.336,3.263],[0,0],[0,0]],"o":[[-23.5,-5.75],[0,0],[0,0]],"v":[[-22.375,143.375],[-54.375,125.375],[-28.375,71.375]]}],"t":20},{"s":[{"c":false,"i":[[13.336,3.263],[0,0],[0,0]],"o":[[-23.5,-5.75],[0,0],[0,0]],"v":[[-22.375,143.375],[-55.875,129.875],[-51.375,79.875]]}],"t":40}],"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":30,"ix":5},"c":{"a":0,"k":[0.2902,0.2902,0.2627],"ix":3}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Body","ix":6,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,42],"ix":3},"s":{"a":0,"k":[132,132],"ix":2}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[5.82,46.333],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.9019607843137255,0.9019607843137255,0.9019607843137255,0.5,0.7254901960784313,0.7254901960784313,0.7254901960784313,1,0.2901960784313726,0.2901960784313726,0.2627450980392157],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[-5.908,-34.167],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,107.75],"ix":1},"s":{"a":0,"k":[120,120],"ix":3},"sk":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-10],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[10],"t":20},{"s":[-10],"t":40}],"ix":4},"p":{"a":0,"k":[19.996,150],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ass 2","ix":7,"cix":2,"np":4,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[30.074,30.511],[-0.096,-45.043],[59.647,0],[0,59.647],[-15.943,6.605],[-6.201,-28.189],[-30.218,20.85],[-21.128,-14.372],[-17.54,27.841],[21.248,27.859],[-1.256,4.178]],"o":[[4.602,1.255],[0.127,59.647],[-59.647,0],[0,-38.576],[-27.264,31.073],[1.062,4.828],[-24.086,46.914],[28.268,19.228],[11.304,-17.942],[33.512,24.136],[5.756,-19.152]],"v":[[50.943,-78.213],[108,0],[0,108],[-108,0],[-52.568,-78.243],[-85.423,19.317],[-43.954,-53.659],[-38.42,47.521],[58.865,34.07],[46.562,-53.753],[88.469,4.429]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.2902,0.2902,0.2627],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,107.75],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[95.8,104.2],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[94,106],"t":3},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[106,94],"t":13},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[94,106],"t":23},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[106,94],"t":33},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[95.8,104.2],"t":40},{"s":[94,106],"t":43}],"ix":3},"sk":{"a":1,"k":[{"o":{"x":0.333,"y":0.63},"i":{"x":0.833,"y":1},"s":[-8.785],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-10],"t":3},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.769},"s":[10],"t":23},{"o":{"x":0.333,"y":0.63},"i":{"x":0.667,"y":1},"s":[-8.785],"t":40},{"s":[-10],"t":43}],"ix":4},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.22},"i":{"x":0.833,"y":1},"s":[-8.781,150],"t":0,"ti":[-3.13,0],"to":[0,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-10,150],"t":3,"ti":[0,0],"to":[3.13,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.769},"s":[10,150],"t":23,"ti":[0,0],"to":[0,0]},{"o":{"x":0.333,"y":0.628},"i":{"x":0.667,"y":1},"s":[-8.781,150],"t":40,"ti":[0,0],"to":[0,0]},{"s":[-10,150],"t":43}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ass","ix":8,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[-59.647,0],[0,-59.647],[59.647,0],[0,59.647]],"o":[[59.647,0],[0,59.647],[-59.647,0],[0,-59.647]],"v":[[0,-108],[108,0],[0,108],[-108,0]]},"ix":2}},{"ty":"gf","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Fill","nm":"Gradient Fill 1","e":{"a":0,"k":[90,-14],"ix":6},"g":{"p":3,"k":{"a":0,"k":[0,0.9019607843137255,0.9019607843137255,0.9019607843137255,0.5,0.7254901960784313,0.7254901960784313,0.7254901960784313,1,0.2901960784313726,0.2901960784313726,0.2627450980392157],"ix":9}},"t":2,"a":{"a":0,"k":0,"ix":8},"h":{"a":0,"k":0,"ix":7},"s":{"a":0,"k":[4,-82],"ix":5},"r":1,"o":{"a":0,"k":100,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,107.75],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[95.8,104.2],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[94,106],"t":3},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[106,94],"t":13},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[94,106],"t":23},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[106,94],"t":33},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[95.8,104.2],"t":40},{"s":[94,106],"t":43}],"ix":3},"sk":{"a":1,"k":[{"o":{"x":0.333,"y":0.63},"i":{"x":0.833,"y":1},"s":[-8.785],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-10],"t":3},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.769},"s":[10],"t":23},{"o":{"x":0.333,"y":0.63},"i":{"x":0.667,"y":1},"s":[-8.785],"t":40},{"s":[-10],"t":43}],"ix":4},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.22},"i":{"x":0.833,"y":1},"s":[-8.781,150],"t":0,"ti":[-3.13,0],"to":[0,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-10,150],"t":3,"ti":[0,0],"to":[3.13,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.769},"s":[10,150],"t":23,"ti":[0,0],"to":[0,0]},{"o":{"x":0.333,"y":0.628},"i":{"x":0.667,"y":1},"s":[-8.781,150],"t":40,"ti":[0,0],"to":[0,0]},{"s":[-10,150],"t":43}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Tail","ix":9,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[2.63,27.876],[12,17.5],[-27.5,-4]],"o":[[-2.5,-26.5],[-16.1,-23.479],[19.792,2.879]],"v":[[-1,-49],[-32.5,-107],[-2.5,-165.5]]}],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[4.94,27.059],[7,16.75],[-19.25,18.25]],"o":[[-5,-27],[-8.022,-19.219],[-8.75,-8.25]],"v":[[-1,-49],[-17.25,-105.25],[-24.75,-180.25]]}],"t":5},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[-0.5,28],[8,23],[-6,28.5]],"o":[[-0.5,-25],[-9.143,-26.285],[2.763,-13.125]],"v":[[-1,-49],[-2.5,-108.5],[-42,-168]]}],"t":10},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[-1.089,27.485],[-6.418,18.866],[27.5,-7.75]],"o":[[1,-27],[8.25,-24.25],[-7.876,-11.679]],"v":[[-1,-49],[12.25,-106.75],[-26,-152.25]]}],"t":15},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[-2.786,27.861],[-10.35,20.344],[29,-6.5]],"o":[[3,-30],[14.5,-28.5],[-19.516,4.374]],"v":[[-1,-49],[27,-107.5],[-3,-166]]}],"t":20},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[0.5,29.5],[-10.75,21.75],[26,1.125]],"o":[[0,-27],[10.868,-21.989],[-11.809,-1.27]],"v":[[-1,-49],[14.75,-113.25],[13.5,-178.625]]}],"t":25},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[1.926,27.272],[-9.765,20.227],[6,26.917]],"o":[[-0.5,-27.5],[14,-29],[-3.965,-6.248]],"v":[[-1,-49],[-4,-106],[32.5,-165.417]]}],"t":30},{"o":{"x":0.167,"y":0.167},"i":{"x":0.833,"y":0.833},"s":[{"c":false,"i":[[4.94,27.059],[7.75,22.25],[-26.75,-10.042]],"o":[[-2,-27.5],[-9.269,-26.612],[9.75,3.958]],"v":[[-1,-49],[-19.25,-107.25],[16.25,-152.958]]}],"t":35},{"s":[{"c":false,"i":[[2.63,27.876],[12,17.5],[-27.5,-4]],"o":[[-2.5,-26.5],[-16.1,-23.479],[19.792,2.879]],"v":[[-1,-49],[-32.5,-107],[-2.5,-165.5]]}],"t":40}],"ix":2}},{"ty":"gs","bm":0,"hd":false,"mn":"ADBE Vector Graphic - G-Stroke","nm":"Gradient Stroke 1","e":{"a":0,"k":[100,0],"ix":5},"g":{"p":3,"k":{"a":0,"k":[0,0.37254901960784315,0.37254901960784315,0.37254901960784315,0.5,0.6666666666666666,0.6666666666666666,0.6666666666666666,1,0.7647058823529411,0.7647058823529411,0.7647058823529411],"ix":8}},"t":2,"a":{"a":0,"k":0,"ix":7},"h":{"a":0,"k":0,"ix":6},"s":{"a":0,"k":[0,0],"ix":4},"lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":9},"w":{"a":0,"k":40,"ix":10}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.297},"i":{"x":0.833,"y":1},"s":[-64.519,0],"t":0,"ti":[-17.92,0],"to":[-7.598,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[-77,0],"t":4,"ti":[0,0],"to":[17.92,0]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.714},"s":[43,0],"t":24,"ti":[30.722,0],"to":[0,0]},{"o":{"x":0.333,"y":0.615},"i":{"x":0.667,"y":1},"s":[-64.519,0],"t":40,"ti":[0.103,0],"to":[-7.598,0]},{"s":[-77,0],"t":44}],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"BG","sr":1,"st":0,"op":300,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 2","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[382,110],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.6784,0.6784,0.6784],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0.63},"i":{"x":0.667,"y":1},"s":[64.751,70.383],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[65.104,70.383],"t":3},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.769},"s":[59.286,70.383],"t":23},{"o":{"x":0.333,"y":0.63},"i":{"x":0.667,"y":1},"s":[64.751,70.383],"t":40},{"s":[65.104,70.383],"t":43}],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0.21},"i":{"x":0.667,"y":1},"s":[10.607,119.621],"t":0,"ti":[-1.565,-0.313],"to":[-0.25,-0.05]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[10,119.5],"t":3,"ti":[0,0],"to":[1.565,0.313]},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":0.769},"s":[20,121.5],"t":23,"ti":[2.503,0.501],"to":[0,0]},{"o":{"x":0.333,"y":0.63},"i":{"x":0.667,"y":1},"s":[10.607,119.621],"t":40,"ti":[0.151,0.03],"to":[-0.25,-0.05]},{"s":[10,119.5],"t":43}],"ix":2},"r":{"a":0,"k":4.95,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":2,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,3],"ix":3},"s":{"a":0,"k":[430,123.822],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8353,0.8353,0.8353],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[8,122],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4}],"v":"5.7.5","fr":30,"op":40,"ip":0,"assets":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/dfu_loading.json b/app/src/main/assets/lottie/dfu_loading.json new file mode 100644 index 0000000..8c0132b --- /dev/null +++ b/app/src/main/assets/lottie/dfu_loading.json @@ -0,0 +1 @@ +{"nm":"Main Scene","ddd":0,"h":200,"w":380,"meta":{"g":"@lottiefiles/creator 1.31.1"},"layers":[{"ty":4,"nm":"Shape Layer 3","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50],"t":17},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75],"t":31},{"s":[50,50],"t":47}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[312,100],"t":17},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[312,60],"t":31},{"s":[312,100],"t":47}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":17},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":31},{"s":[25],"t":47}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.2863,0.298],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":1},{"ty":4,"nm":"Shape Layer 2","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50],"t":9},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75],"t":23},{"s":[50,50],"t":39}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[192,100],"t":9},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[192,60],"t":23},{"s":[192,100],"t":39}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":9},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":23},{"s":[25],"t":39}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.2863,0.298],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":2},{"ty":4,"nm":"Shape Layer 1","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[-284,92,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[50,50],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[75,75],"t":14},{"s":[50,50],"t":30}],"ix":6},"sk":{"a":0,"k":0},"p":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[72,100],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[72,60],"t":14},{"s":[72,100],"t":30}],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[25],"t":0},{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":14},{"s":[25],"t":30}],"ix":11}},"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Ellipse 1","ix":1,"cix":2,"np":3,"it":[{"ty":"el","bm":0,"hd":false,"mn":"ADBE Vector Shape - Ellipse","nm":"Ellipse Path 1","d":1,"p":{"a":0,"k":[0,0],"ix":3},"s":{"a":0,"k":[120,120],"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.0078,0.2863,0.298],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[-284,92],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3}],"v":"5.7.0","fr":60,"op":81,"ip":0,"assets":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/fail_anim.json b/app/src/main/assets/lottie/fail_anim.json new file mode 100644 index 0000000..91e39a2 --- /dev/null +++ b/app/src/main/assets/lottie/fail_anim.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.20","a":"","k":"","d":"","tc":""},"fr":24,"ip":0,"op":36,"w":64,"h":64,"nm":"Failed","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Dash-2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":45,"ix":10},"p":{"a":0,"k":[32.5,33,0],"ix":2},"a":{"a":0,"k":[-3.156,5.437,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[13,5.5],[-29.191,5.379]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0.698039215686,0.141176470588,0.235294117647,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[0.698039215686,0.141176470588,0.235294117647,1]},{"t":24,"s":[1,1,1,1]}],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 35","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[100]},{"t":28,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[100]},{"t":28,"s":[78]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Dash-1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-45,"ix":10},"p":{"a":0,"k":[32.5,33,0],"ix":2},"a":{"a":0,"k":[-3.156,5.437,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[13,5.5],[-29.191,5.379]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0.698039215686,0.141176470588,0.235294117647,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[0.698039215686,0.141176470588,0.235294117647,1]},{"t":24,"s":[1,1,1,1]}],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":4,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Vector 35","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100]},{"t":28,"s":[0]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":24,"s":[100]},{"t":28,"s":[78]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":120,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Outline","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,32,0],"ix":2},"a":{"a":0,"k":[16.383,-9.633,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.9568627450980393,0.2627450980392157,0.21176470588235294,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[16.383,-9.633],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[49]},{"t":16,"s":[87]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[0]},{"t":24,"s":[87]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":720,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Solid","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":16,"s":[0]},{"t":24,"s":[100]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[32,32,0],"ix":2},"a":{"a":0,"k":[16.383,-9.633,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16,"s":[0,0,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":24,"s":[110,110,100]},{"t":28,"s":[100,100,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[56,56],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"tr","p":{"a":0,"k":[16.383,-9.633],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9568627450980393,0.2627450980392157,0.21176470588235294,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":720,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/loading_dog.json b/app/src/main/assets/lottie/loading_dog.json new file mode 100644 index 0000000..2c1be45 --- /dev/null +++ b/app/src/main/assets/lottie/loading_dog.json @@ -0,0 +1 @@ +{"v":"5.7.5","fr":30,"ip":0,"op":21,"w":512,"h":512,"nm":"Dog","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Ear","parent":11,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":-37,"ix":10},"p":{"a":0,"k":[51.418,-85.546,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-2,"s":[{"i":[[0,0],[0,0]],"o":[[-16,-15],[0,0]],"v":[[-78,59.5],[-67.5,-5.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":-1,"s":[{"i":[[0,0],[0,0]],"o":[[-8.5,-38.434],[0,0]],"v":[[-78,59.5],[-114.75,38.95]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[-28.5,-34.5],[0,0]],"v":[[-78,59.5],[-124,67.4]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[0,0],[0,0]],"o":[[-25.375,-29.625],[0,0]],"v":[[-78,59.5],[-124.211,46.487]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0,0],[0,0]],"o":[[-33,-17.5],[0,0]],"v":[[-78,59.5],[-108.75,20.95]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":5,"s":[{"i":[[0,0],[0,0]],"o":[[-20.688,-22.312],[0,0]],"v":[[-78,59.5],[-100.938,8.088]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":8,"s":[{"i":[[0,0],[0,0]],"o":[[-16,-15],[0,0]],"v":[[-78,59.5],[-67.5,-5.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":15,"s":[{"i":[[0,0],[0,0]],"o":[[-16,-15],[0,0]],"v":[[-78,59.5],[-41.619,12.027]],"c":false}]},{"t":20,"s":[{"i":[[0,0],[0,0]],"o":[[-16,-15],[0,0]],"v":[[-78,59.5],[-125.832,17.436]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[5,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":0,"k":57,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Tail","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[0,0,0],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":0,"s":[{"i":[[0,0],[0,0]],"o":[[-16,-15],[0,0]],"v":[[-78,59.5],[-67.5,-5.5]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":1,"s":[{"i":[[0,0],[0,0]],"o":[[-8.5,-38.434],[0,0]],"v":[[-78,59.5],[-114.75,38.95]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":2,"s":[{"i":[[0,0],[0,0]],"o":[[-28.5,-34.5],[0,0]],"v":[[-78,59.5],[-124,67.4]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":4,"s":[{"i":[[0,0],[0,0]],"o":[[-25.375,-29.625],[0,0]],"v":[[-78,59.5],[-124.375,46.675]],"c":false}]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":6,"s":[{"i":[[0,0],[0,0]],"o":[[-33,-17.5],[0,0]],"v":[[-78,59.5],[-108.75,20.95]],"c":false}]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":7,"s":[{"i":[[0,0],[0,0]],"o":[[-20.688,-22.312],[0,0]],"v":[[-78,59.5],[-100.938,8.088]],"c":false}]},{"t":13,"s":[{"i":[[0,0],[0,0]],"o":[[-16,-15],[0,0]],"v":[[-78,59.5],[-67.5,-5.5]],"c":false}]}],"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":1,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Hose 2","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[-256,-256,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-241.934,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-180.066,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-166,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-166,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-168.52,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-175.361,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-185.444,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-197.69,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-224.319,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-236.558,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-246.64,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-253.48,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-256,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"k":[{"s":[0,0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"k":[{"s":[100,100,100],"t":0,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":20,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"k":[{"s":[4],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"r":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ir":{"k":[{"s":[30],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"is":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"or":{"k":[{"s":[34.22],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[28.775],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.608],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.715],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.018],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.273],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.961],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[33.961],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.964],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.273],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.018],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30.334],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.832],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.522],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.08],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[10.694],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.419],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.624],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[29.277],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.032],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.22],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"os":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ix":1,"nm":"LineForCurve","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"tm","s":{"k":[{"s":[0.01],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"e":{"k":[{"s":[24.99],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.99],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"k":[{"s":[-90],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-90],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"m":1,"ix":2,"nm":"Line Halfer","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"k":[{"s":[-34.22,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.775,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-21.608,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.715,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.018,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.273,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.961,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.961,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.964,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.273,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.018,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.334,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.832,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.522,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.08,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.694,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.419,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-22.624,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.277,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.032,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.22,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"k":[{"s":[-100,100],"t":0,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[-100,100],"t":20,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"r":{"k":[{"s":[6.691],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.284],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[72.181],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.769],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-218.627],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-217.52],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-214.839],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-211.517],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-214.829],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-217.521],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-218.627],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-219.684],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-223.289],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.326],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[107.576],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[62.346],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.449],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[10.793],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[5.184],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4.198],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6.691],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"sk":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"sa":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"nm":"Transform"}],"nm":"Arc","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[19],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"k":[{"s":[227,376.474],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[212.714,361.971],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[183.041,320.691],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[158.555,270.2],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[152,243.474],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.747,242.558],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.117,240.277],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[150.296,237.302],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.115,240.268],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.748,242.559],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[152,243.474],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[154.286,247.046],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[160.484,256.77],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[169.579,271.153],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[180.581,288.71],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[192.491,307.944],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[204.297,327.367],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[214.987,345.518],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[223.512,360.942],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[228.45,372.412],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[227,376.474],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"BaseHose","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Style","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hose 2::Ankle","np":0,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[135,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"A","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[200,200],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hose 2::Hip","np":0,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"B","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"2.07","np":0,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"cl":"07"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Version","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"k":[{"s":[4],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"r":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ir":{"k":[{"s":[30],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"is":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"or":{"k":[{"s":[34.22],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[28.775],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[21.608],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[27.715],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.018],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.273],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.961],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[33.961],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.964],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.273],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[32.018],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30.334],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.832],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19.522],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.08],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[10.694],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.419],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.624],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[29.277],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.032],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.22],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"os":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ix":1,"nm":"LineForCurve","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"tm","s":{"k":[{"s":[0.01],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"e":{"k":[{"s":[24.99],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.99],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"k":[{"s":[-90],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-90],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"m":1,"ix":2,"nm":"Line Halfer","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"k":[{"s":[-34.22,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.775,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-21.608,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-27.715,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.018,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.273,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.961,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.961,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.964,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.273,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-32.018,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.334,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-25.832,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-19.522,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.08,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-10.694,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-15.419,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-22.624,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.277,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.032,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.22,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"k":[{"s":[-100,100],"t":0,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[-100,100],"t":20,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"r":{"k":[{"s":[6.691],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[22.284],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[72.181],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[123.769],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-218.627],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-217.52],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-214.839],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-211.517],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-214.829],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-217.521],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-218.627],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-219.684],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-223.289],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[128.326],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[107.576],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[62.346],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[25.449],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[10.793],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[5.184],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4.198],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[6.691],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"ArcMath","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"k":[{"s":[227,376.474],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[212.714,361.971],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[183.041,320.691],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[158.555,270.2],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[152,243.474],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.747,242.558],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.117,240.277],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[150.296,237.302],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.115,240.268],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[151.748,242.559],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[152,243.474],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[154.286,247.046],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[160.484,256.77],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[169.579,271.153],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[180.581,288.71],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[192.491,307.944],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[204.297,327.367],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[214.987,345.518],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[223.512,360.942],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[228.45,372.412],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[227,376.474],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Admin","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Hose 1","parent":9,"sr":1,"ks":{"o":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"r":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[-256,-256,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-241.934,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-180.066,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-166,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-166,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-168.52,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-175.361,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-185.444,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-197.69,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-224.319,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-236.558,0],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-246.64,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-253.48,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-256,-256,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"a":{"k":[{"s":[0,0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}],"l":2},"s":{"k":[{"s":[100,100,100],"t":0,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}},{"s":[100,100,100],"t":20,"i":{"x":[1,1,1],"y":[1,1,1]},"o":{"x":[0,0,0],"y":[0,0,0]}}],"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"k":[{"s":[4],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"r":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ir":{"k":[{"s":[30],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"is":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"or":{"k":[{"s":[34.237],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[28.107],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.524],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[33.886],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.418],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.45],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.592],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.903],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.593],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.45],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.418],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.694],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30.098],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.681],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.055],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.542],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.458],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[29.364],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.146],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.237],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"os":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ix":1,"nm":"LineForCurve","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"tm","s":{"k":[{"s":[0.01],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"e":{"k":[{"s":[24.99],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.99],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"k":[{"s":[-90],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-90],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"m":1,"ix":2,"nm":"Line Halfer","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"k":[{"s":[-34.237,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.107,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.524,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.886,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.418,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.45,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.592,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.903,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.593,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.45,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.418,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.694,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.098,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-23.681,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-17.055,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.542,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-16.458,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.364,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.146,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.237,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"k":[{"s":[-100,100],"t":0,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[-100,100],"t":20,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"r":{"k":[{"s":[84.81],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[68.487],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.783],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-27.715],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-44.444],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-45.558],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-48.322],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-51.882],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-48.332],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-45.556],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-44.444],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-43.336],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-39.67],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-31.682],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-14.22],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.836],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[57.365],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[75.994],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[84.074],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[86.868],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[84.81],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"sk":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"sa":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"nm":"Transform"}],"nm":"Arc","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"st","c":{"a":0,"k":[1,1,1,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"k":[{"s":[19],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[19],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"k":[{"s":[257,375.694],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.158,360.89],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.929,323.777],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[333.758,276.805],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.5,249],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.546,247.998],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.662,245.501],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.812,242.247],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.662,245.492],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.546,247.999],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.5,249],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[337.044,252.445],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[330.381,261.815],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[320.59,275.659],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.722,292.533],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.839,310.98],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.016,329.554],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.317,346.829],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[261.825,361.378],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.77,371.941],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257,375.694],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"BaseHose","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Style","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hose 1::Ankle","np":0,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[135,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"A","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[200,200],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Hose 1::Hip","np":0,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"B","np":1,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"gr","it":[{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"2.07","np":0,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false,"cl":"07"},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Version","np":1,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"k":[{"s":[4],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[4],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"r":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ir":{"k":[{"s":[30],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"is":{"k":[{"s":[100],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[100],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"or":{"k":[{"s":[34.237],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[28.107],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.524],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[33.886],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.418],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.45],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.592],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.903],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.593],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.45],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[36.418],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.694],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[30.098],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[23.681],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[17.055],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[13.542],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[16.458],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[29.364],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.146],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[34.237],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"os":{"k":[{"s":[0],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"ix":1,"nm":"LineForCurve","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"tm","s":{"k":[{"s":[0.01],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[0.01],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"e":{"k":[{"s":[24.99],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[24.99],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"k":[{"s":[-90],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-90],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"m":1,"ix":2,"nm":"Line Halfer","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"tr","p":{"k":[{"s":[0,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[0,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"k":[{"s":[-34.237,0],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-28.107,0],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-24.524,0],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-33.886,0],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.418,0],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.45,0],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.592,0],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.903,0],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.593,0],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.45,0],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-36.418,0],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.694,0],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-30.098,0],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-23.681,0],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-17.055,0],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-13.542,0],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-16.458,0],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-29.364,0],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.146,0],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[-34.237,0],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"s":{"k":[{"s":[-100,100],"t":0,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}},{"s":[-100,100],"t":20,"i":{"x":[1,1],"y":[1,1]},"o":{"x":[0,0],"y":[0,0]}}]},"r":{"k":[{"s":[84.81],"t":0,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[68.487],"t":1,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[15.783],"t":2,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-27.715],"t":3,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-44.444],"t":4,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-45.558],"t":5,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-48.322],"t":6,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-51.882],"t":7,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-48.332],"t":8,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-45.556],"t":9,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-44.444],"t":10,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-43.336],"t":11,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-39.67],"t":12,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-31.682],"t":13,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[-14.22],"t":14,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[20.836],"t":15,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[57.365],"t":16,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[75.994],"t":17,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[84.074],"t":18,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[86.868],"t":19,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}},{"s":[84.81],"t":20,"i":{"x":[1],"y":[1]},"o":{"x":[0],"y":[0]}}]},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"ArcMath","np":2,"cix":2,"bm":0,"ix":4,"mn":"ADBE Vector Group","hd":false},{"ty":"tr","p":{"k":[{"s":[257,375.694],"t":0,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[272.158,360.89],"t":1,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[304.929,323.777],"t":2,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[333.758,276.805],"t":3,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.5,249],"t":4,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.546,247.998],"t":5,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.662,245.501],"t":6,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.812,242.247],"t":7,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.662,245.492],"t":8,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.546,247.999],"t":9,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[339.5,249],"t":10,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[337.044,252.445],"t":11,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[330.381,261.815],"t":12,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[320.59,275.659],"t":13,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[308.722,292.533],"t":14,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[295.839,310.98],"t":15,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[283.016,329.554],"t":16,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[271.317,346.829],"t":17,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[261.825,361.378],"t":18,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[255.77,371.941],"t":19,"i":{"x":1,"y":1},"o":{"x":0,"y":0}},{"s":[257,375.694],"t":20,"i":{"x":1,"y":1},"o":{"x":0,"y":0}}]},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Admin","np":4,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Body","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":0,"s":[256,256,0],"to":[0,-15,0],"ti":[0,15,0]},{"i":{"x":0.667,"y":0.667},"o":{"x":0.333,"y":0.333},"t":4,"s":[256,166,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0},"t":10,"s":[256,166,0],"to":[0,15,0],"ti":[0,-15,0]},{"t":20,"s":[256,256,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[130.039,52.867],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":74,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[-13.98,65.434],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Body","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Neck","parent":9,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[-25]},{"t":20,"s":[0]}],"ix":10},"p":{"a":0,"k":[30.5,48,0],"ix":2,"l":2},"a":{"a":0,"k":[30.5,48,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[21.223,63.195],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":74,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[30.611,29.598],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Neck","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Head","parent":10,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[18]},{"t":20,"s":[0]}],"ix":10},"p":{"a":0,"k":[30,5.5,0],"ix":2,"l":2},"a":{"a":0,"k":[30,8,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[2.313,3.227],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.2196078431372549,0.2901960784313726,0.8274509803921568,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2196078431372549,0.2901960784313726,0.8274509803921568,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[50.539,7.052],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[-7.886,10.149],[0,0]],"o":[[0,0],[0,0],[0,0],[4.335,-5.58],[0,0]],"v":[[29,-0.5],[29,29],[71.5,29],[89.386,18.851],[93.5,-0.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0,0,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.156,0.299],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Head","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Square Solid","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[45]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[135]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[180]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[225]},{"t":60,"s":[270]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[256,276,0],"to":[0,-8.667,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":10,"s":[256,224,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[256,276,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":30,"s":[256,224,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[256,276,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":50,"s":[256,224,0],"to":[0,0,0],"ti":[0,-8.667,0]},{"t":60,"s":[256,276,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[242.574,242.574],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":8,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":1,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.2196078431372549,0.2901960784313726,0.8274509803921568,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":21,"st":0,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Square","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[45]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[90]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[135]},{"i":{"x":[0],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":40,"s":[180]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[225]},{"t":60,"s":[270]}],"ix":10},"p":{"a":1,"k":[{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":0,"s":[256,276,0],"to":[0,-8.667,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":10,"s":[256,224,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":20,"s":[256,276,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":30,"s":[256,224,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0,"y":1},"o":{"x":0.167,"y":0.167},"t":40,"s":[256,276,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.833,"y":0.833},"o":{"x":0.333,"y":0},"t":50,"s":[256,224,0],"to":[0,0,0],"ti":[0,-8.667,0]},{"t":60,"s":[256,276,0]}],"ix":2,"l":2},"a":{"a":0,"k":[0,0,0],"ix":1,"l":2},"s":{"a":0,"k":[100,100,100],"ix":6,"l":2}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[255.574,255.574],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":14,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.2196078431372549,0.2901960784313726,0.8274509803921568,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":8,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":21,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/logo_loader.json b/app/src/main/assets/lottie/logo_loader.json new file mode 100644 index 0000000..8613365 --- /dev/null +++ b/app/src/main/assets/lottie/logo_loader.json @@ -0,0 +1 @@ +{"v":"5.5.7","meta":{"g":"LottieFiles AE 0.1.21","a":"","k":"","d":"","tc":""},"fr":30,"ip":0,"op":50,"w":600,"h":600,"nm":"Infinity","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Path 2c","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-39.09,0],[0,42.016]],"o":[[22.67,22.309],[42.017,0],[0,-43.441]],"v":[[25.726,26.124],[115.7,76.199],[191.899,0]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[30]},{"t":33,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27,"s":[30]},{"t":32,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.6,1,0.941176470588,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27,"s":[30]},{"t":33,"s":[25]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Path 2b","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":18,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":26,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[100]},{"t":38,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,43.44],[-42.016,0],[-22.67,-22.309],[0,0],[-39.09,0],[0,42.016],[42.329,0]],"o":[[-42.329,0],[0,-42.017],[39.088,0],[0,0],[22.67,22.309],[42.017,0],[0,-43.441],[0,0]],"v":[[-117.649,76.199],[-191.899,0],[-115.699,-76.199],[-25.728,-26.127],[25.726,26.124],[115.7,76.199],[191.899,0],[117.649,-76.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[30]},{"t":39,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":18,"s":[30]},{"t":38,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.4,0.944999964097,0.882000014361,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[30]},{"t":38,"s":[5]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Path 2a","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":36,"s":[100]},{"t":41,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,43.44],[-42.016,0],[-22.67,-22.309],[0,0],[-39.09,0],[0,42.016],[42.329,0]],"o":[[-42.329,0],[0,-42.017],[39.088,0],[0,0],[22.67,22.309],[42.017,0],[0,-43.441],[0,0]],"v":[[-117.649,76.199],[-191.899,0],[-115.699,-76.199],[-25.728,-26.127],[25.726,26.124],[115.7,76.199],[191.899,0],[117.649,-76.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[30]},{"t":41,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":21,"s":[30]},{"t":38,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.047058823529,0.658823529412,0.674509803922,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":23,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":31,"s":[25]},{"t":37,"s":[5]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Path 1d","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":5,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":18,"s":[100]},{"t":24,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,43.44],[42.016,0],[22.67,-22.309],[0,0],[39.09,0],[0,42.016],[-42.329,0]],"o":[[42.329,0],[0,-42.017],[-39.088,0],[0,0],[-22.67,22.309],[-42.017,0],[0,-43.441],[0,0]],"v":[[117.649,76.199],[191.899,0],[115.699,-76.199],[25.728,-26.127],[-25.726,26.124],[-115.7,76.199],[-191.899,0],[-117.649,-76.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":6,"s":[30]},{"t":24,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[30]},{"t":21,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.486274509804,0.329411764706,0.925490196078,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[30]},{"t":20,"s":[5]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Path 1c","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":19,"s":[100]},{"t":25,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,43.44],[42.016,0],[22.67,-22.309],[0,0],[39.09,0],[0,42.016],[-42.329,0]],"o":[[42.329,0],[0,-42.017],[-39.088,0],[0,0],[-22.67,22.309],[-42.017,0],[0,-43.441],[0,0]],"v":[[117.649,76.199],[191.899,0],[115.699,-76.199],[25.728,-26.127],[-25.726,26.124],[-115.7,76.199],[-191.899,0],[-117.649,-76.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":7,"s":[30]},{"t":25,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":2,"s":[30]},{"t":22,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.388235294118,0.490196078431,0.921568627451,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[30]},{"t":20,"s":[5]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Path 1b","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100]},{"t":26,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,43.44],[42.016,0],[22.67,-22.309],[0,0],[39.09,0],[0,42.016],[-42.329,0]],"o":[[42.329,0],[0,-42.017],[-39.088,0],[0,0],[-22.67,22.309],[-42.017,0],[0,-43.441],[0,0]],"v":[[117.649,76.199],[191.899,0],[115.699,-76.199],[25.728,-26.127],[-25.726,26.124],[-115.7,76.199],[-191.899,0],[-117.649,-76.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":8,"s":[30]},{"t":26,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":3,"s":[30]},{"t":23,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.682352941176,0.866666666667,0.976470588235,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":2,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[30]},{"t":20,"s":[5]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Path 1a","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100]},{"t":25,"s":[0]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[300,300,0],"ix":2},"a":{"a":0,"k":[266.899,151.199,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,43.44],[42.016,0],[22.67,-22.309],[0,0],[39.09,0],[0,42.016],[-42.329,0]],"o":[[42.329,0],[0,-42.017],[-39.088,0],[0,0],[-22.67,22.309],[-42.017,0],[0,-43.441],[0,0]],"v":[[117.649,76.199],[191.899,0],[115.699,-76.199],[25.728,-26.127],[-25.726,26.124],[-115.7,76.199],[-191.899,0],[-117.649,-76.199]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":18,"s":[30]},{"t":25,"s":[100]}],"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":1,"s":[30]},{"t":21,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim Paths 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.321568627451,0.176470588235,0.501960784314,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":15,"s":[25]},{"t":20,"s":[5]}],"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[266.899,151.199],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"path","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":50,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/map_lowpower.json b/app/src/main/assets/lottie/map_lowpower.json new file mode 100644 index 0000000..3ac78c5 --- /dev/null +++ b/app/src/main/assets/lottie/map_lowpower.json @@ -0,0 +1 @@ +{"v":"5.5.9","fr":60,"ip":0,"op":60,"w":20,"h":35,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"1","sr":1,"ks":{"o":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":14,"s":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":28,"s":[0]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":42,"s":[100]},{"t":56,"s":[4]}],"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[10,28.636363636363626,0],"ix":2},"a":{"a":0,"k":[6.75,2.25,0],"ix":1},"s":{"a":0,"k":[90.9090909090909,92.10526315789475,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-0.552,0],[0,0],[0,-0.553],[0,0],[0.553,0],[0,0],[0,0.553],[0,0]],"o":[[0,0],[0.553,0],[0,0],[0,0.553],[0,0],[-0.552,0],[0,0],[0,-0.553]],"v":[[-5.5,-2],[5.5,-2],[6.5,-1],[6.5,1],[5.5,2],[-5.5,2],[-6.5,1],[-6.5,-1]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.9137254901960784,0.8509803921568627,0.08235294117647059,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[6.75,2.25],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Layer 2 Outlines","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[10,17.72727272727273,0],"ix":2},"a":{"a":0,"k":[12,19.5,0],"ix":1},"s":{"a":0,"k":[90.9090909090909,92.10526315789475,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.104,0],[0,0],[0,-1.104],[0,0],[1.104,0],[0,0],[0,1.104],[0,0]],"o":[[0,0],[1.104,0],[0,0],[0,1.104],[0,0],[-1.104,0],[0,0],[0,-1.104]],"v":[[-8,-15.5],[8,-15.5],[10,-13.5],[10,13.5],[8,15.5],[-8,15.5],[-10,13.5],[-10,-13.5]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.9137254901960784,0.8509803921568627,0.08235294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":2,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[12,21.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[14,1.5],[10,1.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.9137254901960784,0.8509803921568627,0.08235294117647059,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":60,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/pic_map_lost.json b/app/src/main/assets/lottie/pic_map_lost.json new file mode 100644 index 0000000..67c4160 --- /dev/null +++ b/app/src/main/assets/lottie/pic_map_lost.json @@ -0,0 +1 @@ +{"v":"5.4.4","fr":30,"ip":0,"op":90,"w":100,"h":100,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":3,"nm":"Adjustment Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[50,50,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":0,"s":[0,0,100],"e":[26,26,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":6.333,"s":[26,26,100],"e":[20,20,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":11.833,"s":[20,20,100],"e":[24,24,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16.418,"s":[24,24,100],"e":[22,22,100]},{"t":19.1675007807083}],"ix":6}},"ao":0,"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"stroke","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":48,"s":[0],"e":[-4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50.25,"s":[-4],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":52.5,"s":[3],"e":[-2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":54.75,"s":[-2],"e":[4]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":57,"s":[4],"e":[0]},{"t":60.0000024438501}],"ix":10},"p":{"a":0,"k":[258.75,237,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[110,110,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-7.104,12.304],[0,0],[0,0],[-7.104,-12.304],[0,0],[0,0],[14.207,0]],"o":[[0,0],[-14.207,0],[0,0],[0,0],[7.104,-12.304],[0,0],[0,0],[7.104,12.304],[0,0]],"v":[[0,105.676],[-98.937,105.676],[-114.92,77.992],[-65.452,-7.69],[-15.983,-93.372],[15.983,-93.372],[65.452,-7.69],[114.921,77.992],[98.937,105.676]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":14,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 3","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"t":12,"s":[258.75,268.805,0],"e":[258.75,283.75,0],"to":[0,2.491,0],"ti":[0,-2.491,0]},{"t":23.0000009368092}],"ix":2},"a":{"a":0,"k":[16,38,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":16,"s":[91.667,0,100],"e":[91.667,99,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":23.555,"s":[91.667,99,100],"e":[91.667,93.5,100]},{"i":{"x":[0.667,0.667,0.667],"y":[1,1,1]},"o":{"x":[0.333,0.333,0.333],"y":[0,0,0]},"t":29.223,"s":[91.667,93.5,100],"e":[91.667,91.63,100]},{"t":33.0000013441176}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-8.284,0],[0,-8.284],[8.284,0],[0,8.284]],"o":[[8.284,0],[0,8.284],[-8.284,0],[0,-8.284]],"v":[[0,-15],[15,0],[0,15],[-15,0]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[16,53],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 2","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[258.75,271.1,0],"ix":2},"a":{"a":0,"k":[0,76.865,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":18,"s":[50.417,0,100],"e":[50.417,55,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":22,"s":[50.417,55,100],"e":[50.417,47.3,100]},{"i":{"x":[0.833,0.833,0.833],"y":[0.833,0.833,0.833]},"o":{"x":[0.167,0.167,0.167],"y":[0.167,0.167,0.167]},"t":26,"s":[50.417,47.3,100],"e":[50.417,50.38,100]},{"t":30.0000012219251}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[1.327,29.458],[0,0],[2.522,-56],[-6.482,-25.766],[-4.199,16.689]],"o":[[-2.522,-56],[0,0],[-1.327,29.458],[4.199,16.689],[6.482,-25.766]],"v":[[23.327,-25.037],[0,-78.037],[-23.327,-25.037],[-11.647,64.349],[11.647,64.349]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"body","parent":1,"sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":27,"s":[0],"e":[5]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":29.728,"s":[5],"e":[-3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":32.455,"s":[-3],"e":[2]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":35.183,"s":[2],"e":[-5]},{"i":{"x":[0.4],"y":[0.4]},"o":{"x":[0.333],"y":[0]},"t":37.91,"s":[-5],"e":[0]},{"t":42.0000017106951}],"ix":10},"p":{"a":0,"k":[249.583,248.917,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[110,110,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[-7.104,12.304],[0,0],[0,0],[-7.104,-12.304],[0,0],[0,0],[14.207,0]],"o":[[0,0],[-14.207,0],[0,0],[0,0],[7.104,-12.304],[0,0],[0,0],[7.104,12.304],[0,0]],"v":[[0,105.676],[-98.937,105.676],[-114.92,77.992],[-65.452,-7.69],[-15.983,-93.372],[15.983,-93.372],[65.452,-7.69],[114.921,77.992],[98.937,105.676]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Shape 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":900.000036657751,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/pic_map_nosignal.json b/app/src/main/assets/lottie/pic_map_nosignal.json new file mode 100644 index 0000000..1eae40c --- /dev/null +++ b/app/src/main/assets/lottie/pic_map_nosignal.json @@ -0,0 +1 @@ +{"v":"5.5.8","fr":30,"ip":0,"op":120,"w":100,"h":100,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Layer 85","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64.47600000000001,75.76300000000002,0],"ix":2},"a":{"a":0,"k":[43.428,77.289,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.32,0.32,0.32],"y":[1.27,1.27,1]},"o":{"x":[0.54,0.54,0.54],"y":[0,0,0]},"t":41,"s":[0,0,100]},{"t":61,"s":[33.33333333333334,33.33333333333334,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-5.031],[5.031,0],[0,5.031],[-5.031,0]],"o":[[0,5.031],[-5.031,0],[0,-5.031],[5.031,0]],"v":[[52.734,51.625],[43.625,60.734],[34.516,51.625],[43.625,42.516]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[43,-2],[43,29]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":16,"ix":5},"lc":2,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[-37.435,79],[43.428,-52.423],[124.292,79]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":16,"ix":5},"lc":1,"lj":2,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 3","np":3,"cix":2,"bm":0,"ix":3,"mn":"ADBE Vector Group","hd":false}],"ip":41,"op":201,"st":21,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"Isolation Mode 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[37.21300000000001,45.867666666666686,0],"ix":2},"a":{"a":0,"k":[-38.361,-12.397,0],"ix":1},"s":{"a":0,"k":[33.33333333333334,33.33333333333334,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-80.779]],"o":[[80.779,0],[0,0]],"v":[[-111.492,-85.529],[34.771,60.734]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":29,"s":[0]},{"t":49,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":29,"op":189,"st":9,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Isolation Mode 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[31.03933333333334,52.04133333333335,0],"ix":2},"a":{"a":0,"k":[-56.882,6.124,0],"ix":1},"s":{"a":0,"k":[33.33333333333334,33.33333333333334,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-60.321]],"o":[[60.321,0],[0,0]],"v":[[-111.492,-48.487],[-2.272,60.734]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":25,"s":[0]},{"t":45,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":25,"op":185,"st":5,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Isolation Mode","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[25.122000000000007,60.75800000000002,0],"ix":2},"a":{"a":0,"k":[-74.634,32.274,0],"ix":1},"s":{"a":0,"k":[33.33333333333334,33.33333333333334,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,-40.465],[0,0]],"o":[[40.465,0],[0,0],[0,0]],"v":[[-111.268,-12.451],[-38,60.734],[-38,77]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":18,"ix":5},"lc":2,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"tm","s":{"a":0,"k":0,"ix":1},"e":{"a":1,"k":[{"i":{"x":[0.5],"y":[1]},"o":{"x":[0.167],"y":[0.167]},"t":20,"s":[0]},{"t":40,"s":[100]}],"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Trim In Path","mn":"ADBE Vector Filter - Trim","hd":false}],"ip":20,"op":180,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[16.575000000000006,66.50566666666668,0],"ix":2},"a":{"a":0,"k":[-100.275,49.517,0],"ix":1},"s":{"a":1,"k":[{"i":{"x":[0.32,0.32,0.32],"y":[1,1,1]},"o":{"x":[0.17,0.17,0.17],"y":[0,0,0]},"t":0,"s":[0,0,100]},{"t":20,"s":[33.33333333333334,33.33333333333334,100]}],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,-15.604],[15.604,0],[0,15.604],[-15.604,0]],"o":[[0,15.604],[-15.604,0],[0,-15.604],[15.604,0]],"v":[[-72.021,49.517],[-100.275,77.771],[-128.529,49.517],[-100.275,21.263]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.8274509803921568,1,0.3254901960784314,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":12,"ix":5},"lc":1,"lj":1,"ml":10,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":0,"k":[0.07058823529411765,0.09803921568627451,0.11372549019607843,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":180,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/searching_tracker.json b/app/src/main/assets/lottie/searching_tracker.json new file mode 100644 index 0000000..35eafb1 --- /dev/null +++ b/app/src/main/assets/lottie/searching_tracker.json @@ -0,0 +1 @@ +{"v":"5.4.4","fr":30,"ip":0,"op":61,"w":400,"h":400,"nm":"Comp 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":2,"ty":4,"nm":"Shape Layer 18","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":60,"s":[50,50],"e":[400,400]},{"t":120}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[100],"e":[0]},{"t":120}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":60,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":90,"s":[3],"e":[0]},{"t":120}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":60,"op":121,"st":60,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 17","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":50,"s":[50,50],"e":[400,400]},{"t":110}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":50,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":80,"s":[100],"e":[0]},{"t":110}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":50,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":80,"s":[3],"e":[0]},{"t":110}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":50,"op":111,"st":50,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 16","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":40,"s":[50,50],"e":[400,400]},{"t":100}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":40,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":70,"s":[100],"e":[0]},{"t":100}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":40,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":70,"s":[3],"e":[0]},{"t":100}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":40,"op":101,"st":40,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"Shape Layer 15","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":30,"s":[50,50],"e":[400,400]},{"t":90}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":30,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[100],"e":[0]},{"t":90}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":30,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":60,"s":[3],"e":[0]},{"t":90}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":30,"op":91,"st":30,"bm":0},{"ddd":0,"ind":6,"ty":4,"nm":"Shape Layer 14","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":20,"s":[50,50],"e":[400,400]},{"t":80}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":20,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[100],"e":[0]},{"t":80}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":20,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":50,"s":[3],"e":[0]},{"t":80}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":20,"op":81,"st":20,"bm":0},{"ddd":0,"ind":7,"ty":4,"nm":"Shape Layer 13","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":10,"s":[50,50],"e":[400,400]},{"t":70}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":10,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[100],"e":[0]},{"t":70}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":10,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":40,"s":[3],"e":[0]},{"t":70}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":10,"op":71,"st":10,"bm":0},{"ddd":0,"ind":8,"ty":4,"nm":"Shape Layer 12","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":0,"s":[50,50],"e":[400,400]},{"t":60}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[100],"e":[0]},{"t":60}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":0,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":30,"s":[3],"e":[0]},{"t":60}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":61,"st":0,"bm":0},{"ddd":0,"ind":9,"ty":4,"nm":"Shape Layer 11","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":-10,"s":[50,50],"e":[400,400]},{"t":50}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-10,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[100],"e":[0]},{"t":50}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-10,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":20,"s":[3],"e":[0]},{"t":50}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-10,"op":51,"st":-10,"bm":0},{"ddd":0,"ind":10,"ty":4,"nm":"Shape Layer 10","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":-20,"s":[50,50],"e":[400,400]},{"t":40}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-20,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[100],"e":[0]},{"t":40}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-20,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":10,"s":[3],"e":[0]},{"t":40}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-20,"op":41,"st":-20,"bm":0},{"ddd":0,"ind":11,"ty":4,"nm":"Shape Layer 9","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":-30,"s":[50,50],"e":[400,400]},{"t":30}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-30,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[100],"e":[0]},{"t":30}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-30,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":0,"s":[3],"e":[0]},{"t":30}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-30,"op":31,"st":-30,"bm":0},{"ddd":0,"ind":12,"ty":4,"nm":"Shape Layer 8","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":-40,"s":[50,50],"e":[400,400]},{"t":20}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-40,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-10,"s":[100],"e":[0]},{"t":20}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-40,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-10,"s":[3],"e":[0]},{"t":20}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-40,"op":21,"st":-40,"bm":0},{"ddd":0,"ind":13,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[200.063,200,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.167,0.167],"y":[0,0]},"t":-50,"s":[50,50],"e":[400,400]},{"t":10}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529422283,0.823529422283,0.823529422283,1],"ix":3},"o":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-50,"s":[0],"e":[100]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-20,"s":[100],"e":[0]},{"t":10}],"ix":4,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"w":{"a":1,"k":[{"i":{"x":[0.833],"y":[1]},"o":{"x":[0.167],"y":[0]},"t":-50,"s":[0],"e":[3]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"t":-20,"s":[3],"e":[0]},{"t":10}],"ix":5,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"lc":1,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":-50,"op":11,"st":-50,"bm":0},{"ddd":0,"ind":14,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.667,"y":0.426},"o":{"x":0.333,"y":0},"t":0,"s":[200,190,0],"e":[210,200,0],"to":[5.523,0,0],"ti":[0,-5.523,0]},{"i":{"x":0.667,"y":0.653},"o":{"x":0.333,"y":0.305},"t":19.581,"s":[210,200,0],"e":[200,210,0],"to":[0,5.523,0],"ti":[5.523,0,0]},{"i":{"x":0.667,"y":0.695},"o":{"x":0.333,"y":0.347},"t":30,"s":[200,210,0],"e":[190,200,0],"to":[-5.523,0,0],"ti":[0,5.523,0]},{"i":{"x":0.667,"y":1},"o":{"x":0.333,"y":0.574},"t":40.419,"s":[190,200,0],"e":[200,190,0],"to":[0,-5.523,0],"ti":[-5.523,0,0]},{"t":60}],"ix":2,"x":"var $bm_rt;\n$bm_rt = loopOut('cycle', 0);"},"a":{"a":0,"k":[-3.25,-7.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"d":1,"ty":"el","s":{"a":0,"k":[29,29],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"nm":"Ellipse Path 1","mn":"ADBE Vector Shape - Ellipse","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529481888,0.823529481888,0.823529481888,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.25,-7.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 2","np":3,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[9.75,10.5],[21.25,22.5]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"st","c":{"a":0,"k":[0.823529481888,0.823529481888,0.823529481888,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":3,"ix":5},"lc":2,"lj":1,"ml":4,"bm":0,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[-3.25,-7.5],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Ellipse 1","np":3,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":600,"st":0,"bm":0}],"markers":[]} \ No newline at end of file diff --git a/app/src/main/assets/lottie/success_anim.json b/app/src/main/assets/lottie/success_anim.json new file mode 100644 index 0000000..33fbb5d --- /dev/null +++ b/app/src/main/assets/lottie/success_anim.json @@ -0,0 +1 @@ +{"v": "5.5.7", "meta": {"g": "LottieFiles AE 0.1.21", "a": "", "k": "", "d": "", "tc": ""}, "fr": 29.9700012207031, "ip": 0, "op": 60.0000024438501, "w": 120, "h": 120, "nm": "sucess", "ddd": 0, "assets": [], "layers": [{"ddd": 0, "ind": 1, "ty": 3, "nm": "scale up null", "sr": 1, "ks": {"o": {"a": 0, "k": 0, "ix": 11}, "r": {"a": 0, "k": 0, "ix": 10}, "p": {"a": 0, "k": [60, 60, 0], "ix": 2}, "a": {"a": 0, "k": [0, 0, 0], "ix": 1}, "s": {"a": 1, "k": [{"i": {"x": [0.833, 0.833, 0.833], "y": [1, 1, 1]}, "o": {"x": [0.167, 0.167, 0.167], "y": [0, 0, 0]}, "t": 27, "s": [111, 111, 100]}, {"i": {"x": [0.833, 0.833, 0.833], "y": [1, 1, 1]}, "o": {"x": [0.167, 0.167, 0.167], "y": [0, 0, 0]}, "t": 36, "s": [101, 101, 100]}, {"t": 42.0000017106951, "s": [121, 121, 100]}], "ix": 6}}, "ao": 0, "ip": 0, "op": 300.00001221925, "st": 0, "bm": 0}, {"ddd": 0, "ind": 2, "ty": 4, "nm": "spark", "parent": 1, "sr": 1, "ks": {"o": {"a": 0, "k": 100, "ix": 11}, "r": {"a": 0, "k": 0, "ix": 10}, "p": {"a": 0, "k": [0, 0.5, 0], "ix": 2}, "a": {"a": 0, "k": [0, 0, 0], "ix": 1}, "s": {"a": 0, "k": [100, 100, 100], "ix": 6}}, "ao": 0, "shapes": [{"ty": "gr", "it": [{"ty": "rc", "d": 1, "s": {"a": 1, "k": [{"i": {"x": [0.667, 0.667], "y": [1, 1]}, "o": {"x": [0.333, 0.976], "y": [0, 0]}, "t": 35, "s": [2, 0]}, {"i": {"x": [0.667, 0.13], "y": [1, 1]}, "o": {"x": [0.333, 0.333], "y": [0, 0]}, "t": 42, "s": [2, 8]}, {"t": 53.0000021587343, "s": [1.633, 0]}], "ix": 2}, "p": {"a": 1, "k": [{"i": {"x": 0.667, "y": 1}, "o": {"x": 0.333, "y": 0}, "t": 35, "s": [0, -21], "to": [0, -1.5], "ti": [0, 1.5]}, {"t": 53.0000021587343, "s": [0, -30]}], "ix": 3}, "r": {"a": 0, "k": 0, "ix": 4}, "nm": "Rectangle Path 1", "mn": "ADBE Vector Shape - Rect", "hd": false}, {"ty": "fl", "c": {"a": 0, "k": [0.258823529412, 0.878431432387, 0.541176470588, 1], "ix": 4}, "o": {"a": 0, "k": 100, "ix": 5}, "r": 1, "bm": 0, "nm": "Fill 1", "mn": "ADBE Vector Graphic - Fill", "hd": false}, {"ty": "tr", "p": {"a": 0, "k": [0, -8], "ix": 2}, "a": {"a": 0, "k": [0, 0], "ix": 1}, "s": {"a": 0, "k": [100, 100], "ix": 3}, "r": {"a": 0, "k": 0, "ix": 6}, "o": {"a": 0, "k": 100, "ix": 7}, "sk": {"a": 0, "k": 0, "ix": 4}, "sa": {"a": 0, "k": 0, "ix": 5}, "nm": "Transform"}], "nm": "Rectangle 1", "np": 3, "cix": 2, "bm": 0, "ix": 1, "mn": "ADBE Vector Group", "hd": false}, {"ty": "rp", "c": {"a": 0, "k": 8, "ix": 1}, "o": {"a": 0, "k": 0, "ix": 2}, "m": 1, "ix": 2, "tr": {"ty": "tr", "p": {"a": 0, "k": [0, 0], "ix": 2}, "a": {"a": 0, "k": [0, 0], "ix": 1}, "s": {"a": 0, "k": [100, 100], "ix": 3}, "r": {"a": 0, "k": 45, "ix": 4}, "so": {"a": 0, "k": 100, "ix": 5}, "eo": {"a": 0, "k": 100, "ix": 6}, "nm": "Transform"}, "nm": "Repeater 1", "mn": "ADBE Vector Filter - Repeater", "hd": false}], "ip": 35.0000014255792, "op": 335.00001364483, "st": 35.0000014255792, "bm": 0}, {"ddd": 0, "ind": 3, "ty": 4, "nm": "check", "parent": 1, "sr": 1, "ks": {"o": {"a": 0, "k": 100, "ix": 11}, "r": {"a": 0, "k": 0, "ix": 10}, "p": {"a": 0, "k": [0, 0, 0], "ix": 2}, "a": {"a": 0, "k": [0, 0, 0], "ix": 1}, "s": {"a": 0, "k": [100, 100, 100], "ix": 6}}, "ao": 0, "shapes": [{"ind": 0, "ty": "sh", "ix": 1, "ks": {"a": 0, "k": {"i": [[0, 0], [0, 0], [3.5, 3.75], [-12.973, 5.349], [-6.124, -9.992], [12.125, -7.431], [7.431, 12.125]], "o": [[0, 0], [0, 0], [-3.5, -3.75], [10.764, -2.941], [7.431, 12.125], [-12.125, 7.431], [-7.431, -12.125]], "v": [[15.25, -9.688], [-5.75, 10.062], [-16, 0.25], [-6.777, -24.849], [21.955, -13.456], [13.456, 21.955], [-21.955, 13.456]], "c": false}, "ix": 2}, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false}, {"ty": "tm", "s": {"a": 1, "k": [{"i": {"x": [0.363], "y": [1]}, "o": {"x": [0.742], "y": [0]}, "t": 17, "s": [100]}, {"t": 45.0000018328876, "s": [0]}], "ix": 1}, "e": {"a": 1, "k": [{"i": {"x": [0.351], "y": [1]}, "o": {"x": [0.714], "y": [0]}, "t": 10, "s": [100]}, {"t": 40.0000016292334, "s": [23]}], "ix": 2}, "o": {"a": 0, "k": 0, "ix": 3}, "m": 1, "ix": 2, "nm": "Trim Paths 1", "mn": "ADBE Vector Filter - Trim", "hd": false}, {"ty": "st", "c": {"a": 0, "k": [0.260781411564, 0.878829656863, 0.541459326651, 1], "ix": 3}, "o": {"a": 0, "k": 100, "ix": 4}, "w": {"a": 0, "k": 4, "ix": 5}, "lc": 1, "lj": 1, "ml": 4, "bm": 0, "nm": "Stroke 1", "mn": "ADBE Vector Graphic - Stroke", "hd": false}], "ip": 22.0000008960784, "op": 310.000012626559, "st": 10.0000004073083, "bm": 0}, {"ddd": 0, "ind": 4, "ty": 4, "nm": "circle", "parent": 1, "sr": 1, "ks": {"o": {"a": 0, "k": 100, "ix": 11}, "r": {"a": 0, "k": 0, "ix": 10}, "p": {"a": 0, "k": [0, 0, 0], "ix": 2}, "a": {"a": 0, "k": [0, 0, 0], "ix": 1}, "s": {"a": 0, "k": [100, 100, 100], "ix": 6}}, "ao": 0, "shapes": [{"ind": 0, "ty": "sh", "ix": 1, "ks": {"a": 0, "k": {"i": [[-14.221, 0], [0, -14.221], [14.221, 0], [0, 14.221]], "o": [[14.221, 0], [0, 14.221], [-14.221, 0], [0, -14.221]], "v": [[0, -25.75], [25.75, 0], [0, 25.75], [-25.75, 0]], "c": true}, "ix": 2}, "nm": "Path 1", "mn": "ADBE Vector Shape - Group", "hd": false}, {"ty": "st", "c": {"a": 0, "k": [0.260781411564, 0.878829656863, 0.541459326651, 1], "ix": 3}, "o": {"a": 0, "k": 100, "ix": 4}, "w": {"a": 0, "k": 4, "ix": 5}, "lc": 1, "lj": 1, "ml": 4, "bm": 0, "nm": "Stroke 1", "mn": "ADBE Vector Graphic - Stroke", "hd": false}, {"ty": "tm", "s": {"a": 1, "k": [{"i": {"x": [0.363], "y": [1]}, "o": {"x": [0.742], "y": [0]}, "t": 7, "s": [100]}, {"t": 33.0000013441176, "s": [0]}], "ix": 1}, "e": {"a": 1, "k": [{"i": {"x": [0.351], "y": [1]}, "o": {"x": [0.714], "y": [0]}, "t": 0, "s": [100]}, {"t": 30.0000012219251, "s": [100]}], "ix": 2}, "o": {"a": 0, "k": 0, "ix": 3}, "m": 1, "ix": 3, "nm": "Trim Paths 1", "mn": "ADBE Vector Filter - Trim", "hd": false}], "ip": 0, "op": 300.00001221925, "st": 0, "bm": 0}], "markers": []} \ No newline at end of file diff --git a/app/src/main/assets/privacy_cn.html b/app/src/main/assets/privacy_cn.html new file mode 100644 index 0000000..886b6a9 --- /dev/null +++ b/app/src/main/assets/privacy_cn.html @@ -0,0 +1,469 @@ +
+ +

˽ܶƼ޹˾ļΪܶƼ򡰱˾ǡ)öַʽռʹͨƽ̨ύûϢͣݣƷɼûϢͣݡܶƼȷϸİȫܱ׼ûͬ⣬ܶƼй¶κθϢͣϣĵ3СȨ⡣

+ +

˾ǿҽûϸĶ˽Ա˽Ȿ˾ûܶƼƽ̨ṩĸϢݼʹܶƼƷϱϢͣݵߺʵӦá˽עͷעûҵΪҪʱɲڸ¡޶ݡĺİ汾һЧûԱ˽κʻǣµܶƼѯ绰0755-86352225

+ +

Ա˽ߵ飬ֹͣʹܶƼķͲƷʹܶƼķͲƷʾܱ˽и˽߹ɡʹһֲԼ

+ +

ܶƼȨκʱ޸ı˽ߣڹ֮ʱЧ

+ +

һռϢͣ

+ +

(1ܶƼռ洢κûṩǻʹõϢͣݡõϢͣݿܰڣûʹܶƼƷύФԱաߡءֻ롢ַʵַϵϢ罻ýϢЧ֤ͼͥԱϵȸϣԼп֧ƽ̨ϣҪ

+ +

(2ܶƼԶռ洢ûǽлʹǵķͲƷʱϢͣݡûʹܶƼͲƷܱɼģӲϢϵͳ͡豸ʶ IP ַ MAC ַ GPS λáݡ˶ݡΪƫáճݡ֡ͼƬӰϢͣݡ

+ +

(3ܶƼվý塢ƽ̨ƶӦԼƷ񣨡)ӻͨܶƼƽ̨ͲƷ΢šԱȣлϢͣݶܱռǿܽͨܶƼƽ̨ṩǵĻռϢͣݽϣܰڣܶƼ̡ƷӦóϢͣݡϢͣݽᱻΪϣֻҪϱֹ״̬

+ +

(4ͬңܶضƽ̨Ӧƣϸڹңع涨δ18֤꣬ȡ໤˵ͬʹܶƼķͲƷͬⱾ˽ߡ

+ +

ʹϢͣ

+ +

ܶƼȷϸİȫܱ׼Ϸʹռ洢ϢͣݣϢͣݿܱʹڣ

+ +

(1ΪṩܶƼķͲƷ(2ĽάܶƼķͲƷ飬ڷûΪơ

+ +

(3)ܶƼķ񡢲ƷͻԼĿйͨ

+ +

(4ɣṩƷʾϢ

+ +

(5ͳϢͣݲͲƷʹԹ˾˾뱾˾ֱӻӿƹϵشӰϵҵڲʹá

+ +

Ȩ

+ +

һ˵ΪҪάֺ͸ܶƼķͲƷὫϢͣṩܶƼ˾ڲԱԱΪûṩȻ£ǿܻὫϢͣݴݸ

+ +

(1ڱܵ£ǿܻὫϢͣ¶ͣתκα˾ȨĵӦ̻ⲿӦ̣ԴٽṩķһЩ˾ڵҺй֮Ĺң֪Ϥͬ⣬ݱɿܲŷˡһݱ

+ +

(2ǿܽϢܶƼķṩԱ㿪չǵҵ񣬵Ϣͣ¶ͣתܶƼȨκδ˻˾˾ȨʹøõϺͣݡǽɷ书ĸϢͣݣҲΪĿĶʹá

+ +

(3ǿܻйϽȨ˾ŵշҪΪִǵ߻Ϊǻ˵ȨƲͣȫ¶ϢͣݡǿܹϢֹͣǷʹܶƼķͲƷΥʹΪԵϽзɿ硣

+ +

(4ǿǽзթϢռ͵ְȨ˾عϢ˾򡢷ԺϵķɳбҪṩ

+ +

(5ͨܶƼͲƷռĸϢͣݿܶƼ˾֮תƻ򴢴档ʹܶƼķͲƷΪͬⲢȨܶƼ¶תĸϢͣݡ

+ +

(6ϢΪܶƼȫ򲿷ʲۡתáתǽͨʾ֪ͨͨŷ֪ͨ

+ +

ġϢͣ

+ +

(1ʹܶƼضķʱͬ⹫һЩϢܰûڳкͷͼƬ

+ +

(2ͬ⹲ϢͣݣҪ˽á

+ +

(3ѡϢͣͨܶƼƽ̨ƽ̨Ķƽ̨˽ߣǶƽ̨ϢͣݵİȫԲκΡ

+ +

(4ͬdzƹ㱾˾ƷĿĿܻʹϢͣݡ

+ +

塢Ϣͣ

+ +

(1ϢͣݵİȫǷdzҪܶƼȷĸϢͣδȨĽӴʹöֱʩ֤ܺϢͣݰȫ

+ +

(2ѭϵҵ׼ϢͣݰȫȻڻ͵Ӵ洢ԣ޷֤ṩϢͣݰٷٰ֮ȫܡ

+ +

(3ͨرضϢͨѶʽϿӡرնλܵȣֹֹϢͣݵϴ˿ܵ޷ʹܶƼķͲƷǵһЩ͹ܿ޷

+ +

(4Ʊ˻¼ϢȨͣתʹ˻(5˾߽ײõ֧ƽ̨Դٽӽ׵

+ +

(6ͨ¼ܶƼӦóĻɾIJϢͣϣǰϢȻǹɼġ

+ +

(7ͨܶƼ˻κκǷ񾭹ͬ⡣÷޶ڣܶƼκλδȨʹ˻Ϣͣй¶Ϊ

+ +

(8סκιңͬǽϢͣתơʹܶƼݿܶƼݱ˽ռʹϢͣݡݷɹ涨ĸϢͣݽܶƼطͣúֱûʽҪǴݿɾϢͣݻֹܶƼӦóûʸ

+ +

ṩ񹤾߰ơSDK

+ +

Bugly(ѶBugly)

+

ܣ Ϊ˿ٶλӦòƷ⻹ԭCrashCrash豸ͳ

+

ռϢͣCrashCrashϢ̶߳ջROM/RAM/SD/Ե״̬AppϢ汾豸ϢIMEI豸ʶ

+ +

˽ӣhttps://privacy.qq.com/document/preview/fc748b3d96224fdb825ea79e132c1a56

+ + +

ٶȶλ

+

ܣ ʵֶλܣ

+

ռϢͣ豸ʶϢAndroid IDIDFAIDFVγϢϵͳϢϵͳ汾豸ƷƼͺš豸ãӦϢӦ

+

˽ӣhttps://lbs.baidu.com/index.php?title=openprivacy#service-page-anchor-0-0

+ + +

ٶȵͼ

+

ܣ ڵͼʾ켣

+

ռϢͣ豸ʶϢAndroid IDIDFAIDFVγϢдֻô洢Ϣ

+

˽ӣhttps://lbs.baidu.com/index.php?title=openprivacy#service-page-anchor-0-1

+ + +

QQ

+

ܣ QQȨ¼ͷ

+

ռϢͣ豸ʶϢAndroid IDIDFAIDFVϵͳϢڴ濨Ȩ޺ӦбϢ

+

˽ӣhttps://wiki.connect.qq.com/qq%e4%ba%92%e8%81%94sdk%e9%9a%90%e7%a7%81%e4%bf%9d%e6%8a%a4%e5%a3%b0%e6%98%8e

+ + +

΢

+

ܣ ΢Ȩ¼ͷ

+

ռϢͣ豸ʶϢAndroid IDIDFAIDFVϵͳϢڴ濨Ȩ޺ӦбϢ

+ +

˽ӣhttps://support.weixin.qq.com/cgi-bin/mmsupportacctnodeweb-bin/pages/RYiYJkLOrQwu0nb8

+ + +

mapbox

+

ܣ λ·߹滮

+

ռϢͣ豸ʶϢAndroid IDIDFAIDFVϵͳϢڴ濨Ȩ,γϢ

+

˽ӣhttps://www.mapbox.com/legal/privacy

+ + +

ͳơ

+

ܣȡû豸ΨһʶûϢ

+ +

ռϢͣ豸ʶϢAndroid IDIMEIIDFAIDFV豸MACַϵͳϢѰװAPPϢдֻô洢Ϣ

+

˽ӣhttps://www.jiguang.cn/license/privacy

+ + +

ߡ

+ +

ʹǵķͲƷԼɴ˲κ飬ܱ˽ԼʹԼ涨ʹܶƼͲƷĹ

+ + +

лĶκʣµܶƼѯ绰0755-86352225

+ + + + + +
\ No newline at end of file diff --git a/app/src/main/assets/privacy_en.html b/app/src/main/assets/privacy_en.html new file mode 100644 index 0000000..185be48 --- /dev/null +++ b/app/src/main/assets/privacy_en.html @@ -0,0 +1,72 @@ + + +
+

At ABBI PET INC, we respect and are committed to protecting your privacy. This Privacy Policy outlines how we collect, use, safeguard, and keep your information confidential when you use our mobile application ("App").

+ +

1. Information We Collect

+
    +
  • Personal Information: When you create an account, we may collect personal information such as your name, email address, and phone number.
  • +
  • Pet Information: We collect information about your pets, including their name, age, breed, and weight, to provide tailored services.
  • +
  • Usage Data: We collect information on how you interact with our App, such as the features you use and the time spent on the App.
  • +
  • Device Information: We may collect information about the device you use to access the App, including the device type, operating system, and unique device identifiers.
  • +
  • Location Data: With your permission, we may collect and process information about your devices location and your pet's location to provide location-based services.
  • +
+ + +

2. How We Use Your Information

+
    +
  • To Provide and Improve Our Services: We use your information to operate and improve the App and our services.
  • +
  • To Communicate with You: We may use your information to send you updates, newsletters, marketing materials, and other information that may be of interest to you.
  • +
  • To Personalize Your Experience: We use your information to tailor the content and services we offer to better meet your needs and preferences.
  • +
  • To Ensure Security and Compliance: We use your information to ensure the security of our App and compliance with applicable laws and regulations.
  • +
+ + +

3. Data Confidentiality and Security

+
    +
  • Data Encryption: We use industry-standard encryption protocols to protect your personal information during transmission and storage.
  • +
  • Access Controls: Access to your personal information is restricted to authorized personnel only, and we implement strict access controls to prevent unauthorized access.
  • +
  • Data Minimization: We only collect and retain the minimum amount of personal information necessary to provide our services.
  • +
  • Regular Audits: We conduct regular security audits and assessments to ensure our data protection measures are up to date and effective.
  • +
+

4. Sharing Your Information

+
    +
  • Service Providers: We may share your information with third-party service providers who perform services on our behalf, such as hosting, data analysis, and customer support. These service providers are contractually obligated to keep your information confidential and secure.
  • +
  • Legal Requirements: We may disclose your information if required by law or in response to a legal request.
  • +
  • Business Transfers: In the event of a merger, acquisition, or sale of all or a portion of our assets, your information may be transferred to the new owner, subject to the same data protection commitments.
  • +
+ +

5. Your Choices

+
    +
  • Account Information: You can update or delete your account information through the App's settings.
  • +
  • Marketing Communications: You can opt-out of receiving marketing communications from us by following the unsubscribe instructions in those communications or by contacting us directly.
  • +
  • Location Data: You can disable location services through your device settings, but this may affect certain features of the App.
  • +
+ + +

6. Security

+ +

We take reasonable measures to protect your information from unauthorized access, use, or disclosure. However, no method of transmission over the Internet or method of electronic storage is 100% secure.

+ +

7. Children's Privacy

+ +

Our App is not intended for use by children under the age of 13, and we do not knowingly collect personal information from children under 13. If we become aware that we have collected personal information from a child under 13, we will take steps to delete that information.

+ +

8. Changes to This Privacy Policy

+ +

We may update this Privacy Policy from time to time. We will notify you of any changes by posting the new Privacy Policy within the App. You are advised to review this Privacy Policy periodically for any changes.

+ +

9. Contact Us

+

If you have any questions about this Privacy Policy, please contact us at:

+

ABBI PET INC

+

10700 Richmond Ave Suite 143

+

Houston, TX 77042

+

Email: support@abbidot.com

+ +
+ + + + + + \ No newline at end of file diff --git a/app/src/main/assets/subscription_terms_conditions_en.html b/app/src/main/assets/subscription_terms_conditions_en.html new file mode 100644 index 0000000..90afc2a --- /dev/null +++ b/app/src/main/assets/subscription_terms_conditions_en.html @@ -0,0 +1,24 @@ + + +
+ +

ABBIDOT Tracker Subscription Terms and Conditions +

+

1.Subscription Plans Users can choose between Basic and Prime plans. Basic plan subscribers can upgrade to Prime at any time. Prime plan subscribers can't change to Basic plan. + +

2.Refund Policy Automatic Refund (Within 48 Hours): Full refund available if canceled within 48 hours of subscription. Processed automatically without manual review. Manual Refund (After 48 Hours): For subscriptions older than 48 hours but within 1 month, refunds require manual approval. Eligibility: Only applicable to annual (1-year) subscriptions. Refunds may be prorated based on usage. No Refund After 1 Month: Subscriptions active for over 1 month are non-refundable.

+ +

3.Pausing Subscription & Number Retention Users may pause their subscription. Number Retention: Paused numbers can be retained for up to 1 year. A small fee will apply for number retention during the pause period. Failure to reactivate within 1 year may result in number release.

+ +

4.Modifications ABBIDOT reserves the right to update these terms. Users will be notified of changes. By subscribing, you agree to these terms.

+ +

For support contact:

+

support@abbidot.com

+ +
+ + + + + + \ No newline at end of file diff --git a/app/src/main/assets/terms_cn.html b/app/src/main/assets/terms_cn.html new file mode 100644 index 0000000..ce380d0 --- /dev/null +++ b/app/src/main/assets/terms_cn.html @@ -0,0 +1,476 @@ +

˳ABBIDOTɼʹ

лʹܶƼ޹˾ļܶƼ򡰱˾ǡIJƷͷʹܶƼķͲƷǰϸĶʹļơһʹܶƼIJƷעΪܶƼûȷΪ׼ʾͬⱾȫݡй۰̨סκιңرõرڱв˵ǻʵʱԸʾΪܶƼ֮ͷͲƷʹö޽ľзԼЭ顣ǿܻ᲻ʱЩݷشҽԹķʽ֪ı飬ֹͣʹܶƼķͲƷ֪ͨʹñ˾ṩķͲƷӦóƽ̨ͲƷ÷ɽֹΪѽ˸º

һ

δ18꣬ȡøĸ໤˵ͬ󣬲ʸʹܶƼķͲƷͬңܶضƽ̨Ӧƣϸڹңع涨18꣬עΪܶƼûϸ¹

(1ṩʵЧ׼ȷµĸϽעᡣ(2ίעܶƼƷ˻ΪעܶƼ˻

(3Ʊ˻¼ϢȨͣתʹ˻

(4κδʹܶƼƷ˻ͣܶƼṩķͲƷΪ֪ͨǡ

(5ͨܶƼƷ˻κκǷ񾭹ͬ⡣÷޶ڣܶƼκʧδȨʹ˻Ϊ

Ȩ

(1Զ⣬ܶƼƷڵݣı롢űơͼΡӰ֡¼¼¡űͼ廭Ӧó򡢽ܼݣΪܶƼȨʹõĵУȨ̱ꡢרɱڱȷ˵⣬ܶƼȨ

(2̱ꡢ־ۡװ磬ܶƼƺͱ־ƣΪܶƼСעͣɡͨʹܶƼķܶƼIJƷκ̱ꡢ־ۡװɻκȨ

(3ͬⲻɾκδܶƼƷػӡIJϵȨ

(4κȨȨƷݷΧڣܶƼ˵ġ޵ġתõġתɵġɳĽΪԤʹĿҽܶƼڹ޶ڷʡزʹôݵȨݣаκ̱֪ʶȨκȨȨдݽ޸ʹãֹҵ;

(5ܶƼලʹòκʱκɱɻݵķȨ޵ȨܶƼΥõܶƼ֪ʶȨɾκݵȨܶƼ޵ʹȨܶƼݰȨȨκȨķ

(6ͬܶƼܶƼصضʹȨ⣬δܶƼͬ⣬ʹáơ༭ء롢ۡƷκηʽκݣϷκݡδȨʹݿʹܵ»ָغʹܶƼԴ˲еκΡ

ܶƼӦóƽ̨ļƽ̨ϷϷ֡ƬƵۺݣ˻κδУͬܶƼɣ

(1ʾȨκݣܶƼκûƽ̨ݸ

(2ͬܶƼʱɾ޸κηƽ̨ƽ̨صΥݣַФȨȨȨڰҵ;ڵκΥΪݣΥơ޸ġ롢ƷתɡַתõȨ

(3ѱɾݿѱתشܼܶƼƽ̨ظҪ÷ɾθϡ

(4ⷢܶƼƽ̨ϵݾڷDZܵĻṩܶƼõġǶҵġתõġתɵġ˰ġȫ緶ΧɡԲ֪ͨȷϵΪκĿʹƽ̨ϵݣڿƷԼ޸ĻĽʹƷ

ġûΪ淶

ܶƼΪרҵƽ̨߱罻ܣ˽Ⲣϸ¹

(1ܶƼȨɸѡලɾûIJݣûٱߡ֤ΥݵȨ

(2𷢲κΥġġ󵼵ġɧŵġɫġġӵġӵĻ˷еġǡĻΥ÷ɷݡӻκξΪ

(3𷢲κδӵȫɼȨݣгֻᵽκ

ɡ

(4𷢲κο˺ûΪûƬ绰ַ

Ϣ

(5𷢲κι桢ҵݣκεijͽҵܶƼȨ

(6ģ¡αװκ˻֯ܶƼԱ

(7ʹԶܶƼƽ̨ƷκԶץȡھƵݲɼ

(8𷢲κοܹܡȡ۸ƽ̨ȫϵͳ룬κοܸš𻵡ơʹ޷Сɻƽ̨ܵݡ

塢ʾ

ܶƼվý塢ƽ̨ƶӦԼƷӻܹͨܶƼƽ̨лⲢʾܶƼϿɡලЧƵΪܶƼԵݡ߻ͬгеķա

(1ڹʹܶƼIJƷǰǿ漰зա(2ΪκݻյκϢӿ漰зա

(3ڻ͵ӲƷԣܶƼ֤ƽ̨Ʒϵġ޲ġκδ󶼽õĻûκкġ

(4ڻ͵ӲƷԣܶƼķͲƷκʽijŵڷ񼰲Ʒ׼ȷԡԡʵԡɿԻijŵ

(5÷޶ڣܶƼ漰ͲƷԼͨƽ̨ȡûӴκƷб֤𣬰Ȩʵԡض;ԼȨԵı֤

(6÷޶ڣܶƼʹûʹ÷ͲƷĹܿܵκʧ˺еκΡ

(7÷޶ڣܶƼûĻκΪ໥ܵκʧ˺еκΡ

(8÷޶ڣܶƼûƽ̨ϵκݾеκ

Ρ

(9ܶƼûκû֮飬ȨоǷ롣

(10÷û涨ķΧڣʹܶƼķƷκʧи

(11ܶƼϣܳǵķͲƷǣDzκν֤

ߡֹ

(1ܶƼκʱ䲻ֹ֪ͨ޸κܶƼӦóۼƻƷ

(2÷ɣܶƼԲеκεκʱκԭ򶳽ᡢֹֹ˻ɾĵκηݣʹȫ򲿷ַ͹ܡ

(3Ⲣͬ⣬ܶƼƽ̨ϷijЩݣڻҪлƽ̨ʾûݣʹʻֹԽʾȨ÷ɣɾصݡ

(4˻ֹֹͣʹܶƼķͲƷȻЧ

ˡ⳥

(1ܶƼϣǵķͲƷܶƼҲ뱣ɵκ𺦡

(2ͬ⳥Ϊ֮ṩ绤ʹܶƼ߼Ա¡ԱȨ̼Ӧʹõݻƽ̨ͲƷʹáƽ̨ػƽ̨ûصΪκΥʹκηɻκεȨĻκηʽ֮صΡ⡢ʧü֧ʦѡΪļ̳ˡߡаˡƲ˺壬شȫ÷׷κʹƽ̨йصʧеܶƼκȨ

(3ܶƼκʹû޷ʹܶƼƽ̨ͨƽ̨Ʒƽ̨ûΪϵϵģμܶƼμܶƼƽ̨ʹصκݡμκֱӵġӵġżȻġĽ𺦳еΣκʧʧʹܶƼѾ֪ɴ𺦡

(4еʹܶƼͲƷȫΡʹܶƼͲƷݶ𺦵ΨһʩֹͣʹñͲƷʹøƽ̨ɵκ𺦻ʧܶƼ⳥β1Ԫҡ

š˾ϽȨ

й۰̨סκιңòͬ

(1ͬ⣬ܶƼλй½й½˾ϽܶƼйϽȨ

(2ͬ⣬ƽ̨˽ܶƼ֮κӦܵй½ķɹϽĶִкͽͼĽӦй½ķɡ

(3ܶƼͱݻִзκ飬˫ӦѺЭ̽Э̲ʱκһܶƼסصķԺϡ

ʮ涨

(1ͬܶƼڷķΧͨӷʽ͸κ֪ͨЭ顢¶ͨžзЧ

(2ͬܶƼԲ֪ͨκʱκתڱµȨ񣬳Ƿй涨⽫ӰڱµȨ

(3ͬеκβֱ϶ΪЧಿӦЧ

лĶκʣµܶƼѯ绰0755-86352225

 

\ No newline at end of file diff --git a/app/src/main/assets/terms_en.html b/app/src/main/assets/terms_en.html new file mode 100644 index 0000000..7870ac3 --- /dev/null +++ b/app/src/main/assets/terms_en.html @@ -0,0 +1,77 @@ + + + +
+

1. Acceptance of Terms

+ +

By downloading, installing, and using the ABBIDOT mobile application ("App"), you agree to be bound by these Terms and Conditions ("Terms"). If you do not agree to these Terms, do not use the App.

+ + + +

2. Use of the App

+
    +
  • Eligibility: You must be at least 13 years old to use the App. By using the App, you represent and warrant that you meet this eligibility requirement.
  • +
  • License: ABBI PET INC grants you a limited, non-exclusive, non-transferable, and revocable license to use the App for personal, non-commercial use only.
  • +
  • Restrictions: You agree not to:
  • +
+
    +
  • Modify, reverse engineer, decompile, or disassemble the App.
  • +
  • Use the App for any unlawful purpose or in any way that could harm ABBI PET INC or its users.
  • +
  • Access or use the App to build a competing service or product.
  • +
+ + + +

3. Accounts and Security

+
    +
  • Account Creation: To use certain features of the App, you may need to create an account. You agree to provide accurate and complete information during the registration process.
  • +
  • Account Security: You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account. You agree to notify ABBI PET INC immediately of any unauthorized use of your account.
  • +
+

4. Privacy

+ +

Your use of the App is also governed by our Privacy Policy, which is incorporated by reference into these Terms. Please review our Privacy Policy to understand our practices regarding your information.

+ +

5. User Content

+
    +
  • Ownership: You retain ownership of any content you submit or post through the App ("User Content"). By submitting User Content, you grant ABBI PET INC a worldwide, non-exclusive, royalty-free license to use, reproduce, distribute, and display your User Content in connection with the App.
  • +
  • Responsibility: You are solely responsible for your User Content. You agree not to post content that is illegal, harmful, or violates the rights of others.
  • +
+ + +

6. Intellectual Property

+ +

All content, features, and functionality of the App, including but not limited to text, graphics, logos, and software, are the exclusive property of ABBI PET INC or its licensors and are protected by copyright, trademark, and other intellectual property laws.

+ +

7. Termination

+ +

ABBI PET INC may terminate or suspend your access to the App at any time, without prior notice or liability, for any reason, including if you breach these Terms. Upon termination, your right to use the App will immediately cease.

+ +

8. Disclaimers and Limitation of Liability

+
    +
  • Disclaimers: The App is provided "as is" and "as available" without warranties of any kind, either express or implied. ABBI PET INC does not warrant that the App will be uninterrupted or error-free.
  • +
  • Limitation of Liability: To the maximum extent permitted by law, ABBI PET INC shall not be liable for any indirect, incidental, special, or consequential damages arising out of or in connection with your use of the App.
  • +
+ +

9. Governing Law

+

These Terms shall be governed by and construed in accordance with the laws of the State of Texas, without regard to its conflict of law principles.

+ +

10. Changes to Terms

+

ABBI PET INC reserves the right to modify these Terms at any time. We will notify you of any changes by posting the new Terms within the App. Your continued use of the App after the changes become effective constitutes your acceptance of the new Terms.

+ +

11. Data Confidentiality

+

We are committed to maintaining the confidentiality of your data. We employ the following measures to ensure data protection:

+
    +
  • Confidentiality Agreements: All employees and third-party service providers who have access to your data are required to sign confidentiality agreements.
  • +
  • Data Anonymization: Where possible, we anonymize your data to protect your privacy.
  • +
  • Data Retention Policy: We retain your data only as long as necessary for the purposes outlined in our Privacy Policy, after which it is securely deleted or anonymized.
  • +
+ +

12. Contact Us

+

If you have any questions about these Terms, please contact us at:

+

ABBI PET INC

+

10700 Richmond Ave Suite 143

+

Houston, TX 77042

+

Email: support@abbidot.com

+ + +
\ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/MyApplication.kt b/app/src/main/java/com/abbidot/tracker/MyApplication.kt new file mode 100644 index 0000000..933dcdd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/MyApplication.kt @@ -0,0 +1,94 @@ +package com.abbidot.tracker + +import android.app.ActivityManager +import android.app.Application +import android.os.Build +import android.os.Process +import androidx.appcompat.app.AppCompatDelegate +import coil.Coil +import coil.ImageLoader +import coil.decode.GifDecoder +import coil.decode.ImageDecoderDecoder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.hjq.toast.Toaster +import com.qmuiteam.qmui.arch.QMUISwipeBackActivityManager +import dagger.hilt.android.HiltAndroidApp + + +/** + *Created by .yzq on 2021/11/2/002. + * @link + * @description:Application + */ +@HiltAndroidApp +class MyApplication : Application() { + + companion object { + lateinit var instance: Application + } + + override fun onCreate() { + super.onCreate() + //强制浅色模式,使用 AppCompatDelegate 或 MaterialComponents 动态切换主题 + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) + initInMainProcess { + instance = this + AppUtils.syncIsDebug(applicationContext) + QMUISwipeBackActivityManager.init(this) + MMKVUtil.init(this) + + // 初始化 Toast 框架 + Toaster.init(this) + Toaster.setView(R.layout.toast_my_layout) + +// JPushInterface.setDebugMode(true) +// JPushInterface.init(this) + +// if (!AppUtils.isDebug()) { +// //腾讯bugly初始化 +// CrashReport.initCrashReport(applicationContext, "06fc0a9cd1", AppUtils.isDebug()) +// } + +// //同意协议 +// SDKInitializer.setAgreePrivacy(this, true) +//// LocationClient.setAgreePrivacy(true); +// //在使用SDK各组件之前初始化context信息,传入ApplicationContext +// SDKInitializer.initialize(this) +// //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型. +// //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。 +// SDKInitializer.setCoordType(CoordType.GCJ02) + +// if (!AppUtils.isChina()) { +// MapboxSearchSdk.initialize(application = this, +// accessToken = getString(R.string.mapbox_access_token), +// locationEngine = LocationEngineProvider.getBestLocationEngine(this)) +// } + + //设置支持加载gif + val imageLoader = ImageLoader.Builder(this).componentRegistry { + if (Build.VERSION.SDK_INT >= 28) { + add(ImageDecoderDecoder(this@MyApplication)) + } else { + add(GifDecoder()) + } + }.build() + //设置全局唯一实例 + Coil.setImageLoader(imageLoader) + } + } + + /** + * 主进程初始化 + */ + private fun initInMainProcess(block: () -> Unit) { + val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager + val myPId = Process.myPid() + val mainProcessName = packageName + activityManager.runningAppProcesses.forEach { + if (it.pid == myPId && it.processName == mainProcessName) { + block() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/AssignOptionsAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/AssignOptionsAdapter.kt new file mode 100644 index 0000000..9807e18 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/AssignOptionsAdapter.kt @@ -0,0 +1,47 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import coil.load +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2024/5/30/030. + * @link + * @description: + */ +class AssignOptionsAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_bottom_sheet_add_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val circleImageView = holder!!.getImageView(R.id.il_bottom_sheet_circle_image) + val wrapImageView = holder.getImageView(R.id.il_bottom_sheet_wrap_image) + val name = holder.getTextView(R.id.tv_bottom_sheet_name) + + name.text = item.name + if (position == 0) { + circleImageView.visibility = View.GONE + wrapImageView.visibility = View.GONE + } else { + //处理设备类型 + if (item.menuType == ConstantInt.SpecialType) { + wrapImageView.visibility = View.VISIBLE + circleImageView.visibility = View.GONE + wrapImageView.load(R.drawable.icon_tracker_device_small) + } else { + circleImageView.visibility = View.VISIBLE + wrapImageView.visibility = View.GONE + ViewUtil.instance.setPetTypeHead(circleImageView, item.value, item.menuType) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/BaiduMapPoiSearchResultAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/BaiduMapPoiSearchResultAdapter.kt new file mode 100644 index 0000000..e9c700f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/BaiduMapPoiSearchResultAdapter.kt @@ -0,0 +1,24 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.baidu.mapapi.search.core.PoiInfo + +/** + *Created by .yzq on 2022/7/4/004. + * @link + * @description:百度地图搜索地址返回结果 + */ +class BaiduMapPoiSearchResultAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_baidu_map_poi_result + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: PoiInfo) { + holder!!.setText(R.id.baidu_map_poi_name, item.name) + holder.setText(R.id.baidu_map_poi_address, item.address) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/ChangePetListDialogAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/ChangePetListDialogAdapter.kt new file mode 100644 index 0000000..6ecb0bd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/ChangePetListDialogAdapter.kt @@ -0,0 +1,90 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.content.Intent +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.ui.activity.pet.PetProfileActivity +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.TypefaceRoundButton + +/** + *Created by .yzq on 2022/1/8/008. + * @link + * @description:切换宠物列表弹窗适配器 + */ +class ChangePetListDialogAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + + private var mOnChangeClickListener: OnChangeClickListener? = null + + //选中的宠物下标 + private var mSelectPetPos = 0 + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_my_pet_dialog + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: PetBean) { + val current = + holder!!.itemView.findViewById(R.id.dialog_my_pet_current_btn) + val change = + holder.itemView.findViewById(R.id.dialog_my_pet_change_btn) + + holder.setText(R.id.dialog_my_pet_name, item.petName) + + holder.getImageView(R.id.dialog_my_pet_head).apply { + ViewUtil.instance.setPetTypeHead(this, item.imgurl, item.petType) + setOnClickListener { + if (null != mOnChangeClickListener) { + mOnChangeClickListener!!.onCurrentClick() + } + val intent = Intent(mContext, PetProfileActivity::class.java) + intent.putExtra(ConstantString.Edit, true) + intent.putExtra(ConstantString.Pet, item) + mContext.startActivity(intent) + } + } + + if (position == mSelectPetPos) { + current.visibility = View.VISIBLE + change.visibility = View.GONE + } else { + current.visibility = View.GONE + change.visibility = View.VISIBLE + } + + current.setChangeAlphaWhenPress(true) + current.setOnClickListener { + if (null != mOnChangeClickListener) { + mOnChangeClickListener!!.onCurrentClick() + } + } + + change.setChangeAlphaWhenPress(true) + change.setOnClickListener { + mSelectPetPos = position + notifyItemRangeChanged(0, getData().size) + if (null != mOnChangeClickListener) { + mOnChangeClickListener!!.onChangeClick(position) + } + } + } + + fun setSelectPetPos(position: Int) { + mSelectPetPos = position + } + + fun setOnChangeClickListener(onChangeClickListener: OnChangeClickListener) { + mOnChangeClickListener = onChangeClickListener + } + + interface OnChangeClickListener { + fun onCurrentClick() + fun onChangeClick(position: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/ChoosePlanAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/ChoosePlanAdapter.kt new file mode 100644 index 0000000..e9ea3f1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/ChoosePlanAdapter.kt @@ -0,0 +1,82 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.graphics.Paint +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.PackageBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString + +/** + *Created by .yzq on 2024/8/6/006. + * @link + * @description: + */ +class ChoosePlanAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_choose_plan_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: PackageBean) { + holder?.apply { + getView(R.id.rl_choose_plan_shade_item).let { + if (item.selected) it.setBackgroundResource(R.drawable.shape20_yellow_border_cyan_bg) + else it.setBackgroundResource(0) + } + + setText(R.id.tv_choose_plan_item_name, item.planName) + + val price = if (item.discountedPrice > 0) item.discountedPrice else item.planPrice + getTextView(R.id.tv_choose_plan_item_money).apply { + text = if (item.mealUnit == ConstantString.PackageUnitMonth) { + String.format( + mContext.getString(R.string.txt_money_unit), Utils.formatDecimal(price, 2) + ) + } else { + val monthPrice = Utils.formatDecimal(price / (item.period * 12), 2) + String.format(mContext.getString(R.string.txt_money_unit), monthPrice) + } + } + + getTextView(R.id.tv_choose_plan_item_year_money).let { + it.text = String.format( + mContext.getString(R.string.txt_money_unit), + Utils.formatDecimal(item.discountedPrice, 2) + ) + + it.visibility = if (item.discount > 0) View.VISIBLE + else View.GONE + } + + getTextView(R.id.tv_choose_plan_item_discount_money).let { + it.text = String.format( + mContext.getString(R.string.txt_money_unit), + Utils.formatDecimal(item.planPrice, 2) + ) + it.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG + it.visibility = if (item.discount > 0) View.VISIBLE + else View.GONE + } + + + getView(R.id.ll_choose_plan_item_discount_percent).let { + it.visibility = if (item.discount > 0) View.VISIBLE + else View.GONE + } + getTextView(R.id.tv_choose_plan_item_discount_percent).let { + it.text = "${item.discount}%" + } + + getView(R.id.cv_choose_plan_item_popular).let { + if (item.popularType == ConstantInt.Type1) it.visibility = View.VISIBLE + else it.visibility = View.GONE + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/CreditCardAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/CreditCardAdapter.kt new file mode 100644 index 0000000..88da45d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/CreditCardAdapter.kt @@ -0,0 +1,53 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import android.widget.RelativeLayout +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.ui.activity.subscribe.PaymentMethodActivity +import com.abbidot.tracker.ui.fragment.subscribe.CreditCardPaymentFragment +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.widget.TypefaceRoundButton + +/** + *Created by .yzq on 2024/3/21/021. + * @link + * @description: + */ +class CreditCardAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_credit_card_no_data + + override fun getItemLayoutId(viewType: Int) = R.layout.item_credit_card_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: CreditCardBean) { + val relativeLayout = holder!!.itemView as RelativeLayout + val primary = holder.getView(R.id.rb_credit_card_primary_item) as TypefaceRoundButton + if (item.checked) { + relativeLayout.setBackgroundResource(R.drawable.shape8_dark_border_pale_yellow_bg) + } else { + relativeLayout.setBackgroundResource(R.drawable.shape8_dark_gray_border_white_yellow_bg) + } + primary.visibility = if (item.isPrimary) View.VISIBLE else View.GONE + holder.getTextView(R.id.tv_credit_card_number_item).apply { + val formatCardNumber = Util.formatCardNumber(item.cardNumber) + val cardLogoImageId = Util.getCreditCardType(item.cardNumber) + text = formatCardNumber + if (cardLogoImageId == 0) setCompoundDrawablesWithIntrinsicBounds( + 0, 0, 0, 0 + ) + else setCompoundDrawablesWithIntrinsicBounds( + cardLogoImageId, 0, 0, 0 + ) + } + holder.getView(R.id.iv_credit_card_edit_btn).setOnClickListener { + (((mContext as PaymentMethodActivity).mFragments[0]) as CreditCardPaymentFragment).goEditCard( + item + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/DFUStateAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/DFUStateAdapter.kt new file mode 100644 index 0000000..8c49e2f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/DFUStateAdapter.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DFUStateBean + +/** + *Created by .yzq on 2023/4/17/017. + * @link + * @description: + */ +class DFUStateAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_tracker_dfu_state + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DFUStateBean) { + val textView = holder!!.getTextView(R.id.item_dfu_state_name) + val stateName = if (item.progress in 1..100) { + "${item.stateName}${item.progress}%" + } else { + item.stateName + } + textView.text = stateName + + if (position == getData().size - 1) { + holder.getView(R.id.item_dfu_state_progress).visibility = View.VISIBLE + holder.getView(R.id.item_dfu_state_ok).visibility = View.GONE + } else { + holder.getView(R.id.item_dfu_state_progress).visibility = View.GONE + holder.getView(R.id.item_dfu_state_ok).visibility = View.VISIBLE + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/DurationSetAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/DurationSetAdapter.kt new file mode 100644 index 0000000..dfa8001 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/DurationSetAdapter.kt @@ -0,0 +1,52 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.appcompat.widget.AppCompatCheckBox +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.ui.activity.device.set.LiveDurationSetActivity +import com.abbidot.tracker.ui.activity.device.set.TrackingDurationSetActivity + +/** + *Created by .yzq on 2024/6/7/007. + * @link + * @description: + */ +class DurationSetAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private var mTrackingDurationSetActivity: TrackingDurationSetActivity? = null + private var mLiveDurationSetActivity: LiveDurationSetActivity? = null + + init { + if (ctx is TrackingDurationSetActivity) { + mTrackingDurationSetActivity = ctx + } else if (ctx is LiveDurationSetActivity) { + mLiveDurationSetActivity = ctx + } + } + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_duration_set_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + holder!!.setText(R.id.tv_duration_name_item, item.name) + val checkBox = holder.getView(R.id.cb_duration_set_check) as AppCompatCheckBox + checkBox.apply { + isChecked = item.selected + setOnCheckedChangeListener(null) + setOnClickListener { + isChecked = item.selected + if (mContext is TrackingDurationSetActivity) { + mTrackingDurationSetActivity?.setTrackingDuration(position) + } else if (mContext is LiveDurationSetActivity) { + mLiveDurationSetActivity?.setLiveDuration(position) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/FamilyAvatarAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/FamilyAvatarAdapter.kt new file mode 100644 index 0000000..47db29f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/FamilyAvatarAdapter.kt @@ -0,0 +1,38 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.qmuiteam.qmui.widget.QMUIRadiusImageView + +/** + *Created by .yzq on 2024/2/22/022. + * @link + * @description: + */ +class FamilyAvatarAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_invite_family_avatar_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val qMUIRadiusImageView = + holder!!.getView(R.id.il_invite_family_avatar_layout) as QMUIRadiusImageView + qMUIRadiusImageView.apply { + setImageResource(item.imageId) + borderColor = ContextCompat.getColor(mContext, R.color.select_color) + borderWidth = if (item.selected) { + AppUtils.dpToPx(2) + } else { + AppUtils.dpToPx(0) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/FamilyMembersAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/FamilyMembersAdapter.kt new file mode 100644 index 0000000..a6ffd9d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/FamilyMembersAdapter.kt @@ -0,0 +1,63 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.activity.pet.FamilyMembersActivity +import com.qmuiteam.qmui.layout.QMUIButton + +/** + *Created by .yzq on 2024/2/21/021. + * @link + * @description: + */ +class FamilyMembersAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mFamilyMembersActivity: FamilyMembersActivity + + init { + mFamilyMembersActivity = ctx as FamilyMembersActivity + } + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_family_member_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: FamilyBean) { + val activeState = holder!!.getView(R.id.tv_family_member_active_state) + val pendingState = holder.getView(R.id.tv_family_member_pending_state) + val resendBtn = holder.getView(R.id.btn_family_member_resend) as QMUIButton + val avatarImageView = holder.getImageView(R.id.il_family_member_head) + resendBtn.setChangeAlphaWhenPress(true) + if (item.status == ConstantInt.Open) { + activeState.visibility = View.VISIBLE + pendingState.visibility = View.GONE + resendBtn.visibility = View.GONE + } else { + activeState.visibility = View.INVISIBLE + pendingState.visibility = View.VISIBLE + resendBtn.visibility = View.VISIBLE + } + holder.setText(R.id.tv_family_member_email, item.acceptEmail) + + val avatarImageResId = if (item.avatar == ConstantInt.Man) { + R.drawable.icon_family_boy_avatar + } else { + R.drawable.icon_family_girl_avatar + } + avatarImageView.setImageResource(avatarImageResId) + + holder.setClickListener(R.id.iv_delete_family_member_btn, View.OnClickListener { + mFamilyMembersActivity.deleteFamily(item) + }) + resendBtn.setOnClickListener { + mFamilyMembersActivity.resendFamily(item) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/FamilyRelationAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/FamilyRelationAdapter.kt new file mode 100644 index 0000000..c6458b0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/FamilyRelationAdapter.kt @@ -0,0 +1,26 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean + +/** + *Created by .yzq on 2024/2/22/022. + * @link + * @description: + */ +class FamilyRelationAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_invite_family_relation_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + holder!!.setText(R.id.tv_invite_family_relation_text, item.name) + holder.itemView.isSelected = item.selected + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/FeedbackImageAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/FeedbackImageAdapter.kt new file mode 100644 index 0000000..c52c591 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/FeedbackImageAdapter.kt @@ -0,0 +1,53 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.FragmentActivity +import coil.load +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.ui.activity.help.FeedbackActivity +import java.io.File + +/** + *Created by .yzq on 2022/3/23/023. + * @link + * @description:反馈上传的图片 + */ +class FeedbackImageAdapter( + ctx: Context, activity: FragmentActivity, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mActivity = activity as FeedbackActivity + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_feedback_image + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MenuTxtBean) { + val imageView = holder!!.getImageView(R.id.show_select_image) + val deleteView = holder.getTextView(R.id.delete_image_cancel) + if (TextUtils.isEmpty(item.imageUrl)) { + imageView.load(R.drawable.ico_photo) + deleteView.visibility = View.GONE + imageView.setOnClickListener { +// ViewUtil.instance.goSelectPhoto( +// mActivity, +// ResultCode.ResultCode_1, +// count = mActivity.mLimitPhotoCount - (getData().size - 1) +// ) + } + } else { + imageView.setOnClickListener(null) + imageView.load(File(item.imageUrl)) + deleteView.visibility = View.VISIBLE + deleteView.setOnClickListener { + val list = getData() + mActivity.deletePhoto(list, item) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/FencesZoneAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/FencesZoneAdapter.kt new file mode 100644 index 0000000..115c58e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/FencesZoneAdapter.kt @@ -0,0 +1,74 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.content.res.ColorStateList +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.TypefaceRoundButton + +/** + * + */ +class FencesZoneAdapter( + ctx: Context, list: MutableList?, isShowRightArrow: Boolean = true +) : BaseRecyclerAdapter(ctx, list) { + + private var isShowRightArrow = true + + init { + this.isShowRightArrow = isShowRightArrow + } + + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_virtual_fences_empty_view + + override fun getItemLayoutId(viewType: Int) = R.layout.item_virtual_fences_layout + + override fun noData(holder: RecyclerViewHolder?) { + holder?.apply { + getView(R.id.btn_empty_fences_add_safe_zone).visibility = + if (isShowNoDataAddButton) View.VISIBLE + else View.GONE + } + } + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: FencesBean) { + holder!!.getTextView(R.id.tv_virtual_fences_list_name).text = item.name + holder.getTextView(R.id.tv_virtual_fences_list_address).text = item.address + + val fenceNameTypeImage = ViewUtil.instance.getFenceNameImageType(item.fenceNameType) + + holder.getImageView(R.id.iv_virtual_fences_list_type).setImageResource(fenceNameTypeImage) + + val typefaceButton = + holder.getView(R.id.tv_virtual_fences_list_switch_state) as TypefaceRoundButton + val switchText: String + val switchTextColor: Int + val switchStrokeColor = if (item.isOn == ConstantInt.Open) { + switchText = mContext.getString(R.string.txt_active) + switchTextColor = ContextCompat.getColor(mContext, R.color.green_color100) + ContextCompat.getColor(mContext, R.color.green_color100) + } else { + switchText = mContext.getString(R.string.txt_deactivate) + switchTextColor = ContextCompat.getColor(mContext, R.color.block_color2) + ContextCompat.getColor(mContext, R.color.white_gray_color1) + } + typefaceButton.apply { + text = switchText + setTextColor(switchTextColor) + setStrokeColors(ColorStateList.valueOf(switchStrokeColor)) + } + + if (isShowRightArrow) { + holder.getView(R.id.iv_virtual_fences_list_arrow_right).visibility = View.VISIBLE + } else { + holder.getView(R.id.iv_virtual_fences_list_arrow_right).visibility = View.INVISIBLE + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/FirstPermissionsAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/FirstPermissionsAdapter.kt new file mode 100644 index 0000000..c9cb3ff --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/FirstPermissionsAdapter.kt @@ -0,0 +1,81 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.activity.set.FirstPermissionsActivity +import com.abbidot.tracker.widget.TypefaceButton + +/** + *Created by .yzq on 2024/3/25/025. + * @link + * @description: + */ +class FirstPermissionsAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mPermissionsActivity = ctx as FirstPermissionsActivity + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_permission_state_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val stateView = holder!!.getImageView(R.id.cb_permission_state_item) + val typefaceTextView = holder.getTextView(R.id.tv_permission_tip_item) + val btnLayout = holder.getView(R.id.ll_permission_set_layout) + val btnSkip = holder.getView(R.id.btn_permission_skip_set) as TypefaceButton + val btnEnable = holder.getView(R.id.btn_permission_enable_set) as TypefaceButton + btnSkip.setChangeAlphaWhenPress(true) + btnEnable.setChangeAlphaWhenPress(true) + + if (item.selected) { + typefaceTextView.visibility = View.VISIBLE + holder.itemView.setBackgroundResource(R.drawable.shape16_white_color_bg) + + if (item.state == ConstantInt.Type1) { + btnLayout.visibility = View.GONE + } else { + btnLayout.visibility = View.VISIBLE + } + } else { + btnLayout.visibility = View.GONE + typefaceTextView.visibility = View.GONE + holder.itemView.setBackgroundResource(R.drawable.shape10_white_bg) + } + + when (item.state) { + ConstantInt.SpecialType -> stateView.setImageResource(R.drawable.icon_permission_error_check_svg) + ConstantInt.Type0 -> stateView.setImageResource(R.drawable.icon_permission_no_check_svg) + ConstantInt.Type1 -> stateView.setImageResource(R.drawable.icon_permission_check_svg) + } + + holder.setText(R.id.tv_permission_name_item, item.name) + typefaceTextView.text = item.value + + btnSkip.setOnClickListener { + item.state = ConstantInt.SpecialType + item.selected = false + notifyItemChanged(position) +// if (position + 1 < getData().size) { +// getData()[position + 1]. +// } + } + btnEnable.setOnClickListener { + when (position) { + 0 -> mPermissionsActivity.mPermissionsCommon.operateBluetooth() + 1 -> mPermissionsActivity.mPermissionsCommon.operateGPSLocation() + 2 -> mPermissionsActivity.mPermissionsCommon.operateNotifications( + this, ConstantInt.SpecialType + ) + } + } + + mPermissionsActivity.updateNextBtnState() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/GridItemDecoration.kt b/app/src/main/java/com/abbidot/tracker/adapter/GridItemDecoration.kt new file mode 100644 index 0000000..7a1e683 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/GridItemDecoration.kt @@ -0,0 +1,57 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by . + * @link https://blog.csdn.net/Lee465357793/article/details/130830127 + * @description:RecyclerView GridLayoutManager item间距设置,两端对齐 + * @param offset 控件的外边距,不使用屏幕宽度来计算 + */ +class GridItemDecoration( + context: Context, spanCount: Int, private var itemWidth: Int, top: Int, bottom: Int, offset: Int +) : RecyclerView.ItemDecoration() { + + private var mSpanCount = spanCount + private val mContext = context + private var mColumnSpacing = 0 + private val mTop = top + private val mBottom = bottom + + private val rvWidth by lazy { + QMUIDisplayHelper.getScreenWidth(mContext) - offset + } + + init { + mColumnSpacing = (rvWidth - (itemWidth * mSpanCount)) / (mSpanCount - 1) + } + + fun updateSpaceCount(count: Int) { + mSpanCount = count + mColumnSpacing = (rvWidth - (itemWidth * mSpanCount)) / (mSpanCount - 1) + } + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State + ) { + // 获取view 在adapter中的位置。 + val position = parent.getChildAdapterPosition(view) + // view 所在的列 + val column = position % mSpanCount + // column * (列间距 * (1f / 列数)) + outRect.left = (column * mColumnSpacing / mSpanCount) + // 列间距 - (column + 1) * (列间距 * (1f /列数)) + outRect.right = (mColumnSpacing - (column + 1) * mColumnSpacing / mSpanCount) + + if (mTop > 0) { + outRect.top = mTop + } + if (mBottom > 0) { + outRect.bottom = mBottom + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HelpTextImageTypeAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HelpTextImageTypeAdapter.kt new file mode 100644 index 0000000..adef564 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HelpTextImageTypeAdapter.kt @@ -0,0 +1,73 @@ +package com.abbidot.tracker.adapter + +import android.graphics.Typeface +import android.widget.ImageView +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.widget.TypefaceTextView +import com.chad.library.adapter.base.BaseMultiItemQuickAdapter +import com.chad.library.adapter.base.viewholder.BaseViewHolder + + +/** + *Created by .yzq on 2024/8/12/012. + * @link + * @description: + */ +class HelpTextImageTypeAdapter(list: MutableList?) : + BaseMultiItemQuickAdapter(list) { + + init { + //加粗的标题文字 + addItemType(MultipleEntity.TEXT, R.layout.layout_help_title1) + //只是图片的 + addItemType(MultipleEntity.IMG, R.layout.layout_help_image) + //显示不同颜色的文字 + addItemType(MultipleEntity.IMG_TEXT, R.layout.layout_help_title1) + //显示16sp大小的文字内容 + addItemType(MultipleEntity.IMG_IMG, R.layout.layout_help_title1) + //显示14sp大小的文字内容 + addItemType(MultipleEntity.IMG_TEXT_IMG, R.layout.layout_help_title2) + } + + override fun convert(holder: BaseViewHolder, item: MenuTxtBean) { +// val position = getItemPosition(item) + when (item.itemType) { + MultipleEntity.TEXT -> { + // 创建一个Typeface对象,指定加粗字体 + val typefaceBold = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + holder.getView(R.id.tv_help_title_one).apply { + text = item.menuName + typeface = typefaceBold + } + holder.itemView.setPadding(0, AppUtils.dpToPx(8), 0, 0) + } + + MultipleEntity.IMG_TEXT -> holder.getView(R.id.tv_help_title_one) + .apply { + text = item.menuName + setTextColor(ContextCompat.getColor(context, item.colorRedId)) + } + + MultipleEntity.IMG -> { + holder.getView(R.id.iv_help_image_type) + .setImageResource(item.imageId) + } + + MultipleEntity.IMG_IMG -> { + holder.getView(R.id.tv_help_title_one).text = item.menuName + holder.itemView.setPadding(0, AppUtils.dpToPx(20), 0, 0) + } + + MultipleEntity.IMG_TEXT_IMG -> { + holder.getView(R.id.tv_help_title_two).text = item.menuName + holder.itemView.setPadding(0, AppUtils.dpToPx(14), 0, 0) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HomeAccountMenuAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HomeAccountMenuAdapter.kt new file mode 100644 index 0000000..9e44fd6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HomeAccountMenuAdapter.kt @@ -0,0 +1,167 @@ +package com.abbidot.tracker.adapter + +import android.content.Intent +import android.text.TextUtils +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.help.FeedbackActivity +import com.abbidot.tracker.ui.activity.help.HowWorkActivity +import com.abbidot.tracker.ui.activity.set.AboutActivity +import com.abbidot.tracker.ui.activity.set.AdvancedSetActivity +import com.abbidot.tracker.ui.activity.set.NotificationsSetActivity +import com.abbidot.tracker.ui.activity.set.PermissionsSetActivity +import com.abbidot.tracker.ui.common.UnitSelectDialog +import com.abbidot.tracker.ui.fragment.account.AccountV2Fragment +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.TypefaceTextView +import com.chad.library.adapter.base.BaseMultiItemQuickAdapter +import com.chad.library.adapter.base.viewholder.BaseViewHolder +import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView + +/** + *Created by .yzq on 2023/12/27/027. + * @link + * @description: + */ +class HomeAccountMenuAdapter( + accountV2Fragment: AccountV2Fragment, list: MutableList? +) : BaseMultiItemQuickAdapter(list) { + + private val mLogOutTipsType = 0 + private val mClearCacheTipsType = 1 + private val mCloseAccountTipsType = 2 + private lateinit var mHomeV2Activity: HomeV2Activity + private lateinit var mUnitSelectDialog: UnitSelectDialog + private val mAccountV2Fragment = accountV2Fragment + + init { + addItemType(MultipleEntity.TEXT, R.layout.item_title_text_type) + addItemType(MultipleEntity.IMG_TEXT, R.layout.item_qmui_common_item_view) + } + + override fun onAttachedToRecyclerView(recyclerView: RecyclerView) { + super.onAttachedToRecyclerView(recyclerView) + mHomeV2Activity = context as HomeV2Activity + mUnitSelectDialog = UnitSelectDialog(context) + } + + override fun convert(holder: BaseViewHolder, item: MenuTxtBean) { + val position = getItemPosition(item) + when (item.itemType) { + MultipleEntity.TEXT -> { + val typefaceTextView = holder.getView(R.id.tv_title_name) + typefaceTextView.text = item.menuName + } + + MultipleEntity.IMG_TEXT -> { + holder.getView(R.id.tracker_manage_setting_item).run { + ViewUtil.instance.showQMUICommonListItemView( + context, this, { +// Toaster.show("$position") + when (position) { + 1 -> mHomeV2Activity.startActivity( + Intent( + context, HowWorkActivity::class.java + ) + ) + + 2 -> mHomeV2Activity.startActivity( + Intent( + context, FeedbackActivity::class.java + ) + ) + + 4 -> mHomeV2Activity.startActivity( + Intent( + context, NotificationsSetActivity::class.java + ) + ) + + 5 -> mUnitSelectDialog.showUnitDialog(object : + BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is Int) { + val unit = any + val oldUnit = Util.getMeasureSpUnit() + if (unit == oldUnit) { + return + } + item.menuValue = + if (unit == ConstantInt.Type1) context.getString(R.string.txt_imperial) + else context.getString(R.string.txt_metric) + mAccountV2Fragment.setMeasureUnit(unit) + } + } + }) +// mHomeV2Activity.startActivity( +// Intent( +// context, FirstMeasurementUnitsActivity::class.java +// ) +// ) + + + 6 -> mHomeV2Activity.startActivity( + Intent( + context, PermissionsSetActivity::class.java + ) + ) + + 7 -> mHomeV2Activity.startActivity( + Intent( + context, AdvancedSetActivity::class.java + ) + ) + + 9 -> mHomeV2Activity.startActivity( + Intent( + context, AboutActivity::class.java + ) + ) + + 10 -> dialogShowType( + mLogOutTipsType, + mHomeV2Activity.getString(R.string.txt_log_out_tips), + position + ) + + } + }, item.menuName + ) + if (!TextUtils.isEmpty(item.menuValue)) { + detailText = item.menuValue + } + setImageDrawable(ContextCompat.getDrawable(context, item.imageId)) + } + } + } + } + + private fun dialogShowType(type: Int, string: String, pos: Int) { + ViewUtil.instance.showDialog(context, string, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + when { + mLogOutTipsType == type -> { + Util.logOut(mHomeV2Activity, mHomeV2Activity.lifecycleScope) + } + + mCloseAccountTipsType == type -> { +// closeAccount() + } + + else -> { +// mHomeV2Activity.clearCache(this@HomeAccountMenuAdapter, pos) + } + } + } + }, okTextResId = R.string.txt_sure) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HomeMapDeviceMsgAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HomeMapDeviceMsgAdapter.kt new file mode 100644 index 0000000..9375cd4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HomeMapDeviceMsgAdapter.kt @@ -0,0 +1,32 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.widget.TypefaceButton + +/** + * 首页地图宠物警告适配器 + */ +class HomeMapDeviceMsgAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_home_map_device_msg + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val typefaceTextView = holder!!.getView(R.id.tv_device_msg_content) as TypefaceButton + typefaceTextView.apply { + text = item.value + val colorId = if (item.state == ConstantInt.Type1) R.color.green_color10 + else R.color.device_msg_color1 + + setBackgroundColor(ContextCompat.getColor(context, colorId)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HomeMapDeviceStateAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HomeMapDeviceStateAdapter.kt new file mode 100644 index 0000000..136130c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HomeMapDeviceStateAdapter.kt @@ -0,0 +1,70 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.text.TextUtils +import android.view.View +import androidx.appcompat.widget.AppCompatImageView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.widget.BatteryView +import com.abbidot.tracker.widget.TypefaceTextView + +/** + * 首页地图宠物警告适配器 + */ +class HomeMapDeviceStateAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_home_map_device_state + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val typefaceTextView = + holder!!.getView(R.id.tv_home_map_device_name_item) as TypefaceTextView + val iv = holder.getView(R.id.iv_home_map_device_item) as AppCompatImageView + val bv = holder.getView(R.id.bv_home_map_device_power) as BatteryView + val ivState = holder.getView(R.id.iv_home_map_device_state_item) as AppCompatImageView + val detailTextView = + holder.getTextView(R.id.tv_map_device_state_detail_item) as TypefaceTextView + + if (item.menuType == ConstantInt.Open) { + ivState.setImageResource(R.drawable.icon_map_online) + } else { + ivState.setImageResource(R.drawable.icon_map_offline) + } + + if (item.imageId == 0) { + iv.visibility = View.GONE + bv.visibility = View.VISIBLE + bv.setValue(item.state) + } else { + iv.visibility = View.VISIBLE + bv.visibility = View.GONE + iv.setImageResource(item.imageId) + } + + typefaceTextView.text = item.name + detailTextView.text = item.value + + if (item.selected) { + detailTextView.visibility = View.VISIBLE + typefaceTextView.visibility = View.GONE + } else { + detailTextView.visibility = View.GONE + if (TextUtils.isEmpty(item.name)) { + typefaceTextView.visibility = View.GONE + } else { + typefaceTextView.visibility = View.VISIBLE + } + } + + holder.itemView.setOnClickListener { + item.selected = !item.selected + notifyItemChanged(position) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HomePetFamilyAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HomePetFamilyAdapter.kt new file mode 100644 index 0000000..e8e8a13 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HomePetFamilyAdapter.kt @@ -0,0 +1,34 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.constant.ConstantInt + +/** + *Created by .yzq on 2023/12/25/025. + * @link + * @description: + */ +class HomePetFamilyAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_family_empty_view + + override fun getItemLayoutId(viewType: Int) = R.layout.item_home_pet_family + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: FamilyBean) { + holder!!.setText(R.id.item_home_pet_family_name, item.acceptEmail) + val avatarImageView = holder.getImageView(R.id.item_home_pet_family_head) + + val avatarImageResId = if (item.avatar == ConstantInt.Man) { + R.drawable.icon_family_boy_avatar + } else { + R.drawable.icon_family_girl_avatar + } + avatarImageView.setImageResource(avatarImageResId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HomePetTrackMenuAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HomePetTrackMenuAdapter.kt new file mode 100644 index 0000000..b262e80 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HomePetTrackMenuAdapter.kt @@ -0,0 +1,106 @@ +package com.abbidot.tracker.adapter + +import android.graphics.Typeface +import android.view.LayoutInflater +import android.view.View +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.ContextCompat +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.ui.activity.device.set.LedLightActivity +import com.abbidot.tracker.ui.activity.device.set.LiveDurationSetActivity +import com.abbidot.tracker.ui.activity.device.set.PowerOffActivity +import com.abbidot.tracker.ui.activity.device.set.TrackingDurationSetActivity +import com.abbidot.tracker.ui.fragment.device.HomeTrackFragment +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.TypefaceTextView +import com.chad.library.adapter.base.BaseMultiItemQuickAdapter +import com.chad.library.adapter.base.viewholder.BaseViewHolder +import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView + +/** + * + */ +class HomePetTrackMenuAdapter( + fragment: HomeTrackFragment, list: MutableList? +) : BaseMultiItemQuickAdapter(list) { + + private var mTypeface: Typeface + private var mHomeTrackFragment: HomeTrackFragment = fragment + + init { + addItemType(MultipleEntity.TEXT, R.layout.item_qmui_common_item_view) + addItemType(MultipleEntity.IMG_TEXT, R.layout.item_qmui_common_item_view) + addItemType(MultipleEntity.TEXT_NAME, R.layout.item_qmui_common_item_view) + addItemType(MultipleEntity.IMG_TEXT_IMG, R.layout.item_qmui_common_item_view) + mTypeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD) + } + + override fun convert(holder: BaseViewHolder, item: MenuTxtBean) { + val position = getItemPosition(item) + val qMUICommonItemView = + holder.getView(R.id.tracker_manage_setting_item).apply { + ViewUtil.instance.showQMUICommonListItemView( + context, this, null, item.menuName + ) + background = + ContextCompat.getDrawable(context, R.drawable.selector_transparent_pressed) + } + qMUICommonItemView.textView.typeface = mTypeface + qMUICommonItemView.setOnClickListener { + when (position) { + 0 -> mHomeTrackFragment.goPetDataActivity(LedLightActivity::class.java) + 1 -> mHomeTrackFragment.goMapDeviceDataActivity(TrackingDurationSetActivity::class.java) + 2 -> mHomeTrackFragment.goMapDeviceDataActivity(LiveDurationSetActivity::class.java) + 3 -> mHomeTrackFragment.firmwareCheckUpdate() + 4 -> mHomeTrackFragment.goPetDataActivity(PowerOffActivity::class.java) + } + } + + when (item.itemType) { + MultipleEntity.TEXT -> qMUICommonItemView.detailText = item.menuValue + + MultipleEntity.IMG_TEXT -> { + } + + MultipleEntity.TEXT_NAME -> { + qMUICommonItemView.accessoryType = QMUICommonListItemView.ACCESSORY_TYPE_SWITCH + qMUICommonItemView.detailText = "" + qMUICommonItemView.switch.apply { + isChecked = item.menuValue == ConstantInt.Open.toString() + isEnabled = isChecked + setOnClickListener { + if (!isChecked) { + //turnOff + mHomeTrackFragment.operateDeviceTips(R.string.txt_turnoff_tips, 0) + } + //屏蔽点击就变状态 + isChecked = !isChecked + } + } + } + + MultipleEntity.IMG_TEXT_IMG -> setQMUICommonItemViewHead(qMUICommonItemView, item) + } + } + + /** + * 设置QMUICommonItemView 自定义布局 + */ + private fun setQMUICommonItemViewHead( + qMUICommonItemView: QMUICommonListItemView, item: MenuTxtBean + ) { + qMUICommonItemView.detailText = "" + qMUICommonItemView.accessoryType = QMUICommonListItemView.ACCESSORY_TYPE_CUSTOM + val view = + LayoutInflater.from(context).inflate(R.layout.layout_dfu_custom_qmui_group_right, null) + qMUICommonItemView.addAccessoryCustomView(view) + + view.findViewById(R.id.iv_dfu_red_dot).visibility = + if (item.isSwitch) View.VISIBLE else View.GONE + val firmwareName = view.findViewById(R.id.tv_custom_group_dfu_detail_text) + firmwareName.text = item.menuValue + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HomePetTrackStateAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HomePetTrackStateAdapter.kt new file mode 100644 index 0000000..880a721 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HomePetTrackStateAdapter.kt @@ -0,0 +1,60 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.widget.BatteryView +import com.abbidot.tracker.widget.MyBluetoothAnimView +import com.abbidot.tracker.widget.TypefaceTextView + +/** + * 首页宠物设备状态 + */ +class HomePetTrackStateAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_home_pet_device_state + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MenuTxtBean) { + val stateTextView = holder!!.getView(R.id.tv_home_pet_device_state_item) as TypefaceTextView + stateTextView.text = item.menuName + + holder.getTextView(R.id.tv_home_pet_device_state_name_item).apply { + text = item.menuValue + setTextColor(ContextCompat.getColor(mContext, item.colorRedId)) + } + + val bv = holder.getView(R.id.bv_home_pet_device_power) as BatteryView + val bluetoothAnimView = + holder.getView(R.id.mba_home_pet_device_search_anim_item) as MyBluetoothAnimView + when (item.menuType) { + MultipleEntity.IMG_TEXT_IMG -> { + bluetoothAnimView.visibility = View.GONE + bv.visibility = View.VISIBLE + bv.setValue(item.imageId) + bluetoothAnimView.setBluetoothAnimStartAndStop(item.isSwitch) + stateTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + } + + MultipleEntity.TEXT_TEXT_IMG -> { + bluetoothAnimView.visibility = View.VISIBLE + bv.visibility = View.GONE + bluetoothAnimView.setBluetoothAnimStartAndStop(item.isSwitch) + stateTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + } + + else -> { + bv.visibility = View.GONE + bluetoothAnimView.visibility = View.GONE + stateTextView.setCompoundDrawablesWithIntrinsicBounds(0, item.imageId, 0, 0) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/HowWorkAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/HowWorkAdapter.kt new file mode 100644 index 0000000..0c07328 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/HowWorkAdapter.kt @@ -0,0 +1,27 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean +import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView + +/** + *Created by .yzq on 2022/3/22/022. + * @link + * @description: + */ +class HowWorkAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_qmui_common_item_view + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MenuTxtBean) { + val qMUICommonListItemView = + holder!!.getView(R.id.tracker_manage_setting_item) as QMUICommonListItemView + qMUICommonListItemView.detailText = item.menuValue + qMUICommonListItemView.text = item.menuName + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/LedLightColorAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/LedLightColorAdapter.kt new file mode 100644 index 0000000..d47f3eb --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/LedLightColorAdapter.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.qmuiteam.qmui.widget.QMUIRadiusImageView + +/** + *Created by .yzq on 2021/12/30/030. + * @link + * @description:LedLight默认可以选择颜色适配器 + */ +class LedLightColorAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_led_color_select + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: Int) { + val radiusImageView = + holder!!.itemView.findViewById(R.id.show_current_color_select) + radiusImageView.setImageResource(item) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/MySubscriptionAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/MySubscriptionAdapter.kt new file mode 100644 index 0000000..0ecf5fd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/MySubscriptionAdapter.kt @@ -0,0 +1,193 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.content.Intent +import android.view.LayoutInflater +import android.view.View +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.ui.activity.subscribe.ChoosePlanActivity +import com.abbidot.tracker.ui.activity.subscribe.MySubscriptionActivity +import com.abbidot.tracker.ui.activity.subscribe.SubscriptionHistoryActivity +import com.abbidot.tracker.ui.activity.subscribe.SubscriptionPlanActivity +import com.abbidot.tracker.widget.TypefaceTextView +import com.qmuiteam.qmui.skin.QMUISkinManager +import com.qmuiteam.qmui.widget.popup.QMUIPopup +import com.qmuiteam.qmui.widget.popup.QMUIPopups + + +/** + *Created by .yzq on 2024/3/29/029. + * @link + * @description: + */ +class MySubscriptionAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mMySubscriptionActivity = ctx as MySubscriptionActivity + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_my_subscription_layout + + override fun bindData( + holder: RecyclerViewHolder?, position: Int, item: SubscriptionsOrderBean + ) { + holder!!.setText(R.id.tv_my_subscription_device_out_id, item.deviceOutId) + holder.setText(R.id.tv_my_subscription_plan_name, "${item.mealName}(${item.planCategory})") + holder.getTextView(R.id.tv_my_subscription_auto_subscription_tips).apply { + visibility = if (item.subscriptionStatus == ConstantInt.Open) { + text = if (item.mealUnit == ConstantString.PackageUnitYear) String.format( + mContext.getString(R.string.txt_auto_subscription_year), + item.autoRenewPrice.toString() + ) + else String.format( + mContext.getString(R.string.txt_auto_subscription_month), + item.autoRenewPrice.toString() + ) + View.VISIBLE + } else { + View.GONE + } + } + holder.setText( + R.id.tv_my_subscription_active_time, + Utils.stringToDate(item.updateTime, resultFormat = Utils.DATE_FORMAT_PATTERN_EN6) + ) + + holder.getTextView(R.id.tv_my_subscription_annual_care).apply { + text = + if (item.annualCareType == ConstantInt.Type1) mContext.getString(R.string.start_txt_yes) + else mContext.getString(R.string.txt_no) + } + +// val timestamp = getAfterHowTimestamp( +// Utils.stringToTimestamp(item.currentTime), item.surplusDays +// ) + holder.setText( + R.id.tv_my_subscription_expires_on, + Utils.formatTime(item.endTime, Utils.DATE_FORMAT_PATTERN_EN7) + ) + + if (item.subscriptionStatus == ConstantInt.Close) { + holder.setText( + R.id.tv_my_subscription_auto_renew, + mContext.getString(R.string.tracker_manage_set_led_off) + ) + } else { + holder.setText( + R.id.tv_my_subscription_auto_renew, + mContext.getString(R.string.tracker_manage_set_led_on) + ) + } + + val expiresTitle = holder.getTextView(R.id.tv_my_subscription_expires_title) + holder.getButton(R.id.btn_my_subscription_recharge).apply { + visibility = + if (item.orderStatus == 6 || (item.surplusDays == 0L && item.subscriptionStatus == ConstantInt.Close)) { + setText(R.string.txt_card_recharge) + expiresTitle.setText(R.string.txt_expires_on2) + View.VISIBLE + } else if (item.subscriptionStatus == ConstantInt.Close) { + setText(R.string.txt_enable_auto_renew) + expiresTitle.setText(R.string.txt_expires_on1) + View.VISIBLE + } else { + expiresTitle.setText(R.string.txt_expires_on1) + View.GONE + } + } + + holder.getView(R.id.il_my_subscription_count_down).apply { + visibility = + if (item.orderStatus == 6 || (item.surplusDays == 0L && item.subscriptionStatus == ConstantInt.Close)) { + View.GONE + } else { + View.VISIBLE + } + } + + val times = Utils.getDayToYearMonthDay(item.surplusDays) + holder.setText(R.id.tv_add_success_device_expires_days, times[0]) + holder.setText(R.id.tv_add_success_device_expires_hours, times[1]) + holder.setText(R.id.tv_add_success_device_expires_min, times[2]) + + holder.getView(R.id.fl_my_subscription_btn_layout).apply { + val userId = MMKVUtil.getString(MMKVKey.UserId) + //UserId不一样就是共享设备的订单 + visibility = if (item.userId == userId) { + setOnClickListener { + showPop(holder.getView(R.id.iv_my_subscription_btn), item, position) + } + View.VISIBLE + } else View.GONE + } + holder.setClickListener(R.id.btn_my_subscription_recharge) { + if (item.surplusDays == 0L && item.subscriptionStatus == ConstantInt.Close) { + Intent(mContext, SubscriptionPlanActivity::class.java).let { + it.putExtra(ConstantString.LkSetMeal, item) + mMySubscriptionActivity.startActivity(it) + } + } else { + mMySubscriptionActivity.updateSubscriptionStatus(item.subscriptionId, 1, position) + } + } + } + + private fun showPop(v: View, item: SubscriptionsOrderBean, position: Int) { + val view = LayoutInflater.from(mContext).inflate(R.layout.pop_subscription_item, null) + val qMUIPopups = + QMUIPopups.popup(mContext).preferredDirection(QMUIPopup.DIRECTION_BOTTOM).view(view) +// .edgeProtection(QMUIDisplayHelper.dp2px(mContext, 20)) + .dimAmount(0.6f).arrowSize(AppUtils.dpToPx(6), AppUtils.dpToPx(4)) + .radius(AppUtils.dpToPx(16)).animStyle(QMUIPopup.ANIM_GROW_FROM_CENTER) + .skinManager(QMUISkinManager.defaultInstance(mContext)).show(v) + + view.findViewById(R.id.tv_pop_my_subscription_history).apply { + setOnClickListener { + qMUIPopups.dismiss() + Intent(mContext, SubscriptionHistoryActivity::class.java).let { + it.putExtra(ConstantString.DeviceId, item.deviceId) + mMySubscriptionActivity.startActivity(it) + } + } + } + //升级套餐 + view.findViewById(R.id.tv_pop_my_subscription_auto_renew).apply { + visibility = + if (item.planCategory == "Premium" || (item.surplusDays == 0L && item.subscriptionStatus == ConstantInt.Close)) { + View.GONE + } else { + View.VISIBLE + } + setOnClickListener { + qMUIPopups.dismiss() + val intent = Intent(mContext, ChoosePlanActivity::class.java) + intent.putExtra(ConstantString.Title, "Premium") + intent.putExtra(ConstantString.Upgrade, true) + intent.putExtra(ConstantString.LkSetMeal, item) + mMySubscriptionActivity.startActivity(intent) + } + } + view.findViewById(R.id.tv_pop_my_subscription_cancel).apply { + visibility = if (item.subscriptionStatus == ConstantInt.Open) { + View.VISIBLE + } else { + View.GONE + } + setOnClickListener { + qMUIPopups.dismiss() + mMySubscriptionActivity.updateSubscriptionStatus(item.subscriptionId, 0, position) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/MyTrackerV2Adapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/MyTrackerV2Adapter.kt new file mode 100644 index 0000000..a225529 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/MyTrackerV2Adapter.kt @@ -0,0 +1,116 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.activity.device.AddNewTracker1Activity +import com.abbidot.tracker.ui.activity.device.MyTrackerV2Activity +import com.abbidot.tracker.ui.activity.pet.PetProfileActivity +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.widget.QMUIRadiusImageView + +/** + *Created by .yzq on 2024/3/7/007. + * @link + * @description: + */ +class MyTrackerV2Adapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mMyTrackerActivity = mContext as MyTrackerV2Activity + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_my_tracker_v2_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DeviceBean) { + val batteryTextView = holder!!.getTextView(R.id.iv_item_my_tracker_v2_battery) + val trackerImageView = holder.getImageView(R.id.iv_item_my_tracker_v2_device) + val addTrackerLayout = holder.getView(R.id.ll_my_tracker_v2_add_tracker_layout) + val petHead = holder.getView(R.id.il_my_tracker_v2_pet_head) as QMUIRadiusImageView + val addPetLayout = holder.getView(R.id.ll_my_tracker_v2_add_pet_layout) + val trackerName = holder.getTextView(R.id.tv_my_tracker_v2_device_name) + val petName = holder.getTextView(R.id.tv_my_tracker_v2_pet_name) + + holder.getImageView(R.id.iv_my_tracker_v2_share).apply { + visibility = if (item.shared == ConstantInt.NoShare) View.GONE else View.VISIBLE + } + + addTrackerLayout.setOnClickListener { + if (itemCount - 1 == position) { + mMyTrackerActivity.startActivity( + Intent( + mContext, AddNewTracker1Activity::class.java + ) + ) + } else { + mMyTrackerActivity.getAssignData(position) + } + } + addPetLayout.setOnClickListener { + if (itemCount - 1 == position) { + mMyTrackerActivity.startActivity(Intent(mContext, PetProfileActivity::class.java)) + } else { + mMyTrackerActivity.getAssignData(position) + } + } + + trackerImageView.setOnClickListener { + if (item.shared == ConstantInt.NoShare) { + mMyTrackerActivity.showDeviceBottomSheetMenu(position) + } + } + petHead.setOnClickListener { + if (item.shared == ConstantInt.NoShare) { + mMyTrackerActivity.showPetBottomSheetMenu(position) + } + } + + if (itemCount - 1 == position) { + batteryTextView.visibility = View.GONE + trackerImageView.visibility = View.GONE + trackerName.visibility = View.GONE + addTrackerLayout.visibility = View.VISIBLE + + addPetLayout.visibility = View.VISIBLE + petHead.visibility = View.GONE + petName.visibility = View.GONE + } else { + if (TextUtils.isEmpty(item.deviceOutId)) { + addTrackerLayout.visibility = View.VISIBLE + batteryTextView.visibility = View.GONE + trackerImageView.visibility = View.GONE + trackerName.visibility = View.GONE + } else { + addTrackerLayout.visibility = View.GONE + batteryTextView.visibility = View.VISIBLE + trackerImageView.visibility = View.VISIBLE + trackerName.visibility = View.VISIBLE + + trackerName.text = item.deviceOutId + val battery = "${item.batteryCapacity} " + batteryTextView.text = battery + } + if (null == item.pet) { + addPetLayout.visibility = View.VISIBLE + petHead.visibility = View.GONE + petName.visibility = View.GONE + } else { + addPetLayout.visibility = View.GONE + petHead.visibility = View.VISIBLE + petName.visibility = View.VISIBLE + item.pet?.let { + petName.text = it.petName + ViewUtil.instance.setPetTypeHead(petHead, it.imgurl, it.petType) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/NotificationV2Adapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/NotificationV2Adapter.kt new file mode 100644 index 0000000..49af39b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/NotificationV2Adapter.kt @@ -0,0 +1,73 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.text.TextUtils +import android.text.format.DateUtils +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MessageBean +import com.abbidot.tracker.widget.TypefaceRoundButton + +/** + *Created by .yzq on 2023/12/26/026. + * @link + * @description: + */ +class NotificationV2Adapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_home_route_notification + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MessageBean) { + val roundButton = + holder!!.getView(R.id.iv_home_route_notification_state) as TypefaceRoundButton + val textColor = if (item.isRead) { + roundButton.visibility = View.GONE + R.color.black70 + } else { + roundButton.visibility = View.VISIBLE + R.color.data_black_color + } + holder.getTextView(R.id.tv_home_route_notification_content).apply { + text = item.message + if (item.isSelect) { + ellipsize = null + isSingleLine = false + } else { + ellipsize = TextUtils.TruncateAt.END + isSingleLine = true + } + setTextColor(ContextCompat.getColor(mContext, textColor)) + } + holder.getTextView(R.id.tv_home_route_notification_type).apply { + text = item.title + setTextColor(ContextCompat.getColor(mContext, textColor)) + } + holder.getTextView(R.id.tv_home_route_notification_date).apply { + text = getRelativeTimeString(Utils.stringToTimestamp(item.sendTime)) + setTextColor(ContextCompat.getColor(mContext, textColor)) + } + } + + private fun getRelativeTimeString(time: Long): String { + var formatDateStr = "" + val now = System.currentTimeMillis() + formatDateStr = if (DateUtils.isToday(time)) { + mContext.getString(R.string.txt_today) + " " + Utils.formatTime( + time, Utils.DATE_FORMAT_PATTERN_EN8 + ) + } else if (now - time < (48 * 60 * 60 * 1000)) { + mContext.getString(R.string.txt_yesterday) + } else { + Utils.formatTime(time, Utils.DATE_FORMAT_PATTERN_EN9) + } + return formatDateStr + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/PermissionsSetAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/PermissionsSetAdapter.kt new file mode 100644 index 0000000..3cc98ac --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/PermissionsSetAdapter.kt @@ -0,0 +1,66 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.activity.set.PermissionsSetActivity +import com.abbidot.tracker.widget.TypefaceButton + +/** + *Created by .yzq on 2024/3/25/025. + * @link + * @description: + */ +class PermissionsSetAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mPermissionsSetActivity = ctx as PermissionsSetActivity + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_permission_state_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val stateView = holder!!.getImageView(R.id.cb_permission_state_item) + val typefaceTextView = holder.getTextView(R.id.tv_permission_tip_item) + val btnLayout = holder.getView(R.id.ll_permission_set_layout) + val btnSkip = holder.getView(R.id.btn_permission_skip_set) as TypefaceButton + val btnEnable = holder.getView(R.id.btn_permission_enable_set) as TypefaceButton + btnSkip.visibility = View.INVISIBLE + btnEnable.setChangeAlphaWhenPress(true) + btnEnable.setText(R.string.txt_turn_on_upper) + + if (item.selected) { + typefaceTextView.visibility = View.VISIBLE + btnLayout.visibility = View.VISIBLE + holder.itemView.setBackgroundResource(R.drawable.shape16_white_color_bg) + } else { + btnLayout.visibility = View.GONE + typefaceTextView.visibility = View.GONE + holder.itemView.setBackgroundResource(R.drawable.shape10_white_bg) + } + + when (item.state) { + ConstantInt.Type0 -> stateView.setImageResource(R.drawable.icon_permission_error_check_svg) + ConstantInt.Type1 -> stateView.setImageResource(R.drawable.icon_permission_check_svg) + } + + holder.setText(R.id.tv_permission_name_item, item.name) + typefaceTextView.text = item.value + + btnEnable.setOnClickListener { + when (position) { + 0 -> mPermissionsSetActivity.mPermissionsCommon.operateBluetooth() + 1 -> mPermissionsSetActivity.mPermissionsCommon.operateGPSLocation() + 2 -> mPermissionsSetActivity.mPermissionsCommon.operateNotifications( + this, ConstantInt.Type0 + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/PetLabelsAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/PetLabelsAdapter.kt new file mode 100644 index 0000000..17650ba --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/PetLabelsAdapter.kt @@ -0,0 +1,57 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.LabelBean +import com.qmuiteam.qmui.layout.QMUILinearLayout +import kotlin.random.Random + +/** + *Created by .yzq on 2024/2/21/021. + * @link + * @description: + */ +class PetLabelsAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + + private val mLabelBgColorList = mutableListOf( + R.color.green_color10, + R.color.label_color2, + R.color.label_color3, + R.color.label_color4, + R.color.label_color5, + R.color.label_color6, + R.color.label_color7, + R.color.label_color8, + R.color.label_color9, + R.color.label_color10, + R.color.label_color11, + R.color.label_color12, + R.color.label_color13, + R.color.label_color14 + ) + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_pet_label_layout_v2 + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: LabelBean) { + val textView = holder!!.getTextView(R.id.tv_label_v2_name) + val rootView = holder.itemView as QMUILinearLayout + val imageView = holder.getImageView(R.id.iv_label_v2_is_select) + if (item.check) { + imageView.visibility = View.VISIBLE + } else { + imageView.visibility = View.GONE + } + textView.text = item.name + + rootView.setBackgroundResource(mLabelBgColorList[Random.nextInt(mLabelBgColorList.size)]) + + rootView.radius = AppUtils.dpToPx(6) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/PetsBadgesAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/PetsBadgesAdapter.kt new file mode 100644 index 0000000..b9f0c4f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/PetsBadgesAdapter.kt @@ -0,0 +1,31 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean + +/** + *Created by .yzq on 2021/12/30/030. + * @link + * @description: + */ +class PetsBadgesAdapter(ctx: Context, list: MutableList?,itemViewWidthDp:Int=80) : + BaseRecyclerAdapter(ctx, list) { + + private val mItemViewWidthDp=AppUtils.dpToPx(itemViewWidthDp) + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_pets_badges_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + holder!!.getTextView(R.id.tv_item_my_pets_badge).apply { + text = item.name + } + holder.itemView.layoutParams.width= mItemViewWidthDp + holder.getImageView(R.id.iv_item_my_pets_badge).setImageResource(item.imageId) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/PetsParentBadgesAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/PetsParentBadgesAdapter.kt new file mode 100644 index 0000000..7e15840 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/PetsParentBadgesAdapter.kt @@ -0,0 +1,89 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.content.Intent +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.ParentBadgeBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.ui.activity.pet.BadgeDetailActivity +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.TypefaceButton + +/** + *Created by .yzq on 2021/12/30/030. + * @link + * @description: + */ +class PetsParentBadgesAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + + //默认显示的勋章个数 + private val defaultShowCount = 9 + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_pets_parent_badges_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: ParentBadgeBean) { + val recyclerView = holder!!.getView(R.id.rv_pet_badges_list) as RecyclerView + holder.setText(R.id.tv_pets_parent_badges_name, item.parentName) + val btn = (holder.getView(R.id.btn_parent_badges_expand_fold) as TypefaceButton).apply { + visibility = View.GONE + if (item.isExpand) { + setText(R.string.txt_view_less) + } else { + setText(R.string.txt_see_all) + } + setOnClickListener { + item.isExpand = !item.isExpand + notifyItemChanged(position) + } + } + item.badgeList?.apply { + if (size > defaultShowCount) { + btn.visibility = View.VISIBLE + } else { + btn.visibility = View.GONE + } + } + + val list = if (item.isExpand || null == item.badgeList || item.badgeList!!.size < 4) { + item.badgeList + } else { + item.badgeList!!.subList(0, defaultShowCount) + } + if (null == recyclerView.adapter) { + val petsBadgesAdapter = PetsBadgesAdapter(mContext, list).apply { + setOnItemClickListener(object : OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val activity = mContext as BaseActivity<*> + Intent(mContext, BadgeDetailActivity::class.java).let { + it.putExtra(ConstantString.PetLabels, list!![pos]) + activity.startActivity(it) + } + } + }) + } + + ViewUtil.instance.setRecyclerViewGridLayout( + mContext, + recyclerView, + petsBadgesAdapter, + 3, + AppUtils.dpToPx(80), + top = AppUtils.dpToPx(16), + bottom = AppUtils.dpToPx(18), + offset = AppUtils.dpToPx(32) + ) + } else { + val adapter = recyclerView.adapter as PetsBadgesAdapter + adapter.setData(list, true) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SelectFencesTypeCardShadeAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SelectFencesTypeCardShadeAdapter.kt new file mode 100644 index 0000000..a28254a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SelectFencesTypeCardShadeAdapter.kt @@ -0,0 +1,37 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.cardview.widget.CardView +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + */ +class SelectFencesTypeCardShadeAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_select_type_card_shade_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val imageView = holder!!.getImageView(R.id.iv_select_type_image) + imageView.setImageResource(item.imageId) + val cardView = holder.itemView as CardView + cardView.apply { + isSelected = item.selected + cardElevation = if (item.selected) { + setCardBackgroundColor(ContextCompat.getColor(context, R.color.select_color)) + QMUIDisplayHelper.dpToPx(4).toFloat() + } else { + setCardBackgroundColor(ContextCompat.getColor(context, R.color.transparent)) + 0f + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SelectMapListDialogAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SelectMapListDialogAdapter.kt new file mode 100644 index 0000000..52dc62b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SelectMapListDialogAdapter.kt @@ -0,0 +1,29 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean + +/** + *Created by .yzq on 2022/1/10/010. + * @link + * @description:选择地图应用列表弹窗 + */ +class SelectMapListDialogAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_select_map + + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MenuTxtBean) { + val textView = holder!!.getTextView(R.id.item_select_map_name) + textView.text = item.menuName + textView.setCompoundDrawablesWithIntrinsicBounds( + item.imageId, 0, 0, 0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SelectMapTypeCardShadeAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SelectMapTypeCardShadeAdapter.kt new file mode 100644 index 0000000..ee6aed2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SelectMapTypeCardShadeAdapter.kt @@ -0,0 +1,41 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.cardview.widget.CardView +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean + +/** + */ +class SelectMapTypeCardShadeAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_map_type_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val imageView = holder!!.getImageView(R.id.iv_select_type_image) + imageView.setImageResource(item.imageId) + holder.setText(R.id.tv_map_type_name, item.name) + val cardView = holder.getView(R.id.il_map_type_shade_image) as CardView + cardView.apply { + isSelected = item.selected + cardElevation = if (item.selected) { + setCardBackgroundColor(ContextCompat.getColor(context, R.color.white)) + val padding = AppUtils.dpToPx(3) + setContentPadding(padding, padding, padding, padding) + AppUtils.dpToPx(4).toFloat() + } else { + setCardBackgroundColor(ContextCompat.getColor(context, R.color.transparent)) + setContentPadding(0, 0, 0, 0) + 0f + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SpaceItemDecoration.kt b/app/src/main/java/com/abbidot/tracker/adapter/SpaceItemDecoration.kt new file mode 100644 index 0000000..86b23a6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SpaceItemDecoration.kt @@ -0,0 +1,37 @@ +package com.abbidot.tracker.adapter + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +/** + *Created by .yzq on 2021/12/24/024. + * @link https://qa.1r1g.com/sf/ask/1997239751/ + * @description:RecyclerView的LinearLayoutManager和FlexboxLayoutManager通用item间距设置 + */ +class SpaceItemDecoration( + left: Int = 0, right: Int = 0, top: Int = 0, bottom: Int = 0 +) : RecyclerView.ItemDecoration() { + + private var mLeft = left + private var mRight = right + private var mTop = top + private var mBottom = bottom + + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State + ) { + if (mLeft > 0) { + outRect.left = mLeft + } + if (mRight > 0) { + outRect.right = mRight + } + if (mTop > 0) { + outRect.top = mTop + } + if (mBottom > 0) { + outRect.bottom = mBottom + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionHistoryAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionHistoryAdapter.kt new file mode 100644 index 0000000..a283407 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionHistoryAdapter.kt @@ -0,0 +1,122 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.content.Intent +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.ui.activity.subscribe.RequestRefundActivity +import com.abbidot.tracker.ui.activity.subscribe.SubscriptionHistoryActivity +import com.abbidot.tracker.widget.TypefaceRoundButton + +/** + *Created by .yzq on 2024/8/19/019. + * @link + * @description: + */ +class SubscriptionHistoryAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + private val mHistoryActivity = ctx as SubscriptionHistoryActivity + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_subscription_history_layout + + override fun bindData( + holder: RecyclerViewHolder?, position: Int, item: SubscriptionsOrderBean + ) { + holder!!.setText( + R.id.tv_subscription_history_order_id, + mContext.getString(R.string.txt_order_id) + item.orderNum + ) + holder.setText( + R.id.tv_subscription_history_order_time, + Utils.stringToDate(item.createTime, resultFormat = Utils.DATE_FORMAT_PATTERN_EN10) + ) + holder.setText(R.id.tv_subscription_history_package_name, item.mealName) + holder.setText( + R.id.tv_subscription_history_package_price, + String.format(mContext.getString(R.string.txt_money_unit), "${item.totalAmountWithTax}") + ) + + + (holder.getView(R.id.btn_subscription_history_operate) as TypefaceRoundButton).apply { + + setTextColor(ContextCompat.getColor(mContext, R.color.line_color1)) + + if (item.enabled == ConstantInt.Type0) visibility = View.GONE + else { + visibility = View.VISIBLE + when (item.orderStatus) { + 1 -> { + val updateTimestamp = Utils.stringToTimestamp(item.updateTime) + val nowTimestamp = System.currentTimeMillis() +// val nowTimestamp = Utils.stringToTimestamp(item.currentTime) + if (item.mealUnit == ConstantString.PackageUnitMonth) { + val day7Timestamp = 7 * 24 * 60 * 60 * 1000L + //套餐超出7天不能退款 + if (nowTimestamp - updateTimestamp <= day7Timestamp) { + visibility = View.VISIBLE + setText(R.string.txt_refund) + } else { + visibility = View.GONE + } + } else { + val day30Timestamp = 30 * 24 * 60 * 60 * 1000L + //套餐超出30天不能退款 + if (nowTimestamp - updateTimestamp <= day30Timestamp) { + visibility = View.VISIBLE + setText(R.string.txt_refund) + } else { + visibility = View.GONE + } + } + setTextColor(ContextCompat.getColor(mContext, R.color.select_color)) + } + + 4 -> setText(R.string.txt_waiting) + 5 -> setText(R.string.txt_refunding) + 6 -> setText(R.string.txt_refunded) + 7, 8 -> { + setTextColor(ContextCompat.getColor(mContext, R.color.orange_red_color)) + setText(R.string.txt_rejected) + } + + else -> visibility = View.GONE + } + } + + setChangeAlphaWhenPress(true) + setOnClickListener { + if (item.orderStatus == ConstantInt.Type1) { + Intent(mContext, RequestRefundActivity::class.java).apply { + putExtra(ConstantString.SetMeal, item) + mHistoryActivity.startActivity(this) + } + } + } + } + + val state = + if (item.enabled == ConstantInt.Type0 || (item.surplusDays == 0L && item.subscriptionStatus == ConstantInt.Close)) { + mContext.getString(R.string.txt_expired) + } else { + mContext.getString(R.string.txt_active) + } + holder.setText(R.id.tv_subscription_history_subscription_state, state) + + holder.getTextView(R.id.tv_subscription_history_annual_care).apply { + text = + if (item.annualCareType == ConstantInt.Type1) mContext.getString(R.string.start_txt_yes) + else mContext.getString(R.string.txt_no) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionPlanAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionPlanAdapter.kt new file mode 100644 index 0000000..ae672e4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionPlanAdapter.kt @@ -0,0 +1,45 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.widget.RelativeLayout +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.PackageTypeBean + +/** + *Created by .yzq on 2024/3/19/019. + * @link + * @description: + */ +class SubscriptionPlanAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_subscription_plan_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: PackageTypeBean) { + val relativeLayout = holder!!.getView(R.id.rl_subscribe_plan_shade_item) as RelativeLayout +// val popularCardView = holder.getView(R.id.cv_subscribe_plan_item_popular) as CardView +// val subscription = holder.getTextView(R.id.tv_subscribe_plan_item_per_year) + + if (item.selected) { + relativeLayout.setBackgroundResource(R.drawable.shape20_yellow_border_cyan_bg) +// popularCardView.visibility = View.VISIBLE + } else { + relativeLayout.setBackgroundResource(0) +// popularCardView.visibility = View.GONE + } + holder.setText(R.id.tv_subscribe_plan_item_name, item.name) + holder.setText( + R.id.tv_subscribe_plan_item_money, + String.format(mContext.getString(R.string.txt_money_unit), Utils.formatDecimal( item.price, 2)) + ) +// subscription.text = +// String.format(mContext.getString(R.string.txt_per_year_money), item.subscriptionFee) +// subscription.visibility = if (item.subscriptionFee == 0.0) View.INVISIBLE +// else View.VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionPlanDetailAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionPlanDetailAdapter.kt new file mode 100644 index 0000000..0369e71 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SubscriptionPlanDetailAdapter.kt @@ -0,0 +1,31 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.appcompat.widget.AppCompatCheckBox +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.PackageBenefitsBean +import com.abbidot.tracker.constant.ConstantInt + +/** + *Created by .yzq on 2024/3/19/019. + * @link + * @description: + */ +class SubscriptionPlanDetailAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_subscription_plan_detail_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: PackageBenefitsBean) { + val checkBox = holder!!.getView(R.id.cb_subscribe_plan_detail_item) as AppCompatCheckBox + checkBox.setOnClickListener { + checkBox.isChecked = !checkBox.isChecked + } + holder.setText(R.id.tv_subscribe_plan_detail_name, item.title) + checkBox.isChecked = item.status == ConstantInt.Open + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/SureSubscribeSummaryAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/SureSubscribeSummaryAdapter.kt new file mode 100644 index 0000000..7d07159 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/SureSubscribeSummaryAdapter.kt @@ -0,0 +1,34 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean + +/** + *Created by .yzq on 2024/9/9/009. + * @link + * @description: + */ +class SureSubscribeSummaryAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_subscribe_summary_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MenuTxtBean) { + holder?.apply { + setText(R.id.tv_subscribe_summary_item_name, item.menuName) + getTextView(R.id.tv_subscribe_summary_item_money).let { + val string = + String.format(mContext.getString(R.string.txt_money_unit), item.menuValue) + it.text = if (item.isSwitch) "-$string" + else string + it.setTextColor(ContextCompat.getColor(mContext, item.colorRedId)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/TextViewAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/TextViewAdapter.kt new file mode 100644 index 0000000..8d3158b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/TextViewAdapter.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean + +/** + *Created by .yzq on 2024/2/20/020. + * @link + * @description: + */ +class TextViewAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_text_view + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + holder!!.setText(R.id.tv_text_view_item, item.name) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/ViewPagerAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/ViewPagerAdapter.kt new file mode 100644 index 0000000..18b04cf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/ViewPagerAdapter.kt @@ -0,0 +1,21 @@ +package com.abbidot.tracker.adapter + +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter + +/** + *Created by .yzq on 2021/11/19/019. + * @link + * @description: + */ +class ViewPagerAdapter(list: MutableList, fragmentActivity: FragmentActivity) : + FragmentStateAdapter(fragmentActivity) { + + private val mFragmentList = list + + override fun getItemCount() = mFragmentList.size + + override fun createFragment(position: Int) = mFragmentList[position] + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/VirtualFencesZoneShadeAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/VirtualFencesZoneShadeAdapter.kt new file mode 100644 index 0000000..564e274 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/VirtualFencesZoneShadeAdapter.kt @@ -0,0 +1,34 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import androidx.cardview.widget.CardView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *围栏类型 + */ +class VirtualFencesZoneShadeAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_shadow_image_view + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DataBean) { + val imageView = holder!!.getImageView(R.id.iv_item_shadow_image_view) + val cardView = holder.itemView as CardView + + if (item.selected) { + imageView.setImageResource(item.selectImageId) + cardView.cardElevation = QMUIDisplayHelper.dpToPx(4).toFloat() + } else { + imageView.setImageResource(item.imageId) + cardView.cardElevation = 0f + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/WiFiListAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/WiFiListAdapter.kt new file mode 100644 index 0000000..93a3e17 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/WiFiListAdapter.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.WiFiBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.widget.MyWiFiSignalView + +/** + *Created by .yzq on 2024/3/26/026. + * @link + * @description: + */ +class WiFiListAdapter( + ctx: Context, list: MutableList? +) : BaseRecyclerAdapter(ctx, list) { + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_wifi_info_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: WiFiBean) { +// val wifiName = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// item.wifiSsid.toString() +// } else { +// item.SSID +// } + holder!!.setText(R.id.tv_wifi_item_name, item.name) + val signalView = holder.getView(R.id.tv_wifi_item_signal) as MyWiFiSignalView + val signal = Utils.calculateSignalLevel(item.signal, 3) + if (item.auth_type == ConstantInt.Type0) { + signalView.setValue(signal) + } else { + signalView.setValue(signal, true) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/adapter/WiFiZoneAdapter.kt b/app/src/main/java/com/abbidot/tracker/adapter/WiFiZoneAdapter.kt new file mode 100644 index 0000000..260056d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/adapter/WiFiZoneAdapter.kt @@ -0,0 +1,43 @@ +package com.abbidot.tracker.adapter + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.constant.ConstantInt + +/** + */ +class WiFiZoneAdapter( + ctx: Context, list: MutableList?, isShowArrowRight: Boolean = true +) : BaseRecyclerAdapter(ctx, list) { + + private val isShowArrow = isShowArrowRight + + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_virtual_fences_empty_view + + override fun getItemLayoutId(viewType: Int) = R.layout.item_wifi_zone_layout + + override fun noData(holder: RecyclerViewHolder?) { + holder?.apply { + getView(R.id.btn_empty_fences_add_safe_zone).visibility = + if (isShowNoDataAddButton) View.VISIBLE + else View.GONE + } + } + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: WiFiZoneBean) { + holder!!.setText(R.id.tv_wifi_zone_name, item.wifiZoneName) + holder.setText(R.id.tv_wifi_zone_address, item.address) + val wifiZoneTypeImage = + if (item.type == ConstantInt.Type1) R.drawable.icon_wifi_zone_type1_svg + else R.drawable.icon_wifi_zone_type2_svg + holder.getImageView(R.id.iv_wifi_zone_type).setBackgroundResource(wifiZoneTypeImage) + + holder.getImageView(R.id.iv_wifi_zone_list_arrow_right).visibility = + if (isShowArrow) View.VISIBLE + else View.GONE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/alipay/PayResult.kt b/app/src/main/java/com/abbidot/tracker/alipay/PayResult.kt new file mode 100644 index 0000000..001d31b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/alipay/PayResult.kt @@ -0,0 +1 @@ +package com.abbidot.tracker.alipay import android.text.TextUtils /** * 支付宝支付验证消息 */ class PayResult(rawResult: Map?) { /** * @return the resultStatus */ var resultStatus: String? = null /** * @return the result */ var result: String? = null /** * @return the memo */ var memo: String? = null override fun toString(): String { return "resultStatus={$resultStatus};memo={$memo};result={$result}" } init { rawResult?.let { for (key in it.keys) { when { TextUtils.equals(key, "resultStatus") -> { resultStatus = it[key] } TextUtils.equals(key, "result") -> { result = it[key] } TextUtils.equals(key, "memo") -> { memo = it[key] } } } } } } \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt b/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt new file mode 100644 index 0000000..70d199f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseActivity.kt @@ -0,0 +1,799 @@ +package com.abbidot.tracker.base + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.graphics.Rect +import android.os.Bundle +import android.text.TextUtils +import android.view.Gravity +import android.view.LayoutInflater +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.view.Window +import android.view.inputmethod.InputMethodManager +import android.widget.Button +import android.widget.EditText +import androidx.activity.OnBackPressedCallback +import androidx.activity.result.ActivityResultLauncher +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.ContextCompat +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentTransaction +import androidx.viewbinding.ViewBinding +import com.abbidot.baselibrary.network.exception.ErrorCode +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.util.ViewUtil +import com.hjq.toast.Toaster +import com.qmuiteam.qmui.alpha.QMUIAlphaButton +import com.qmuiteam.qmui.alpha.QMUIAlphaImageButton +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import com.qmuiteam.qmui.util.QMUIStatusBarHelper +import com.qmuiteam.qmui.widget.QMUITopBar +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + + +/** + *Created by .yzq on 2021/11/2/002. + * @link + * @description: Activity基类 + */ +abstract class BaseActivity(val inflater: (inflater: LayoutInflater) -> T) : + AppCompatActivity(), View.OnClickListener { + + protected lateinit var mViewBinding: T + var mTopBar: QMUITopBar? = null + lateinit var mContext: Context + lateinit var mActivity: Activity + private var mLoadingDialog: QMUITipDialog? = null + + //默认删除去掉titleTopBar底部间隔下划线 + var mDeleteBottomDivider: Boolean = true + + //Activity是否在前台显示运行 + var isFrontRunning = false + + //是否需要通知上个页面更新数据 + var isNotifyUpdate = false + + //右边图标按钮 + var mRightImageButton: QMUIAlphaImageButton? = null + + //右边按钮 + var mRightTextButton: Button? = null + + //网络请求失败,需要重试次数 + var mNetworkRequestsFailRetryCount = 0 + val mNetworkRequestsFailMaxCount = 3 + val mNetworkRequestsFailLimit = "networkRequestsFailLimit" + + //是否拦截网络请求错误code提示 + var isInterceptNetErrorCodeTip = false + + //软键盘管理 + private var mInputMethodManager: InputMethodManager? = null + + // 监听因输入框弹出,遮挡输入框,界面移动,需要移动的view + private var mTranslateView: View? = null + + /*****************************监听软键盘弹出,输入框上移start************************************/ + private var isKeyboardTranslate = true + + //是否需要适配底部EdgeToEdge + var isEdgeToEdgeAdapterNavigationBars = true + + override fun onCreate(savedInstanceState: Bundle?) { + isFrontRunning = true + super.onCreate(savedInstanceState) + mViewBinding = inflater(layoutInflater) + mContext = this + mActivity = this + //禁用屏幕旋转 代码设置无效 +// requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + mViewBinding.root.apply { + //点击屏幕页面隐藏软键盘start + setOnClickListener { + hideInputMethod(this) + } + + //判断是否是深色模式 + if (AppUtils.isDarkThemeEnabled(mContext)) { + // 设备处于深色模式 + setBackgroundResource(R.color.gray) + } else { + // 设备处于浅色模式,统一设置的渐变背景色 + background = ContextCompat.getDrawable(mContext, R.drawable.v2_bg_color_gradient) + } + } + setContentView(mViewBinding.root) + mTopBar = getTopBar() + + //拦截返回按键 + onBackPressedDispatcher.addCallback(this, object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + listenBackPressed() + //Removing this callback +// remove() +// onBackPressedDispatcher.onBackPressed() + } + }) + + initData() + liveDataObserve() + } + + // 字体大小不跟随系统 + override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(getConfigurationContext(newBase)) + } + + // 字体大小不跟随系统 + private fun getConfigurationContext(context: Context): Context { + val configuration = context.resources.configuration + configuration.fontScale = 1F + return context.createConfigurationContext(configuration) + } + + /* + lifecycleScope.launch { + + Dispatchers.IO + 专为磁盘和网络IO进行了优化 + 数据库-文件读写-网络处理 + Dispatchers.Default + 专为CPU密集型任务进行了优化 + 数组排序 - JSON数据解析-处理差异判断 + withContext(Dispatchers.Default){ + + } + lifecycleScope.coroutineContext + } + */ + + override fun onResume() { + super.onResume() + isFrontRunning = true + } + + override fun onPause() { + super.onPause() + //键盘隐藏恢复默认状态 + mTranslateView?.apply { + scrollTo(0, 0) + isKeyboardTranslate = false + } + } + + override fun onStop() { + super.onStop() + showLoading(false) + isFrontRunning = false + } + + /** + * 没有TopBar就设置返回null + */ + abstract fun getTopBar(): QMUITopBar? + + /** + * 注册监听livedata观察者 + */ + open fun liveDataObserve() {} + + /** + * open修饰的类和方法可以被子类继承和重写 + * 初始化 + */ + open fun initData() { + //沉浸式状态栏 + QMUIStatusBarHelper.translucent(window) + //判断是否是深色模式 + if (AppUtils.isDarkThemeEnabled(mContext)) { + setStatusBarDark() + } else { + setStatusBarLight() + } + mTopBar?.apply { + //适配状态栏 + this.parent?.let { + edgeToEdgeAdapterBars(it as View, WindowInsetsCompat.Type.statusBars()) + } + //去掉titleTopBar底部间隔下划线 + if (mDeleteBottomDivider) updateBottomDivider(0, 0, 0, 0) + } + //适配底部导航栏 + if (isEdgeToEdgeAdapterNavigationBars) edgeToEdgeAdapterBars(mViewBinding.root) + } + + /** + * 和 setContentView 对应的方法 + */ + private fun getContentView(): ViewGroup { + return findViewById(Window.ID_ANDROID_CONTENT) + } + + /** + * 监听键盘弹出和隐藏,解决键盘弹出隐藏输入框的问题 + */ + fun setListenKeyboardChange(windowTranslateY: Int = 500, view: View = window.decorView) { + window.decorView.viewTreeObserver.addOnGlobalLayoutListener { + onLayoutChange(view, windowTranslateY) + } + } + + /** + * 设置其他包含的view点击。隐藏软键盘,如ScrollView + */ + fun setViewClickHideInputMethod(view: View) { + // 点击外部隐藏软键盘,提升用户体验 + view.setOnClickListener { + hideInputMethod(view) + } + } + + /** + * edgeToEdge全面屏适配Android15 底部。防止view被菜单、状态栏遮挡 + * https://mp.weixin.qq.com/s?__biz=MzA5MzI3NjE2MA==&mid=2650286452&idx=1&sn=354b78ae82c52b6d3d510256779a3df9&scene=21&poc_token=HN91iGij_n_mGA9z9ZarQC_44XYBhBjN9N9z_w1_ + */ + fun edgeToEdgeAdapterBars( + view: View, typeMask: Int = WindowInsetsCompat.Type.navigationBars() + ) { + ViewCompat.setOnApplyWindowInsetsListener(view) { v, insets -> + val bars = insets.getInsets(typeMask) + v.setPadding(bars.left, bars.top, bars.right, bars.bottom) + insets + } + } + + /** + * 修改状态颜色,有状态栏时用到,现在状态栏直接被占位 + */ +// fun setStatusBarColor(color: Int) { +// //这里动态修改颜色 +// window.statusBarColor = color +// } + + /** + * 设置状态栏白色字体与图标 + */ + fun setStatusBarDark() { + QMUIStatusBarHelper.setStatusBarDarkMode(this) + } + + /** + * 设置状态栏黑色字体与图标 + */ + fun setStatusBarLight() { + QMUIStatusBarHelper.setStatusBarLightMode(this) + } + + /** + * 设置多个view的监听事件 + */ + fun setOnClickListenerViews(vararg views: View?) { + for (v in views) { + v?.let { + if (v is QMUIAlphaButton) { + //设置按下点击透明效果 + v.setChangeAlphaWhenPress(true) + } else if (v is QMUIAlphaImageButton) { + //设置按下点击透明效果 + v.setChangeAlphaWhenPress(true) + } + v.setOnClickListener(this) + } + } + } + + override fun onClick(v: View?) { + // 默认不实现,让子类实现 + } + + /** + * 按钮开启/禁用点击 + */ + fun setButtonEnabled(v: View, enabled: Int = ConstantInt.SpecialType) { + when (enabled) { + ConstantInt.SpecialType -> v.isEnabled = !v.isEnabled + ConstantInt.Type0 -> v.isEnabled = false + ConstantInt.Type1 -> v.isEnabled = true + } + } + + /** + * 设置title标题 resIdTitle + */ + fun setTopBarTitle(resIdTitle: Int) { + setTopBarTitle(getString(resIdTitle)) + } + + /** + * 设置透明一体化状态栏TopBarTitle,只有返回按钮 + */ + fun setTranslucentTopBar() { + //去掉titleTopBar背景颜色,设置TopBar背景透明 + mTopBar!!.setBackgroundAlpha(0) + setTopBarTitle("") + } + + /** + * 设置按钮图片 + */ + fun setLeftBackImage(@DrawableRes resId: Int) { + mTopBar!!.removeAllLeftViews() + val leftBtn = mTopBar!!.addLeftImageButton(resId, R.id.topbar_left_change_button) + if (resId == 0) { + return + } + leftBtn.setOnClickListener { leftBackOnClick() } + } + + /** + * 需要重写返回按钮时 + */ + open fun leftBackOnClick() { + finish() + } + + /** + * 拦截返回按键 + */ +// override fun onBackPressed() { +// leftBackOnClick() +// super.onBackPressed() +// } + + open fun listenBackPressed() { + leftBackOnClick() + } + + + /** + * 添加TopBar右边图片按钮 + */ + fun addRightImageButton(@DrawableRes resId: Int): QMUIAlphaImageButton { + val rightBtn = mTopBar!!.addRightImageButton(resId, R.id.topbar_right_change_button) + setOnClickListenerViews(rightBtn) + return rightBtn + } + + /** + * 添加TopBar右边文字按钮 + */ + fun addRightTextButton( + @StringRes resId: Int, textColor: Int = R.color.select_color + ): Button { + val textButton = mTopBar!!.addRightTextButton( + getString(resId), R.id.topbar_right_change_textview + ).apply { + setBackgroundResource(R.drawable.selector_transparent_pressed) + setTextColor(ContextCompat.getColor(mContext, textColor)) + setOnClickListenerViews(this) + } + return textButton + } + + /** + * 设置title标题titleString + */ + fun setTopBarTitle(title: String) { + if (null != mTopBar) { + if (TextUtils.isEmpty(title)) mTopBar!!.showTitleView(false) + else { + mTopBar!!.setTitle(title) + val tf = + ViewUtil.instance.setTypeface(mContext, getString(R.string.roboto_bold_font)) + mTopBar!!.titleView!!.setTypeface(tf) + } + mTopBar!!.addLeftBackImageButton().setOnClickListener { leftBackOnClick() } + } + } + + fun isShowLoading(): Boolean { + if (null != mLoadingDialog && mLoadingDialog!!.isShowing) { + return true + } + return false + } + + fun showLoading(show: Boolean, tipsRes: Int, cancelable: Boolean = true) { + showLoading(show, getString(tipsRes), cancelable) + } + + fun showLoading(show: Boolean, tip: String = "", cancelable: Boolean = true) { +// if (!isFrontRunning) { +// return +// } +// if (show) { +// if (isShowLoading()) { +// mLoadingDialog!!.dismiss() +// } +// var message = tip +// if (TextUtils.isEmpty(tip)) { +// message = getString(R.string.txt_loading) +// } +// mLoadingDialog = +// QMUITipDialog.Builder(mContext).setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) +// .setTipWord(message).create(cancelable).apply { +// setCanceledOnTouchOutside(false) +// show() +// } +// } else { +// mLoadingDialog?.dismiss() +// } + } + + fun showNoCancelableLoading(show: Boolean) { + if (!isFrontRunning) { + return + } + if (show) { + if (isShowLoading()) { + mLoadingDialog!!.dismiss() + } +// var message = tip +// if (TextUtils.isEmpty(tip)) { +// } + val message = getString(R.string.txt_loading) + mLoadingDialog = + QMUITipDialog.Builder(mContext).setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord(message).create(false).apply { + setCanceledOnTouchOutside(false) + show() + } + } else { + mLoadingDialog?.dismiss() + } + } + + /** + * 显示提示toast + */ + fun showToast( + tip: String, isFinish: Boolean = false, gravity: Int = Gravity.BOTTOM +// @QMUITipDialog.Builder.IconType iconType: Int = QMUITipDialog.Builder.ICON_TYPE_NOTHING + ) { + if (gravity == Gravity.BOTTOM) Toaster.setGravity(gravity, 0, 300, 0f, 0f) + if (isFinish) { +// DialogUtils.showTipDialog(mContext, mViewBinding.root, tip, iconType, true) + //activity前台显示运行才弹出 + if (isFrontRunning) { + Toaster.show(tip) + //根据文字长度来决定显示时间长短 + var showTime = if (AppUtils.isChina()) { + tip.length * (1000L / 4) + } else { + tip.length * (1000L / 10) + } + if (showTime < 1000) showTime = 1500 + if (showTime > 8000) showTime = 8000 + mViewBinding.root.postDelayed({ + if (!isFinishing) { + Toaster.cancel() + finish() + } + }, showTime) + } + } else { + Toaster.show(tip) + } + } + + fun showToast( + resIdString: Int, isFinish: Boolean = false, gravity: Int = Gravity.BOTTOM +// @QMUITipDialog.Builder.IconType iconType: Int = QMUITipDialog.Builder.ICON_TYPE_NOTHING + ) { + showToast(getString(resIdString), isFinish, gravity) + } + + fun hideFragment(fragmentTransaction: FragmentTransaction, fragment: Fragment) { + if (fragment.isAdded) { + fragmentTransaction.hide(fragment) + } + } + + override fun finish() { + super.finish() + overridePendingTransition(R.anim.slide_still, R.anim.slide_out_right) + } + + override fun startActivity(intent: Intent) { + super.startActivity(intent) + overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left) + } + + fun startActivityFinish(intent: Intent) { + startActivity(intent) + super.finish() + } + + override fun startActivity(intent: Intent, options: Bundle?) { + super.startActivity(intent, options) + overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left) + } + + /** + * onActivityResult 替换跳转方法 + */ + fun launchActivity(activityResultLauncher: ActivityResultLauncher, intent: Intent) { + activityResultLauncher.launch(intent) + overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left) + } + + /** + * 统一处理网络请求结果 + */ + fun dealRequestResult( + it: Result, + getResultCallback: GetResultCallback, + showLoading: Boolean = false, + isShowNoDataTip: Boolean = true, + isShowRequestErrorTip: Boolean = true, + errorTip: String = getString(R.string.txt_net_error) + ) { + try { + showLoading(showLoading) + //服务器返回错误信息 + val exceptionMessage = it.exceptionOrNull()?.message + //服务器返回错误code + val exceptionCode: String? = it.exceptionOrNull()?.toString() + LogUtil.d("Throwable?", "code=${exceptionCode},${exceptionMessage}") + if (null != exceptionCode) { + //处理注册用户已存在的情况 + if (exceptionCode == ErrorCode.USER_HAS_EXIST.toString()) { + getResultCallback.onRequestError(exceptionCode) + } + //拦截请求异常code + if (netErrorCodeTips(exceptionCode)) { + //已经处理相关错误码就直接返回 + return + } + } + + val data = it.getOrNull() + if (null == data) { + if (isShowRequestErrorTip) showToast(errorTip) + if (mNetworkRequestsFailRetryCount < mNetworkRequestsFailMaxCount) { + mNetworkRequestsFailRetryCount++ + getResultCallback.onRequestError(exceptionCode) + } else { + //请求次数上限 + getResultCallback.onRequestError(mNetworkRequestsFailLimit) + } + } else { + mNetworkRequestsFailRetryCount = 0 + if (isShowNoDataTip && data is MutableList<*> && data.size == 0) { + showToast(R.string.no_data) + } + //不做强制类型转换 + getResultCallback.onResult(0) + } + } catch (e: Exception) { + LogUtil.e("接口出错造成的异常:${e.message}") + } + } + + /** + * 网络请求错误code提示 + */ + private fun netErrorCodeTips(exceptionCode: String): Boolean { + //设备已过期,请续费 + when (exceptionCode) { + ErrorCode.DEVICE_RENEWAL.toString() -> { + showToast(R.string.txt_device_renewal) + return true + } + //TOKEN失效或者过期 + ErrorCode.TOKEN_OVERDUE.toString() -> { + showToast(R.string.txt_token_overdue) + return true + } + //用户名或密码错误 + ErrorCode.USER_PASSWORD_ERROR.toString() -> { + showToast(R.string.txt_user_password_error, gravity = Gravity.CENTER) + return true + } + //验证码错误 + ErrorCode.VERIFY_CODE_ERROR.toString() -> { + showToast(R.string.txt_verify_code_error, gravity = Gravity.CENTER) + return true + } + //验证码已过期 + ErrorCode.VERIFY_CODE_EXPIRE.toString() -> { + showToast(R.string.txt_verify_code_expire, gravity = Gravity.CENTER) + return true + } + //注册失败 + ErrorCode.REGISTER_FAIL.toString() -> { + showToast(R.string.txt_register_fail, gravity = Gravity.CENTER) + return true + } + //用户不存在 + ErrorCode.USER_NO_EXIST.toString() -> { + showToast(R.string.txt_user_no_exist, gravity = Gravity.CENTER) + return true + } + //用户已存在 + ErrorCode.USER_HAS_EXIST.toString() -> { + if (!isInterceptNetErrorCodeTip) { + showToast(R.string.txt_user_has_exist, gravity = Gravity.BOTTOM) + } + return true + } + //邮箱格式不对 + ErrorCode.EMAIL_FORMAT_ERROR.toString() -> { + showToast(R.string.txt_email_format_error, gravity = Gravity.CENTER) + return true + } + //不能分享给自己 + ErrorCode.NO_SHARE_MYSELF.toString() -> { + showToast(R.string.txt_no_share_myself) + return true + } + //当前用户已经绑定该设备 + ErrorCode.DEVICE_HAS_BOUND.toString() -> { + showToast(R.string.txt_device_has_bound) + return true + } + //该设备已被绑定 + ErrorCode.DEVICE_HAS_BIND.toString() -> { + showToast(R.string.txt_device_has_bind) + return true + } + //设备不存在 + ErrorCode.DEVICE_NOT_EXIST.toString() -> { + showToast(R.string.txt_no_dfu) + return true + } + + else -> return false + } + } + + /** + * 监听软键盘隐藏 + */ + open fun listenHideKeyboard() {} + + /******************************点击屏幕页面隐藏软键盘start**************************************/ +// override fun dispatchTouchEvent(ev: MotionEvent): Boolean { +// if (ev.action == MotionEvent.ACTION_DOWN) { +// val v = currentFocus +// v?.let { +// //如果搜索框内没有输入,隐藏键盘时则返回Contacts页面 +// if (isShouldHideKeyboard(v, ev)) { +// if (hideInputMethod(it)) { +// LogUtil.e("dispatchTouchEvent-->listenHideKeyboard,隐藏键盘") +// listenHideKeyboard() +// //这里可以执行隐藏需要执行的任务; +// //隐藏键盘时,其他控件不响应点击事件==》注释则不拦截点击事件 +// //不拦截事件,按钮需要事件 +//// return true +// } +// } +// } +// +// } +// return super.dispatchTouchEvent(ev) +// } + + /** + * 获取InputMethodManager + */ + private fun getInputMethodManager(): InputMethodManager? { + if (null == mInputMethodManager) mInputMethodManager = + getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + return mInputMethodManager + } + + /** + * 隐藏软键盘 + */ + fun hideInputMethod(view: View): Boolean { + getInputMethodManager()?.apply { + return hideSoftInputFromWindow(view.windowToken, 0) + } + return false + } + + /** + * 弹出软键盘 + */ + fun showInputMethod(view: View) { + getInputMethodManager()?.apply { + showSoftInput(view, InputMethodManager.SHOW_IMPLICIT) + } + } + + /** + * 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘,因为当用户点击EditText时则不能隐藏 + */ + private fun isShouldHideKeyboard(v: View?, ev: MotionEvent): Boolean { + if (v != null && (v is EditText)) { + val l = intArrayOf(0, 0) + v.getLocationInWindow(l) + val left = l[0] + val top = l[1] + val bottom = top + v.getHeight() + val right = left + v.getWidth() + return !(ev.x > left && ev.x < right && ev.y > top && ev.y < bottom) + } + // 如果焦点不是EditText则忽略,这个发生在视图刚绘制完,第一个焦点不在EditText上,和用户用轨迹球选择其他的焦点 + return false + } + + /******************************点击屏幕页面隐藏软键盘end**************************************/ + + + /** + * 软键盘显示 + */ + open fun showSoftKeyboardCallback() { + LogUtil.e("监听重写软键盘弹出") + } + + /** + * 软键盘隐藏 + */ + open fun hideSoftKeyboardCallback() { + LogUtil.e("监听重写软键盘隐藏") + } + + /* + * 获取导航栏高度 + */ + private fun getNavigatorBarHeight(): Int { +// val resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android") +// val height = resources.getDimensionPixelSize(resourceId) + return QMUIDisplayHelper.getNavMenuHeight(mContext) + } + + /** + * 监听因输入框弹出,遮挡输入框,界面移动 + * @param translateView 需要移动的view + * @param translateY 移动的坐标点 + */ + private fun onLayoutChange(translateView: View, translateY: Int) { + mTranslateView = translateView + //获取当前屏幕内容的高度 + val screenHeight = window.decorView.height + //获取View可见区域的bottom + val rect = Rect() + window.decorView.getWindowVisibleDisplayFrame(rect) + if (screenHeight - getNavigatorBarHeight() > rect.bottom) { + // 获取按钮的左上角,按钮高度为40dp +// val location = IntArray(2) +// translateView.getLocationOnScreen(location) +// val bottom = location[1] + resources.getDimensionPixelSize(R.dimen.dp_80) +//// 如果按钮被覆盖,移动整个界面向上移动 +// if (bottom > rect.bottom) { +// window.decorView.scrollBy(0, bottom - rect.bottom) +// isKeyboardTranslate = true +// } + if (!isKeyboardTranslate) { + //移动到指定位置点坐标 + translateView.scrollBy(0, translateY) + isKeyboardTranslate = true + } + LogUtil.e("软键盘弹出") + showSoftKeyboardCallback() + } else { + LogUtil.e("隐藏软键盘") + if (isKeyboardTranslate) { + translateView.scrollTo(0, 0) + isKeyboardTranslate = false + } + hideSoftKeyboardCallback() + } + } + /*****************************监听软键盘弹出,输入框上移end************************************/ +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseDialog.kt b/app/src/main/java/com/abbidot/tracker/base/BaseDialog.kt new file mode 100644 index 0000000..0394f07 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseDialog.kt @@ -0,0 +1,96 @@ +package com.abbidot.tracker.base + +import android.content.Context +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnClickListener +import android.view.WindowManager +import androidx.annotation.StyleRes +import androidx.appcompat.app.AppCompatDialog +import androidx.viewbinding.ViewBinding +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.qmuiteam.qmui.alpha.QMUIAlphaButton +import com.qmuiteam.qmui.alpha.QMUIAlphaImageButton + + +/** + *Created by .yzq on 2022/1/11/011.- + * @link + * @dialogHeightDp 默认高度350dp + * @description:BaseDialog + */ +abstract class BaseDialog + ( + val inflater: (inflater: LayoutInflater) -> T, + context: Context, + dialogHeightDp: Int = 0, + @StyleRes animationResId: Int = R.style.QMUI_Animation_Scale, + @StyleRes themeResId: Int = R.style.my_Dialog_shadow_style +) : AppCompatDialog(context, themeResId), OnClickListener { + + protected lateinit var mViewBinding: T + + val mContext = context + + private val mAnimationResId = animationResId + private val mDialogHeightDp = dialogHeightDp + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mViewBinding = inflater(layoutInflater) + setContentView(mViewBinding.root) + //设置Dialog动画 + window!!.setWindowAnimations(mAnimationResId) + //设置弹窗的高度 + val layoutParams: WindowManager.LayoutParams = window!!.attributes + if (mDialogHeightDp == 0) { + //采用自适应高度 + layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT + } else { + layoutParams.height = AppUtils.dpToPx(mDialogHeightDp) + } +// layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT + window!!.attributes = layoutParams + + initView() + } + + abstract fun initView() + + /** + * 设置多个view的监听事件 + */ + fun setOnClickListenerViews(vararg views: View?) { + for (v in views) { + v?.let { + if (v is QMUIAlphaButton) { + //设置按下点击透明效果 + v.setChangeAlphaWhenPress(true) + } else if (v is QMUIAlphaImageButton) { + //设置按下点击透明效果 + v.setChangeAlphaWhenPress(true) + } + v.setOnClickListener(this) + } + } + } + + override fun onClick(v: View) { + // 默认不实现,让子类实现 + } + + interface OnDialogOkListener { + fun onOkClick(dialog: BaseDialog<*>) + } + + interface OnDialogSelectListener { + fun onSelectClick(dialog: BaseDialog<*>, any: Any) + } + + interface OnDialogCancelListener { + fun onCancelClick(dialog: BaseDialog<*>) + } +} + diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseDiffBean.kt b/app/src/main/java/com/abbidot/tracker/base/BaseDiffBean.kt new file mode 100644 index 0000000..ae739eb --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseDiffBean.kt @@ -0,0 +1,14 @@ +package com.abbidot.tracker.base + +/** + *Created by .yzq on 2022/4/20/020. + * @link + * @description: + */ +abstract class BaseDiffBean { + // 判断当前对象和给定对象是否是同一对象 + abstract fun isSameObject(other: Any): Boolean + + // 判断当前对象和给定对象是否拥有相同内容 + abstract fun hasSameContent(other: Any): Boolean +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseDiffCallBack.kt b/app/src/main/java/com/abbidot/tracker/base/BaseDiffCallBack.kt new file mode 100644 index 0000000..c5cfff1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseDiffCallBack.kt @@ -0,0 +1,57 @@ +package com.abbidot.tracker.base + +import androidx.recyclerview.widget.DiffUtil + +/** + *Created by .yzq on 2021/11/12/012. + * @link + * @description: DiffUtil基类封装 + */ +open class BaseDiffCallBack(oldList: MutableList?, newList: MutableList?) : + DiffUtil.Callback() { + + private var mOldList: MutableList? = oldList + private var mNewList: MutableList? = newList + + override fun getOldListSize() = if (mOldList == null) { + 0 + } else mOldList!!.size + + override fun getNewListSize() = if (mNewList == null) { + 0 + } else mNewList!!.size + + /** + * 判断新旧item是否是同一个item,最好以唯一不变的id作比较 + */ + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { +// if (null == mNewList || null == mOldList) return false + val newItem = mNewList!![newItemPosition] + if (newItem is BaseDiffBean) { + return newItem.isSameObject(mOldList!![oldItemPosition]!!) + } + return true + } + + /** + * 是同一个item。然后比较新旧item的内容是否一样 + */ + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val newItem = mNewList!![newItemPosition] + if (newItem is BaseDiffBean) { + return newItem.hasSameContent(mOldList!![oldItemPosition]!!) + } + return false + } + + /** + * 局部刷新比较,只更新某个item + */ + override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? { + val newDeviceBean = mNewList?.get(newItemPosition) + if (!areContentsTheSame(oldItemPosition, newItemPosition)) { + return newDeviceBean + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseFragment.kt b/app/src/main/java/com/abbidot/tracker/base/BaseFragment.kt new file mode 100644 index 0000000..5b0e8b8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseFragment.kt @@ -0,0 +1,167 @@ +package com.abbidot.tracker.base + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.activity.result.ActivityResultLauncher +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.viewbinding.ViewBinding +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.GetResultCallback +import com.qmuiteam.qmui.alpha.QMUIAlphaButton +import com.qmuiteam.qmui.alpha.QMUIAlphaImageButton +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +/** + *Created by .yzq on 2021/11/2/002. + * @link + * @description:Fragment基类 + * 关于@JvmOverloads constructor说明 https://stackoverflow.com/questions/34469487/caused-by-java-lang-nosuchmethodexception-init-class-android-content-contex + */ +abstract class BaseFragment( + val inflater: (inflater: LayoutInflater, container: ViewGroup?, attachToRoot: Boolean) -> T +) : Fragment(), View.OnClickListener { + + protected lateinit var mViewBinding: T + var mActivity: Activity? = null + var mContext: Context? = null + var mLoadingDialog: QMUITipDialog? = null + + /** + * Fragment中嵌套子Fragment时,如果父Fragment被销毁而子Fragment未被销毁,会导致子Fragment的视图不再显示‌ + */ + override fun onCreate(savedInstanceState: Bundle?) { + savedInstanceState?.remove("android:support:fragments") + super.onCreate(savedInstanceState) + } + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View? { + mViewBinding = this.inflater(inflater, container, false) + LogUtil.e("${javaClass.simpleName},onCreateView") + return mViewBinding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mActivity = activity + mContext = context + //统一设置的渐变背景色 + mViewBinding.root.background = + ContextCompat.getDrawable(mContext!!, R.drawable.v2_bg_color_gradient) + LogUtil.e("${javaClass.simpleName},onViewCreated") + initData() + liveDataObserve() + } + + + /** + * 注册监听livedata观察者 + */ + open fun liveDataObserve() {} + + /** + * 初始化 + */ + open fun initData() {} + + fun showLoading(show: Boolean, tip: String = "", cancelable: Boolean = true) { + (activity as BaseActivity<*>).showLoading(show, tip, cancelable) + } + + fun showLoading(show: Boolean) { + (activity as BaseActivity<*>).showLoading(show) + } + + fun showNoCancelableLoading(show: Boolean) { + (activity as BaseActivity<*>).showNoCancelableLoading(show) + } + + /** + * 显示提示toast + */ + fun showToast( + tip: String, isFinish: Boolean = false, gravity: Int = Gravity.BOTTOM + ) { +// DialogUtils.showTipDialog(mContext!!, mViewBinding.root, tip, iconType, isFinish = isFinish) + (activity as BaseActivity<*>).showToast(tip, isFinish, gravity) + } + + fun showToast( + resIdString: Int, isFinish: Boolean = false, gravity: Int = Gravity.BOTTOM + ) { +// showToast(getString(resIdString), isFinish, iconType) + (activity as BaseActivity<*>).showToast(resIdString, isFinish, gravity) + } + + /** + * 设置多个view的监听事件 + */ + fun setOnClickListenerViews(vararg views: View?) { + for (v in views) { + v?.let { + if (v is QMUIAlphaButton) { + //设置按下点击透明效果 + v.setChangeAlphaWhenPress(true) + } else if (v is QMUIAlphaImageButton) { + //设置按下点击透明效果 + v.setChangeAlphaWhenPress(true) + } + v.setOnClickListener(this) + } + } + } + + override fun onClick(v: View?) { + // 默认不实现,让子类实现 + } + + fun finish(activity: Activity) { + (activity as BaseActivity<*>).finish() + } + + fun startActivity(activity: Activity, intent: Intent) { + (activity as BaseActivity<*>).startActivity(intent) + } + + fun startActivityFinish(activity: Activity, intent: Intent) { + (activity as BaseActivity<*>).startActivityFinish(intent) + } + + fun startActivity(activity: Activity, intent: Intent, options: Bundle?) { + (activity as BaseActivity<*>).startActivity(intent, options) + } + + /** + * onActivityResult 替换跳转方法 + */ + fun launchActivity( + activity: Activity, activityResultLauncher: ActivityResultLauncher, intent: Intent + ) { + (activity as BaseActivity<*>).launchActivity(activityResultLauncher, intent) + } + + /** + * 统一处理网络请求结果 + */ + fun dealRequestResult( + it: Result, + getResultCallback: GetResultCallback, + showLoading: Boolean = false, + isShowNoDataTip: Boolean = true, + isRequestErrorTip: Boolean = true, + errorTip: String = getString(R.string.txt_net_error) + ) { + (activity as BaseActivity<*>).dealRequestResult( + it, getResultCallback, showLoading, isShowNoDataTip, isRequestErrorTip, errorTip + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseMapCommon.kt b/app/src/main/java/com/abbidot/tracker/base/BaseMapCommon.kt new file mode 100644 index 0000000..ed6b00f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseMapCommon.kt @@ -0,0 +1,195 @@ +package com.abbidot.tracker.base + +import android.content.Context +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.fragment.map.baidumap.BaseBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.BaseGoogleMapFragment +import com.abbidot.tracker.util.Util +import com.baidu.mapapi.utils.DistanceUtil +import com.google.android.gms.maps.model.LatLng + +/** + *Created by .yzq on 2024/5/10/010. + * @link + * @description:重复调用方法写在这个类中 + */ +open class BaseMapCommon { + + /** + * 地图是否加载好了,准备好了 + */ + fun isMapLoadOk( + baiduMapFragment: BaseBaiduMapFragment?, googleMapFragment: BaseGoogleMapFragment? + ): Boolean { + var loadOK = false + if (null != baiduMapFragment) { + loadOK = baiduMapFragment.isMapLoadOk + } else if (null != googleMapFragment) { + loadOK = googleMapFragment.isMapLoadOk + } + return loadOK + } + + /** + * 开始每隔几秒刷新用户位置 + */ + fun startRefreshUserLocation( + baiduMapFragment: BaseBaiduMapFragment?, googleMapFragment: BaseGoogleMapFragment? + ) { + googleMapFragment?.getLastLocation() + } + + /** + * 切换地图类型 + */ + fun switchSatelliteAndNormalMapType( + baiduMapFragment: BaseBaiduMapFragment?, googleMapFragment: BaseGoogleMapFragment? + ) { + val mapTypeSp = Util.getMapTypeSp() + if (mapTypeSp == ConstantInt.Type0) { + MMKVUtil.putInt(MMKVKey.MapType, ConstantInt.Type1) + } else { + MMKVUtil.putInt(MMKVKey.MapType, ConstantInt.Type0) + } + if (null != baiduMapFragment) { + baiduMapFragment.switchSatelliteAndNormalMapType() + } else googleMapFragment?.switchSatelliteAndNormalMapType() + } + + /** + * 设置宠物当前经纬度和头像 + */ + fun setPetHeadAndCurrentLocation( + petHeadUrl: String, + petType: Int, + lat: Double, + lon: Double, + baiduMapFragment: BaseBaiduMapFragment?, + googleMapFragment: BaseGoogleMapFragment? + ) { + if (null != googleMapFragment) { + googleMapFragment.setPetCurrentLocation(petHeadUrl, lat, lon, petType) + } else baiduMapFragment?.setPetCurrentLocation(petHeadUrl, lat, lon) + } + + /** + * 切换用户和宠物的位置居中显示 + */ + fun switchShowLocationType( + @ConstantInt type: Int, + baiduMapFragment: BaseBaiduMapFragment?, + googleMapFragment: BaseGoogleMapFragment? + ) { + if (null != baiduMapFragment) { + baiduMapFragment.switchShowLocation(type) + } else googleMapFragment?.switchShowLocation(type) + } + + /** + * 切换用户、宠物和第三方的位置(围栏或者wifi zone)居中显示 + */ + fun switchShowLocationType( + @ConstantInt type: Int, + lat: Double, + lon: Double, + baiduMapFragment: BaseBaiduMapFragment?, + googleMapFragment: BaseGoogleMapFragment? + ) { + if (null != baiduMapFragment) { + if (type == ConstantInt.OtherLocationType) { + baiduMapFragment.moveCameraLocation(lat, lon) + } else { + baiduMapFragment.switchShowLocation(type) + } + } else if (null != googleMapFragment) { + if (type == ConstantInt.OtherLocationType) { + googleMapFragment.moveCameraUpdate(lat, lon) + } else { + googleMapFragment.switchShowLocation(type) + } + } + } + + + /** + * 根据预览围栏类型设置地图等级 + * @param padding 距离屏幕边缘的距离,px单位 + */ + fun restoreFencesSetMapZoom( + context: Context, + fencesBean: FencesBean, + baiduMapFragment: BaseBaiduMapFragment?, + googleMapFragment: BaseGoogleMapFragment?, + padding: Int + ) { + fencesBean.apply { + when (fenceShapeType) { + ConstantInt.CircleShapeType -> { + googleMapFragment?.apply { + setLatLngZoom( + context, + padding, + LatLng(ancillaryLatitude, ancillaryLongitude), + LatLng(ancillaryOtherLatitude, ancillaryOtherLongitude) + ) + } + baiduMapFragment?.apply { + setBaiduMapZoom(getDistanceZoom(radius.toDouble()) + 1) + } + } + + ConstantInt.RectangleShapeType -> { + googleMapFragment?.setLatLngZoom( + context, + padding, + LatLng(latitudeA, longitudeA), + LatLng(latitudeB, longitudeB), + LatLng(latitudeC, longitudeC), + LatLng(latitudeD, longitudeD) + ) + baiduMapFragment?.apply { + setBaiduMapZoom( + getDistanceZoom( + maxOf( + shortDistance, longDistance + ).toDouble() + ) + 1 + ) + } + } + + ConstantInt.PolygonShapeType -> { + googleMapFragment?.setLatLngZoom( + context, + padding, + LatLng(latitudeA, longitudeA), + LatLng(latitudeB, longitudeB), + LatLng(latitudeC, longitudeC), + LatLng(latitudeD, longitudeD), + LatLng(latitudeE, longitudeE), + LatLng(latitudeF, longitudeF), + ) + baiduMapFragment?.apply { + val minLat = + minOf(latitudeA, latitudeB, latitudeC, latitudeD, latitudeE, latitudeF) + val maxLat = + minOf(latitudeA, latitudeB, latitudeC, latitudeD, latitudeE, latitudeF) + val minLon = minOf( + longitudeA, longitudeB, longitudeC, longitudeD, longitudeE, longitudeF + ) + val maxLon = minOf( + longitudeA, longitudeB, longitudeC, longitudeD, longitudeE, longitudeF + ) + val distance = DistanceUtil.getDistance( + getBaiduMapLatLng(minLat, minLon), getBaiduMapLatLng(maxLat, maxLon) + ) + setBaiduMapZoom(getDistanceZoom(distance)) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseQMUIRVItemSwipeAction.kt b/app/src/main/java/com/abbidot/tracker/base/BaseQMUIRVItemSwipeAction.kt new file mode 100644 index 0000000..694e39f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseQMUIRVItemSwipeAction.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.base + +import androidx.recyclerview.widget.RecyclerView +import com.qmuiteam.qmui.recyclerView.QMUIRVItemSwipeAction + +/** + *Created by .yzq on 2021/11/22/022. + * @link + * @description: QMUIRVItemSwipeAction.Callback + */ +abstract class BaseQMUIRVItemSwipeAction(swipeDirection: Int) : QMUIRVItemSwipeAction.Callback() { + + private val mSwipeDirection = swipeDirection + + abstract fun removeItem(position: Int) + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + removeItem(viewHolder.adapterPosition) + } + + override fun getSwipeDirection(recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder): Int { + return mSwipeDirection + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseSwipeMenuAdapter.kt b/app/src/main/java/com/abbidot/tracker/base/BaseSwipeMenuAdapter.kt new file mode 100644 index 0000000..ef6955f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseSwipeMenuAdapter.kt @@ -0,0 +1,152 @@ +package com.abbidot.tracker.base + +import android.content.Context +import android.graphics.Color +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.qmuiteam.qmui.recyclerView.QMUISwipeAction +import com.qmuiteam.qmui.recyclerView.QMUISwipeAction.ActionBuilder +import com.qmuiteam.qmui.recyclerView.QMUISwipeViewHolder +import com.qmuiteam.qmui.util.QMUIDisplayHelper + + +/** + *Created by .yzq on 2021/11/20/020. + * @link + * @description:带有滑动删除菜单列表适配器,配合DiffUtil使用取消notifyDataSetChanged()方法 + */ +abstract class BaseSwipeMenuAdapter(context: Context, list: MutableList) : + RecyclerView.Adapter() { + + private var mData: MutableList = list + private val mContext: Context = context + private val mInflater: LayoutInflater = LayoutInflater.from(context) + + private var mClickListener: BaseRecyclerAdapter.OnItemClickListener? = null + private var mLongClickListener: BaseRecyclerAdapter.OnItemLongClickListener? = null + + // 是否显示空布局,默认显示 + private var showEmptyView = true + + //重试事件 + private var mAgainClickListener: BaseRecyclerAdapter.OnAgainClickListener? = null + + /** + * viewType--分别为item以及空view + */ + private val mViewTypeEmpty = 0 + private val mViewTypeItem = 1 + + //菜单样式Builder + var mActionBuilder: ActionBuilder = + ActionBuilder().textSize(QMUIDisplayHelper.sp2px(context, 14)).textColor(Color.WHITE) + .paddingStartEnd(QMUIDisplayHelper.dp2px(context, 14)) + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): QMUISwipeViewHolder { + //空布局 + if (getEmptyLayoutId(viewType) != 0 && viewType == mViewTypeEmpty) { + val itemView: View = mInflater.inflate(getEmptyLayoutId(viewType), parent, false) + val swipeViewHolder = QMUISwipeViewHolder(itemView) + if (null != mAgainClickListener) { + itemView.setOnClickListener { + showEmptyView(false) + mAgainClickListener!!.onAgainListener() + } + } + return swipeViewHolder + } else { + val itemView: View = mInflater.inflate(getItemLayoutId(viewType), parent, false) + val swipeViewHolder = QMUISwipeViewHolder(itemView) + //添加滑动菜单 + for (swipeAction in getSwipeActions()) { + swipeViewHolder.addSwipeAction(swipeAction) + } + if (null != mClickListener) { + itemView.setOnClickListener { + mClickListener!!.onItemClick(itemView, swipeViewHolder.adapterPosition) + } + } + if (null != mLongClickListener) { + itemView.setOnLongClickListener { + mLongClickListener!!.onItemLongClick(itemView, swipeViewHolder.adapterPosition) + true + } + } + return swipeViewHolder + } + } + + override fun onBindViewHolder(holder: QMUISwipeViewHolder, position: Int) { + if (mData.size > 0) bindData(holder, position, mData[position]) + } + + /** + * 判断是否是空布局 + */ + private fun isEmptyPosition(position: Int): Boolean { + val count = mData.size + return position == 0 && showEmptyView && count == 0 + } + + /** + * 设置空布局显示。默认显示 + */ + private fun showEmptyView(isShow: Boolean) { + if (isShow != showEmptyView) { + showEmptyView = isShow + notifyDataSetChanged() + } + } + + override fun getItemCount() = if (mData.size == 0 && showEmptyView) 1 else mData.size + + override fun getItemViewType(position: Int) = + if (isEmptyPosition(position)) mViewTypeEmpty else mViewTypeItem + + fun setData(list: MutableList?) { + if (list != null) { + mData = list + //出现没有数据就显示空布局 + if (mData.size == 0) { + showEmptyView(true) + } + } + } + + fun getData() = mData + + fun remove(oldList: MutableList, pos: Int): MutableList { + val newList: MutableList = oldList.toMutableList() + newList.removeAt(pos) + return newList + } + + fun getItem(pos: Int) = mData[pos] + + /** + * 设置显示空布局 等于0不显示空布局 + */ + abstract fun getEmptyLayoutId(viewType: Int): Int + + //菜单集合 + abstract fun getSwipeActions(): MutableList + abstract fun getItemLayoutId(viewType: Int): Int + abstract fun bindData(holder: QMUISwipeViewHolder?, position: Int, item: T) + + fun setOnItemClickListener(listener: BaseRecyclerAdapter.OnItemClickListener?) { + mClickListener = listener + } + + fun setOnItemLongClickListener(listener: BaseRecyclerAdapter.OnItemLongClickListener?) { + mLongClickListener = listener + } + + fun setOnAgainClickListener(listener: BaseRecyclerAdapter.OnAgainClickListener?) { + mAgainClickListener = listener + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/base/BaseView.kt b/app/src/main/java/com/abbidot/tracker/base/BaseView.kt new file mode 100644 index 0000000..ef561ee --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/base/BaseView.kt @@ -0,0 +1,51 @@ +package com.abbidot.tracker.base + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.abbidot.baselibrary.util.AppUtils + +/** + *Created by .yzq on 2024/10/11/011. + * @link + * @description: + */ +open class BaseView : View { + + lateinit var mContext: Context + var mWidth = 0 + var mHeight = 0 + var mValue = 0 + var mStrokeWidth = 0f + + lateinit var mPaint: Paint + + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) + + open fun init(context: Context, attrs: AttributeSet?) { + mContext = context + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mStrokeWidth = AppUtils.dpToPx(1f) + mPaint = Paint() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + open fun startDraw(canvas: Canvas) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/ActiveTimeBean.kt b/app/src/main/java/com/abbidot/tracker/bean/ActiveTimeBean.kt new file mode 100644 index 0000000..68f5651 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/ActiveTimeBean.kt @@ -0,0 +1,40 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/3/15/015. + * @link + * @description: + */ +@Parcelize +data class ActiveTimeBean( + var activeId: String, + var activeTime: Int, + var goalTime: Int, + var goal: Int, + var calories: Double, + var dayTime: String, + var deviceId: String, + var distance: Double, + var lickCounts: Int, + var persisted: Int, + var scratchCounts: Int, + var steps: Long, + var timeZone: Int, + var updateDate: String, + var vomitCount: Int, + var avgActiveTime: Int, + var avgSleepTime: Int, + var totalTimes: Int, + var totalDistance: Double, + var activeValue: String, + var avgCalories: Int, + var sevenDatas: MutableList?, + var thirtyDatas: MutableList?, +) : Parcelable { + constructor() : this( + "", 0, 0, 0, 0.0, "", "", 0.0, 0, 0, 0, 0, 0, "", 0, 0, 0, 0, 0.0, "", 0, null, null + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/AlipayOrderBean.kt b/app/src/main/java/com/abbidot/tracker/bean/AlipayOrderBean.kt new file mode 100644 index 0000000..d750436 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/AlipayOrderBean.kt @@ -0,0 +1,10 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/7/29/029. + * @link + * @description:微信支付预支付订单 + */ +data class AlipayOrderBean(val orderString: String) { + constructor() : this("") +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/BleReportDataBean.kt b/app/src/main/java/com/abbidot/tracker/bean/BleReportDataBean.kt new file mode 100644 index 0000000..c972358 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/BleReportDataBean.kt @@ -0,0 +1,78 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2025/3/19/019. + * @link + * @description: + */ +data class BleReportDataBean( + var mac: String, + var haveDeviceStateData: Boolean, + var updateTime: Long, + var latLonUpdateTime: Long, + var powerSwitch: Int, + var lteSignal: Int, + var gpsSignal: Int, + var batteryLevel: Int, + var gnssInterval: Int, + var ledSwitch: Int, + var ledMode: Int, + var ledRedValue: Int, + var ledGreenValue: Int, + var ledBlueValue: Int, + var liveFlag: Int, + var inSafeZone: Int, + var inNoGoZone: Int, + var cFencesId: String, + var inWifiZone: Int, + var wiFiZoneMac: String, + var haveLocationData: Boolean, + var locationMode: Int, + var locationList: MutableList?, + var haveActiveSleepData: Boolean, + var year: Int, + var month: Int, + var day: Int, + var activeTime: Long, + var steps: Long, + var timeZone: Int, + var cTime: Int, + var cActiveTime: Int, + var cSleepTime: Int +) { + constructor() : this( + "", + false, + 0L, + 0L, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + "", + 0, + "", + false, + 0, + null, + false, + 0, + 0, + 0, + 0L, + 0, + 0, + 0, + 0, + 0 + ) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/BleTrackDeviceBean.kt b/app/src/main/java/com/abbidot/tracker/bean/BleTrackDeviceBean.kt new file mode 100644 index 0000000..d9e1bd2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/BleTrackDeviceBean.kt @@ -0,0 +1,15 @@ +package com.abbidot.tracker.bean + +import com.abbidot.baselibrary.constant.ConState +import com.clj.fastble.data.BleDevice + +/** + *Created by .yzq on 2022/5/27/027. + * @link + * @description:已绑定添加的设备实体类 + */ +data class BleTrackDeviceBean(var mac: String, + var bleDevice: BleDevice?, + @ConState var conState: Int) { + constructor() : this("", null, ConState.DISCONNECTED) +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/CountryCodeBean.kt b/app/src/main/java/com/abbidot/tracker/bean/CountryCodeBean.kt new file mode 100644 index 0000000..aae044f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/CountryCodeBean.kt @@ -0,0 +1,8 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/5/23/023. + * @link + * @description: + */ +data class CountryCodeBean(var countryCode: Int) diff --git a/app/src/main/java/com/abbidot/tracker/bean/CreditCardBean.kt b/app/src/main/java/com/abbidot/tracker/bean/CreditCardBean.kt new file mode 100644 index 0000000..08a4433 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/CreditCardBean.kt @@ -0,0 +1,30 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import com.abbidot.tracker.database.MyDatabase +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/4/1/001. + * @link + * @description:保存用户输入信用卡 + */ +@Parcelize +@Entity(tableName = MyDatabase.CreditCardTableName) +data class CreditCardBean( + @PrimaryKey @ColumnInfo var cardNumber: String, + @ColumnInfo var firstName: String, + @ColumnInfo var lastName: String, + @ColumnInfo var cardName: String, + @ColumnInfo var expirationDate: String, + @ColumnInfo var cvv: String,//信用卡验证值(Card Verification Value) + @ColumnInfo var countryCode: String, + @ColumnInfo var isPrimary: Boolean, + @Ignore var checked: Boolean = false +) : Parcelable { + constructor() : this("", "", "", "", "", "", "USA", false, false) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/DFUStateBean.kt b/app/src/main/java/com/abbidot/tracker/bean/DFUStateBean.kt new file mode 100644 index 0000000..475cf6c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/DFUStateBean.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2023/3/23/023. + * @link + * @description: + */ +data class DFUStateBean(var stateName: String, var progress: Int, var filePath: String) { + constructor() : this("", 0, "") + constructor(stateName: String, progress: Int) : this(stateName, progress, "") + constructor(stateName: String) : this(stateName, 0, "") +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/DataBean.kt b/app/src/main/java/com/abbidot/tracker/bean/DataBean.kt new file mode 100644 index 0000000..e075f95 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/DataBean.kt @@ -0,0 +1,43 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.constant.MultipleEntity +import com.chad.library.adapter.base.entity.MultiItemEntity +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.IgnoredOnParcel + +/** + *Created by .yzq on 2021/12/20/020. + * @link + * @description: + */ +@Parcelize +data class DataBean( + var id: String, + var name: String, + var value: String, + var imageId: Int, + var state: Int, + var selectImageId: Int, + var unit: String, + var selected: Boolean = false, + var checked: Boolean = false, + var menuType: Int +) : MultiItemEntity, Parcelable { + + //设置空的构造方法 + constructor() : this("", "", "", 0, 0, 0, "", false, false, MultipleEntity.TEXT) + + constructor(name: String, value: String) : this( + "", name, value, 0, 0,0, "", false, false, MultipleEntity.TEXT + ) + + //设置多个构造方法 + constructor(name: String, value: String, imageId: Int, unit: String) : this( + "", name, value, imageId, 0, 0, unit, false, false, MultipleEntity.TEXT + ) + + @IgnoredOnParcel + @MultipleEntity + override val itemType = menuType +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/DeviceBean.kt b/app/src/main/java/com/abbidot/tracker/bean/DeviceBean.kt new file mode 100644 index 0000000..07bdbf4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/DeviceBean.kt @@ -0,0 +1,53 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.DeviceType +import com.abbidot.tracker.base.BaseDiffBean +import com.abbidot.tracker.database.MyDatabase +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2021/11/8/008. + * @link + * @description:连接保存的设备表 + */ +// 定义表名称,SQLite 中的表名称不区分大小写,2个主键 +@Entity(tableName = MyDatabase.DeviceTableName, primaryKeys = ["deviceOutId", "deviceId"]) +@Parcelize +data class DeviceBean(//@PrimaryKey(autoGenerate = true) var id: Long,//autoGenerate = true 自动增长分配ID + @ColumnInfo @DeviceType var type: Int,//设备类型 + @ColumnInfo var deviceName: String, + @ColumnInfo var deviceOutId: String, + @ColumnInfo var deviceId: String, + @ColumnInfo var shared: Int, //0=没有分享,1=是授权分享的 + @ColumnInfo var batteryCapacity: Int, + @ColumnInfo var ledSwitch: Int, + @ColumnInfo @ConState var conState: Int,//连接状态 + @ColumnInfo var macId: String, + @ColumnInfo var deviceInfoId: String, + @ColumnInfo var surplusDays: Int, + @ColumnInfo var gnssSignal: Int, + @ColumnInfo var lteSignal: Int, + @ColumnInfo var turnOff: Int, + @ColumnInfo var lightMode: Int, + @Ignore var pet: PetBean?) : BaseDiffBean(), Parcelable { + // 如果某个实体中有您不想保留的字段,则可以使用 @Ignore 为这些字段添加注释 + + //设置空的构造方法 + constructor() : this(DeviceType.TYPE_ONE, "", "", "", 0, 0, 0, ConState.DISCONNECTED, "", "", 0, + 0, 0, 0, 0, null) + + override fun isSameObject(other: Any): Boolean { + val oldItem = other as DeviceBean + return deviceId == oldItem.deviceId + } + + override fun hasSameContent(other: Any): Boolean { + val oldItem = other as DeviceBean + return oldItem.deviceOutId == deviceOutId && oldItem.batteryCapacity == batteryCapacity && oldItem.ledSwitch == ledSwitch && oldItem.pet == pet && oldItem.gnssSignal == gnssSignal && oldItem.lteSignal == lteSignal + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/DeviceDetailBean.kt b/app/src/main/java/com/abbidot/tracker/bean/DeviceDetailBean.kt new file mode 100644 index 0000000..349ecbd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/DeviceDetailBean.kt @@ -0,0 +1,20 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/2/28/028. + * @link + * @description:设备详情 + */ +@Parcelize +data class DeviceDetailBean(var deviceOutId: String, + var deviceId: String, + var macId: String, + var pet: PetBean?, + var fenceCount: Int, + var surplusDays: Int, + var familieCount: Int,var deviceInfo:DeviceInfoBean?) : Parcelable { + constructor() : this("", "", "", null, 0, 0,0,null) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/DeviceInfoBean.kt b/app/src/main/java/com/abbidot/tracker/bean/DeviceInfoBean.kt new file mode 100644 index 0000000..b574afc --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/DeviceInfoBean.kt @@ -0,0 +1,22 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class DeviceInfoBean(var deviceInfoId: String, + var red: Int, + var green: Int, + var blue: Int, + //第一个 是:几分钟设备上报。2信号强度,3电量 4设备关机还是开机 5 light登打开还是关闭。5 wifi强度 + var gnssInterval: Int, + var gnssSignal: Int, + var batteryLevel: Int, + var turnOff: Int, + var ledSwitch: Int, + var wifiSignal: Int, + //当前LTE网络信号 + var lteSignal: Int, + var lightMode: Int) : Parcelable { + constructor() : this("", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/FamilyBean.kt b/app/src/main/java/com/abbidot/tracker/bean/FamilyBean.kt new file mode 100644 index 0000000..5b99359 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/FamilyBean.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.base.BaseDiffBean +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/2/28/028. + * @link + * @description: + */ +@Parcelize +data class FamilyBean( + var acceptEmail: String, + var deviceId: String, + var familyId: String, + var imgurl: String, + var inviteDate: String, + var sendEmail: String, + var status: Int,//设备的分享状态 1 是分享成功 0 分享中 2 已拒绝 + var userId: String, + var relation: Int,//关系 1 Family 2 friend 3 love 4 colleague + var avatar: Int,//头像 1 男生 2 女生 3 未知 + var userName: String, + var petId: String, + var petName: String +) : Parcelable, BaseDiffBean() { + constructor() : this("", "", "", "", "", "", 0, "", 1, 1, "", "", "") + + override fun isSameObject(other: Any): Boolean { + val oldItem = other as FamilyBean + return familyId == oldItem.familyId + } + + override fun hasSameContent(other: Any): Boolean { + val oldItem = other as FamilyBean + return oldItem.imgurl == imgurl && oldItem.acceptEmail == acceptEmail + } +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/FamilyPetBean.kt b/app/src/main/java/com/abbidot/tracker/bean/FamilyPetBean.kt new file mode 100644 index 0000000..84bca9d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/FamilyPetBean.kt @@ -0,0 +1,13 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/3/10/010. + * @link + * @description: + */ +data class FamilyPetBean(var petName: String, + var petImgurl: String, + var deviceOutId: String, + var families: MutableList?) { + constructor() : this("", "", "", null) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/FencesBean.kt b/app/src/main/java/com/abbidot/tracker/bean/FencesBean.kt new file mode 100644 index 0000000..da62399 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/FencesBean.kt @@ -0,0 +1,96 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.base.BaseDiffBean +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/2/28/028. + * @link + * @description:围栏 + */ +@Parcelize +data class FencesBean( + var deviceId: String, + var deviceServiceId: String, + var fenceId: String, + var name: String, + var address: String, + var imgurl: String, + var isOn: Int, + var radius: Int, + var longitude: Double, + var latitude: Double, + var fenceShapeType: Int,// fenceShapeType 围栏类型,1是圆形,2是矩形 3是六边形 + var fenceType: Int,//围栏类型,1是Safe Zone,2是No Go Zone + var fenceNameType: Int,//名称类型,1是home,2是Office,3是Garden,4是Park,5是Play,6是Garage,7是vegetable garden,8是River + var longitudeA: Double, + var latitudeA: Double, + var longitudeB: Double, + var latitudeB: Double, + var longitudeC: Double, + var latitudeC: Double, + var longitudeD: Double, + var latitudeD: Double, + var longitudeE: Double, + var latitudeE: Double, + var longitudeF: Double, + var latitudeF: Double, + var longitudeCenter: Double, + var latitudeCenter: Double, + var ancillaryLatitude: Double, + var ancillaryLongitude: Double, + var ancillaryOtherLatitude: Double, + var ancillaryOtherLongitude: Double, + var longDistance: Int, + var shortDistance: Int, + var startDrag: Boolean//围栏形状是否开始拖动 +) : Parcelable, BaseDiffBean() { + constructor() : this( + "", + "", + "", + "", + "", + "", + 0, + 0, + 0.0, + 0.0, + 1, + 1, + 1, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0, + 0, + false + ) + + override fun isSameObject(other: Any): Boolean { + val oldItem = other as FencesBean + return oldItem.fenceId == fenceId + } + + override fun hasSameContent(other: Any): Boolean { + val oldItem = other as FencesBean + return oldItem.isOn == isOn && oldItem.name == name && oldItem.imgurl == imgurl && oldItem.address == address && oldItem.fenceType == fenceType + } + +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/FirmwareBean.kt b/app/src/main/java/com/abbidot/tracker/bean/FirmwareBean.kt new file mode 100644 index 0000000..6821e4e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/FirmwareBean.kt @@ -0,0 +1,16 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2023/3/22/022. + * @link + * @description:固件信息 + */ +data class FirmwareBean( + var deviceName: String, + var version: String, + var remark: String, + var fileName: String, + var firmwareUrl: String, + var mac: String, + var firmwareSize: String +) \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/GetFencesBean.kt b/app/src/main/java/com/abbidot/tracker/bean/GetFencesBean.kt new file mode 100644 index 0000000..99f061b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/GetFencesBean.kt @@ -0,0 +1,19 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2024/4/25/025. + * @link + * @description:获取围栏数据返回 + */ +@Parcelize +data class GetFencesBean( + var latitude: Double, + var longitude: Double, + var normalFenceList: MutableList?, + var nogoFenceList: MutableList? +) : Parcelable { + constructor() : this(0.0, 0.0, null, null) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/HistoryDataBean.kt b/app/src/main/java/com/abbidot/tracker/bean/HistoryDataBean.kt new file mode 100644 index 0000000..3e56793 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/HistoryDataBean.kt @@ -0,0 +1,31 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.constant.ConstantInt +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/3/16/016. + * @link + * @description: + */ +@Parcelize +data class HistoryDataBean(var dayTime: String, + var deviceId: String, + var deviceInfoId: String, + var historyPointId: String, + var latitude: Double, + var longitude: Double, + var address: String, + var positionMode: Int, + var timeStamp: Long, + var ledSwitch: Int, + var gnssSignal: Int, + var batteryLevel: Int, + var lightMode: Int, + @ConstantInt var inFance: Int, + var petName: String, + var petImgurl: String) : Parcelable { + constructor() : this("", "", "", "", 0.0, 0.0, "", 0, 0L, 0, 0, 0, 0, ConstantInt.GoodZone, "", + "") +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/HomeDataBean.kt b/app/src/main/java/com/abbidot/tracker/bean/HomeDataBean.kt new file mode 100644 index 0000000..47fa20e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/HomeDataBean.kt @@ -0,0 +1,14 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/3/15/015. + * @link + * @description: + */ +data class HomeDataBean( + var activeTime: ActiveTimeBean?, + var sleepTime: SleepTimeBean?, + var updateTime: Long +) { + constructor() : this(null, null, 0) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/InviteInfoBean.kt b/app/src/main/java/com/abbidot/tracker/bean/InviteInfoBean.kt new file mode 100644 index 0000000..f2ce9a3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/InviteInfoBean.kt @@ -0,0 +1,10 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/12/4/004. + * @link + * @description:设备邀请信息 + */ +data class InviteInfoBean( + var deleteArray: MutableList, var inviteArray: MutableList +) diff --git a/app/src/main/java/com/abbidot/tracker/bean/LKSimListBean.kt b/app/src/main/java/com/abbidot/tracker/bean/LKSimListBean.kt new file mode 100644 index 0000000..ae32fac --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/LKSimListBean.kt @@ -0,0 +1,16 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2023/7/10/010. + * @link + * @description: + */ +@Parcelize +data class LKSimListBean( + var partnerCode: String, var planList: MutableList? +) : Parcelable { + constructor() : this("", null) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/LKSimPriceBean.kt b/app/src/main/java/com/abbidot/tracker/bean/LKSimPriceBean.kt new file mode 100644 index 0000000..d1fb76c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/LKSimPriceBean.kt @@ -0,0 +1,24 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2023/7/7/007. + * @link + * @description:领科sim卡套餐价格 + */ +@Parcelize +data class LKSimPriceBean( + var autoRenewal: String, + var currency: String, + var currencySymbol: String, + var discountPriceCNY: String, + var discountPriceUSD: Double, + var orderPeriod: String, + var packageType: String, + var specifications: String, + var specifiedNameLabel: String +) : Parcelable { + constructor() : this("", "", "", "", 0.0, "", "", "", "") +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/LKSimSetMealBean.kt b/app/src/main/java/com/abbidot/tracker/bean/LKSimSetMealBean.kt new file mode 100644 index 0000000..6fd90ad --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/LKSimSetMealBean.kt @@ -0,0 +1,27 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2023/7/7/007. + * @link + * @description:领科sim卡套餐 + */ +@Parcelize +data class LKSimSetMealBean( + var currency: String, + var currencyId: String, + var currencySymbol: String, + var languageCode: String, + var minOrderPeriod: String, + var packageCode: String, + var servicePlanCode: String, + var servicePlanDetailsDesc: String, + var servicePlanShortDesc: String, + var servicePlanShowName: String, + var specAndPriceList: MutableList?, + var stripeCoupon: String +) : Parcelable { + constructor() : this("", "", "", "", "", "", "", "", "", "", null, "") +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/LabelBean.kt b/app/src/main/java/com/abbidot/tracker/bean/LabelBean.kt new file mode 100644 index 0000000..3f6cb8d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/LabelBean.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.bean + +import com.abbidot.tracker.base.BaseDiffBean + +/** + *Created by .yzq on 2021/12/21/021. + * @link + * @description:宠物标签 + */ +data class LabelBean(var name: String, var check: Boolean) : BaseDiffBean() { + + //设置空的构造方法 + constructor() : this("", false) + constructor(name: String) : this(name, false) + + override fun isSameObject(other: Any): Boolean { + val oldItem = other as LabelBean + return oldItem.name == name + } + + override fun hasSameContent(other: Any): Boolean { + val oldItem = other as LabelBean + return oldItem.name == name && oldItem.check == check + } +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/LiveDataBean.kt b/app/src/main/java/com/abbidot/tracker/bean/LiveDataBean.kt new file mode 100644 index 0000000..a84cd5c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/LiveDataBean.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/4/28/028. + * @link + * @description: + */ +data class LiveDataBean( + var livePoints: MutableList?, var deviceInfo: MapDeviceBean? +) { + constructor() : this(null, null) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/MapDeviceBean.kt b/app/src/main/java/com/abbidot/tracker/bean/MapDeviceBean.kt new file mode 100644 index 0000000..96f1dab --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/MapDeviceBean.kt @@ -0,0 +1,70 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.constant.ConstantInt +import kotlinx.parcelize.Parcelize + +@Parcelize +data class MapDeviceBean( + var deviceId: String, + var deviceOutId: String, + var baseId: String, + var inSafeZone: Int, + var gpsSignal: Int, + var batteryLevel: Int, + var longitude: Double, + var latitude: Double, + var ledSwitch: Int, + var gnssInterval: Int, + var liveTime: Int, + var liveFlag: Int, + var updateTime: Long, + var latLonUpdateTime: Long, + var lteSignal: Int, + var deviceMacId: String, + var deviceServerId: String, + var powerSwitch: Int, + var inNoGoZone: Int, + var speed: Float, + var inWifiZone: Int, + var ledMode: Int, + var ledRedValue: Int, + var ledGreenValue: Int, + var ledBlueValue: Int, + var fences: MutableList?, + var wifiZones: MutableList?, + var startTime: String, + var endTime: String +) : Parcelable { + constructor() : this( + "", + "", + "", + ConstantInt.GoodZone, + 0, + 0, + 0.0, + 0.0, + 0, + 0, + 0, + 0, + System.currentTimeMillis(), + System.currentTimeMillis(), + 0, + "", + "", + 0, + 0, + 0f, + 0, + 0, + 0, + 0, + 0, + null, + null, + "", + "" + ) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/MenuTxtBean.kt b/app/src/main/java/com/abbidot/tracker/bean/MenuTxtBean.kt new file mode 100644 index 0000000..63580d4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/MenuTxtBean.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.bean + +import com.abbidot.tracker.constant.MultipleEntity +import com.chad.library.adapter.base.entity.MultiItemEntity + +/** + *Created by .yzq on 2021/12/20/020. + * @link + * @description: 菜单显示 + */ +data class MenuTxtBean( + var menuName: String, + var menuValue: String, + var imageId: Int, + var colorRedId: Int, + var imageUrl: String, + var isSwitch: Boolean, + @MultipleEntity val menuType: Int +) : MultiItemEntity { + + //设置空的构造方法 + constructor(@MultipleEntity menuType: Int) : this("", "", 0, 0, "", false, menuType) + + //设置多个构造方法 + constructor(menuName: String, menuValue: String, @MultipleEntity menuType: Int) : this( + menuName, menuValue, 0, 0, "", false, menuType + ) + + constructor(menuName: String, menuValue: String) : this( + menuName, menuValue, 0, 0, "", false, MultipleEntity.TEXT + ) + + constructor(menuName: String, imageId: Int, menuValue: String) : this( + menuName, menuValue, imageId, 0, "", false, MultipleEntity.TEXT + ) + + @MultipleEntity + override val itemType = menuType +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/MessageBean.kt b/app/src/main/java/com/abbidot/tracker/bean/MessageBean.kt new file mode 100644 index 0000000..4b8ce1f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/MessageBean.kt @@ -0,0 +1,28 @@ +package com.abbidot.tracker.bean + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Ignore +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.database.MyDatabase + +/** + *Created by .yzq on 2022/2/24/024. + * @link + * @description:消息列表 + */ +@Entity(tableName = MyDatabase.MessageTableName, primaryKeys = ["messageId"]) +data class MessageBean( + @ColumnInfo var messageId: String, + @ColumnInfo var title: String, + @ColumnInfo var message: String, + @ColumnInfo var sendTime: String, + @ColumnInfo var messageType: Int, + @ColumnInfo var imgUrl: String, + @ColumnInfo var isRead: Boolean, + @Ignore @MultipleEntity var isContainImg: Int, + @Ignore var isSelect: Boolean +) { + constructor() : this("", "", "", "", 1, "", false, MultipleEntity.TEXT, false) +} + diff --git a/app/src/main/java/com/abbidot/tracker/bean/NotificationBean.kt b/app/src/main/java/com/abbidot/tracker/bean/NotificationBean.kt new file mode 100644 index 0000000..0e81b68 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/NotificationBean.kt @@ -0,0 +1,11 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/9/26/026. + * @link + * @description: + */ + +data class NotificationBean( + var enabled: Int, var notificationType: Int, var status: Int, var userId: String +) \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/OrderBean.kt b/app/src/main/java/com/abbidot/tracker/bean/OrderBean.kt new file mode 100644 index 0000000..71d1718 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/OrderBean.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/3/31/031. + * @link + * @description: + * @param isCanRefund 是否可以退款 1 是可以 0 是不可以 + */ +data class OrderBean( + var orderId: String, var orderNo: String, var availableOrder: Int,//判断套餐是否可用或过期,1是可用 0不可用 + var isCanRefund: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/PackageBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PackageBean.kt new file mode 100644 index 0000000..0856c69 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PackageBean.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2024/9/5/005. + * @link + * @description: + */ +@Parcelize +data class PackageBean( + var mealId: String, + var planName: String, + var mealUnit: String, + var planCategory: String, + var period: Int, + var popularType: Int, + var discountedPrice: Double, + var autoRenewPrice: Double, + var planPrice: Double, + var discount: Int, + var active: Int, + var selected: Boolean +) : Parcelable diff --git a/app/src/main/java/com/abbidot/tracker/bean/PackageBenefitsBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PackageBenefitsBean.kt new file mode 100644 index 0000000..4484813 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PackageBenefitsBean.kt @@ -0,0 +1,8 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/9/5/005. + * @link + * @description: + */ +data class PackageBenefitsBean(var title: String, var status: Int) diff --git a/app/src/main/java/com/abbidot/tracker/bean/PackageTypeBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PackageTypeBean.kt new file mode 100644 index 0000000..4ee7f82 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PackageTypeBean.kt @@ -0,0 +1,14 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/9/5/005. + * @link + * @description: + */ +data class PackageTypeBean( + var price: Double, + var name: String, + var packageId: String, + var list: MutableList?, + var selected: Boolean +) diff --git a/app/src/main/java/com/abbidot/tracker/bean/ParentBadgeBean.kt b/app/src/main/java/com/abbidot/tracker/bean/ParentBadgeBean.kt new file mode 100644 index 0000000..946ec7d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/ParentBadgeBean.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/3/6/006. + * @link + * @description: + */ +data class ParentBadgeBean( + var parentName: String, var badgeList: MutableList?, var isExpand: Boolean = false +) { + constructor() : this("", null, false) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/PayResultBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PayResultBean.kt new file mode 100644 index 0000000..1fca3b0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PayResultBean.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.constant.ConstantInt +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/3/28/028. + * @link + * @description:BraintreePal请求结果返回 + */ +@Parcelize +data class PayResultBean( + var orderId: String, + var tokenId: String, + var url: String, + var surplusDays: Long, + var expirationTime: String, + var deviceOutId: String, + var deviceId: String, + //设备充值类型,0=首次设备充值激活,1=已添加设备再次添加设备充值,2=给当前的设备续费或者升级套餐 + var rechargeType: Int +) : Parcelable { + constructor() : this("", "", "", 0L, "", "", "", ConstantInt.Type1) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/PetAllInfoBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PetAllInfoBean.kt new file mode 100644 index 0000000..70cefac --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PetAllInfoBean.kt @@ -0,0 +1,15 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/5/28/028. + * @link + * @description: + */ +data class PetAllInfoBean( + var badges: MutableList?, + var activity: ActiveTimeBean?, + var petInfo: PetBean?, + var invites: MutableList? +) { + constructor() : this(null, null, null, null) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/PetBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PetBean.kt new file mode 100644 index 0000000..74f4141 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PetBean.kt @@ -0,0 +1,52 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.base.BaseDiffBean +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/2/28/028. + * @link + * @description: + */ +@Parcelize +data class PetBean( + var petId: String, + var deviceServiceId: String, + var petName: String, + var breed: String, + var shared: Int, //0=没有分享,1=是授权分享的 + var petType: Int, //宠物类别 0=其他 1是狗 2是猫 + var userId: String, + var imgurl: String, + var gender: Int, + var birthdayDate: String, + var createDate: String, + var weight: Float, + var height: Float, + var labels: String, + var deviceOutId: String, + var macID: String, + var mealType: Int, + var availableOrder: Int,//判断套餐是否可用或过期,1是可用 0不可用 + var deviceId: String +) : Parcelable, BaseDiffBean() { + constructor() : this( + "", "", "", "", 0, 1, MMKVUtil.getString(MMKVKey.UserId), "", 0, Utils.formatTime( + System.currentTimeMillis(), Utils.DATE_FORMAT_PATTERN_CN + ), "", 0f, 0f, "", "", "", 1, 1, "" + ) + + override fun isSameObject(other: Any): Boolean { + val oldItem = other as PetBean + return oldItem.petId == petId + } + + override fun hasSameContent(other: Any): Boolean { + val oldItem = other as PetBean + return oldItem.petName == petName && oldItem.imgurl == imgurl && oldItem.deviceOutId == deviceOutId && oldItem.breed == breed && oldItem.gender == gender && oldItem.birthdayDate == birthdayDate && oldItem.petType == petType && oldItem.weight == weight && oldItem.height == height && oldItem.labels == labels + } +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/PointBean.kt b/app/src/main/java/com/abbidot/tracker/bean/PointBean.kt new file mode 100644 index 0000000..b792724 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/PointBean.kt @@ -0,0 +1,14 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/1/26/026. + * @link + * @description: + */ +data class PointBean(var x: Float, var y: Float) { + constructor() : this(0f, 0f) + + override fun toString(): String { + return "x=$x,y=$y" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/ReceiveDeviceData.kt b/app/src/main/java/com/abbidot/tracker/bean/ReceiveDeviceData.kt new file mode 100644 index 0000000..390cfd6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/ReceiveDeviceData.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.bean + +import com.clj.fastble.data.BleDevice + +/** + *Created by .yzq on 2021/12/1/001. + * @link + * @description: + */ +data class ReceiveDeviceData(var bleDevice: BleDevice?, var data: ByteArray?, var mac: String) { + constructor() : this(null, null, "") +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/RechargeDeviceBean.kt b/app/src/main/java/com/abbidot/tracker/bean/RechargeDeviceBean.kt new file mode 100644 index 0000000..a2f4c2c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/RechargeDeviceBean.kt @@ -0,0 +1,29 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import com.abbidot.tracker.constant.ConstantInt +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/3/25/025. + * @link + * @description:my card + */ +@Parcelize +data class RechargeDeviceBean( + var deviceId: String, + var deviceOutId: String, + var payType: Int,//付款渠道 1 信用卡 2Paypal + var orderId: String, + var subscriptionsId: String,//取消自动续费用的id + var overdueDate: String,//过期日期 + @ConstantInt var subscriptionStatus: Int,//自动续费状态 + var pet: PetBean?, + var surplusDays: Int, + var addStatus: Int, + var orderStatus: Int, + var period: String, + var iccid: String +) : Parcelable { + constructor() : this("", "", 0, "", "", "", ConstantInt.Close, null, 0, 0, 0, "0", "") +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/RechargeHistoryBean.kt b/app/src/main/java/com/abbidot/tracker/bean/RechargeHistoryBean.kt new file mode 100644 index 0000000..6b3571d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/RechargeHistoryBean.kt @@ -0,0 +1,14 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/3/26/026. + * @link + * @description: + */ +data class RechargeHistoryBean(var cost: Float, + var historyId: String, + var payTime: String, + var feePackage: String, + var deviceOutId: String) { + constructor() : this(0f, "", "", "", "") +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/SetMealBean.kt b/app/src/main/java/com/abbidot/tracker/bean/SetMealBean.kt new file mode 100644 index 0000000..75e3c6a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/SetMealBean.kt @@ -0,0 +1,24 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2022/3/26/026. + * @link + * @description:充值套餐列表 + */ +@Parcelize +data class SetMealBean(var subscriptionFee: Double, + var hotType: Int,//1=hot 0=普通 + var mealId: String, + var name: String, + var createdDate: String, + var updateDate: String, + var days: Int, + var period: Float, + var planId: String, + var selected: Boolean, + var accountFee: Double) : Parcelable { + constructor() : this(0.0, 0, "", "", "", "", 0, 0f, "", false,0.0) +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/SleepTimeBean.kt b/app/src/main/java/com/abbidot/tracker/bean/SleepTimeBean.kt new file mode 100644 index 0000000..4f5add1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/SleepTimeBean.kt @@ -0,0 +1,20 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/3/15/015. + * @link + * @description: + */ +data class SleepTimeBean( + var dayTime: String, + var deviceId: String, + var sleepTime: Int, + var activeTime: Int, + var sleepId: String, + var sleepValue: String, + var avgSleepTime: Int, + var sevenDatas: MutableList? = null, + var thirtyDatas: MutableList? = null +) { + constructor() : this("", "", 0, 0, "", "", 0, null, null) +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/StripeSubscriptionPriceBean.kt b/app/src/main/java/com/abbidot/tracker/bean/StripeSubscriptionPriceBean.kt new file mode 100644 index 0000000..7bd8364 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/StripeSubscriptionPriceBean.kt @@ -0,0 +1,10 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2023/7/20/020. + * @link + * @description: + */ +data class StripeSubscriptionPriceBean(var customerId: String, var priceId: String) { + constructor() : this("", "") +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/SubscriptionsOrderBean.kt b/app/src/main/java/com/abbidot/tracker/bean/SubscriptionsOrderBean.kt new file mode 100644 index 0000000..b3d735f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/SubscriptionsOrderBean.kt @@ -0,0 +1,98 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2024/9/4/004. + * @link + * @description: + */ +@Parcelize +data class SubscriptionsOrderBean( + var annualCarePrice: Double, + var annualCareType: Int, + var couponCode: String, + var couponPrice: Double, + var createTime: String, + var customerId: String, + var deviceId: String, + var deviceOutId: String, + var enabled: Int, + var id: String, + var mealDesc: String, + var mealId: String, + var mealName: String, + var mealPeriod: Int, + var mealPrice: Double, + var mealDiscountPrice: Double, + var orderNum: String, + var orderStatus: Int,//0支付取消 1支付成功 2支付失败 3待支付 4申请审核中 5退款中 6退款成功 7退款失败 8审核失败 + var payTime: String, + var payType: Int, + var planCategory: String, + var planId: String, + var priceId: String, + var reactivatePrice: Double, + var reactivation: Int, + var simId: String, + var subscriptionId: String, + var subscriptionStatus: Int, + var surplusDays: Long, + var autoRenewPrice: Double, + var tax: Double, + var totalAmountWithTax: Double, + var transactionId: String, + var updateTime: String, + var useCoupon: Int, + var userId: String, + var mealUnit: String, + var username: String, + var iccid: String, + var endTime: Long, + var isUpdateOrder: Int = 0//是否是升级订单 1:是 0:否 +) : Parcelable { + constructor() : this( + 0.0, + 0, + "", + 0.0, + "", + "", + "", + "", + 0, + "", + "", + "", + "", + 0, + 0.0, + 0.0, + "", + 0, + "", + 0, + "", + "", + "", + 0.0, + 0, + "", + "", + 0, + 0L, + 0.0, + 0.0, + 0.0, + "", + "", + 0, + "", + "", + "", + "", + 0L, + 0 + ) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/UserBean.kt b/app/src/main/java/com/abbidot/tracker/bean/UserBean.kt new file mode 100644 index 0000000..c9f184c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/UserBean.kt @@ -0,0 +1,26 @@ +package com.abbidot.tracker.bean + +import com.abbidot.tracker.constant.ConstantInt + +/** + *Created by .yzq on 2022/2/18/018. + * @link + * @description:登录成功返回用户信息 + */ +data class UserBean( + var userId: String, + var email: String, + var token: String, + var userName: String, + var wxId: String, + //0没有绑定手机号 1绑定了 + var status: Int, + var isBindDevice: Int, //是否绑定了设备 0未绑定 1绑定 + var isBindPet: Int, //是否绑定了宠物 0未绑定 1绑定 + var isExistNewInvite: Int, //是否有被邀请的设备 0没有 1有 + var measurementUnitType: Int//1:Imperial 2:Metric +) { + + //设置空的构造方法 + constructor() : this("", "", "", "", "", 0, 0, 0, 0, ConstantInt.Type1) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/UserInfoBean.kt b/app/src/main/java/com/abbidot/tracker/bean/UserInfoBean.kt new file mode 100644 index 0000000..468bcce --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/UserInfoBean.kt @@ -0,0 +1,37 @@ +package com.abbidot.tracker.bean + +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil + +/** + *Created by .yzq on 2022/2/18/018. + * @link + * @description:用户信息 + */ +data class UserInfoBean( + var userId: String, + var email: String, + var userName: String, + var imgurl: String, + var phone: String, + var location: String, + var gender: Int, + var birthdayDate: String, + var deviceCount: Int, + val countryCode: String +// var pets: MutableList? +) { + + constructor() : this( + MMKVUtil.getString(MMKVKey.UserId), + MMKVUtil.getString(MMKVKey.Email), + "", + "", + "", + "", + 1, + "", + 0, + "" + ) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/WXPayOrderBean.kt b/app/src/main/java/com/abbidot/tracker/bean/WXPayOrderBean.kt new file mode 100644 index 0000000..a40bae6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/WXPayOrderBean.kt @@ -0,0 +1,16 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2022/7/29/029. + * @link + * @description:微信支付预支付订单 + */ +data class WXPayOrderBean(val appid: String, + val noncestr: String, + val packageStr: String, + val partnerid: String, + val prepayid: String, + val sign: String, + val timestamp: String) { + constructor() : this("", "", "", "", "", "", "") +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/bean/WiFiBean.kt b/app/src/main/java/com/abbidot/tracker/bean/WiFiBean.kt new file mode 100644 index 0000000..e3660a5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/WiFiBean.kt @@ -0,0 +1,14 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description: + */ +@Parcelize +data class WiFiBean( + var id: Int, var name: String, var mac: String, var signal: Int, var auth_type: Int +) : Parcelable diff --git a/app/src/main/java/com/abbidot/tracker/bean/WiFiBleListBean.kt b/app/src/main/java/com/abbidot/tracker/bean/WiFiBleListBean.kt new file mode 100644 index 0000000..71b9440 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/WiFiBleListBean.kt @@ -0,0 +1,8 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description:获取设备蓝牙返回json字符串 + */ +data class WiFiBleListBean(var wifi: MutableList) diff --git a/app/src/main/java/com/abbidot/tracker/bean/WiFiZoneBean.kt b/app/src/main/java/com/abbidot/tracker/bean/WiFiZoneBean.kt new file mode 100644 index 0000000..3d9f238 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/WiFiZoneBean.kt @@ -0,0 +1,24 @@ +package com.abbidot.tracker.bean + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description: + */ +@Parcelize +data class WiFiZoneBean( + var wifiZoneId: String, + var deviceId: String, + var wifiZoneName: String, + var address: String, + var wifiMac: String, + var wifiName: String, + var longitude: Double, + var latitude: Double, + var type: Int //1是home 2是office +) : Parcelable { + constructor() : this("", "", "", "", "", "", 0.0, 0.0, 1) +} diff --git a/app/src/main/java/com/abbidot/tracker/bean/WiFiZoneListBean.kt b/app/src/main/java/com/abbidot/tracker/bean/WiFiZoneListBean.kt new file mode 100644 index 0000000..5c619f0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/bean/WiFiZoneListBean.kt @@ -0,0 +1,10 @@ +package com.abbidot.tracker.bean + +/** + *Created by .yzq on 2024/6/5/005. + * @link + * @description: + */ +data class WiFiZoneListBean( + var latitude: Double, var longitude: Double, var list: MutableList? +) diff --git a/app/src/main/java/com/abbidot/tracker/constant/ConstantInt.kt b/app/src/main/java/com/abbidot/tracker/constant/ConstantInt.kt new file mode 100644 index 0000000..ebdd91a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/constant/ConstantInt.kt @@ -0,0 +1,122 @@ +package com.abbidot.tracker.constant + +import android.annotation.SuppressLint +import androidx.annotation.IntDef + +/** + *Created by .yzq on 2022/4/8/008. + * @link + * @description:int 常量 + */ +@SuppressLint("UniqueConstants") +@IntDef( + ConstantInt.Open, + ConstantInt.Close, + ConstantInt.BadZone, + ConstantInt.GoodZone, + ConstantInt.LowBattery20, + ConstantInt.LowBattery10, + ConstantInt.LowBattery30, + ConstantInt.NoSignal, + ConstantInt.Line, + ConstantInt.Point, + ConstantInt.CircleShapeType, + ConstantInt.RectangleShapeType, + ConstantInt.UserLocationType, + ConstantInt.PolygonShapeType, + ConstantInt.OtherLocationType, + ConstantInt.SafeZone, + ConstantInt.NoGoZone, + ConstantInt.OtherPetType, + ConstantInt.DogPetType, + ConstantInt.CatPetType, + ConstantInt.WoMan, + ConstantInt.Man, + ConstantInt.Type0, + ConstantInt.Type1, + ConstantInt.Type2, + ConstantInt.Type3, + ConstantInt.Type4, + ConstantInt.SpecialType, + ConstantInt.isBind, + ConstantInt.NoBind, + ConstantInt.WeakSignal, + ConstantInt.DefaultSportGoal, + ConstantInt.MaxType, + ConstantInt.MinType, + ConstantInt.HeightType, + ConstantInt.WeightType, + ConstantInt.BasicPackage, + ConstantInt.PremiumPackage, + ConstantInt.MinSportGoal, + ConstantInt.NoShare, + ConstantInt.ReportTimeOutTime, + ConstantInt.PetLocationType +) +@Retention(AnnotationRetention.RUNTIME) +annotation class ConstantInt { + companion object { + const val SpecialType = -1 + const val Type0 = 0 + const val Type1 = 1 + const val Type2 = 2 + const val Type3 = 3 + const val Type4 = 4 + + const val OtherPetType = 0 + const val DogPetType = 1 + const val CatPetType = 2 + + const val MaxType = 0 + const val MinType = 1 + const val HeightType = 0 + const val WeightType = 1 + + const val WoMan = 0 + const val Man = 1 + + const val isBind = 1 + const val NoBind = 0 + + const val Open = 1 + const val Close = 0 + const val BadZone = 1 + const val GoodZone = 0 + const val SafeZone = 1 + const val NoGoZone = 2 + const val LowBattery20 = 20 + const val LowBattery10 = 10 + const val LowBattery30 = 30 + const val NoSignal = 0 + const val WeakSignal = 50 + const val Line = 2 + const val Point = 3 + + //围栏样式 1=圆形,2=矩形,3=六边形 + const val CircleShapeType = 1 + const val RectangleShapeType = 2 + const val PolygonShapeType = 3 + + const val OtherLocationType = 2 + const val UserLocationType = 1 + const val PetLocationType = 0 + + //默认的运动目标时间(min) + const val DefaultSportGoal = 60 + + //推荐最小的运动目标时间(min) + const val MinSportGoal = 30 + + //基础套餐 + const val BasicPackage = 1 + + //高级套餐 + const val PremiumPackage = 2 + + //不是分享的 + const val NoShare = 0 + + //上报超时时间(min) + const val ReportTimeOutTime = 70 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/constant/ConstantString.kt b/app/src/main/java/com/abbidot/tracker/constant/ConstantString.kt new file mode 100644 index 0000000..5a6153e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/constant/ConstantString.kt @@ -0,0 +1,118 @@ +package com.abbidot.tracker.constant + +import androidx.annotation.StringDef + +/** + *Created by .yzq on 2022/4/8/008. + * @link + * @description:String 常量 + */ +@StringDef( + ConstantString.isCheck, + ConstantString.Amount, + ConstantString.Card, + ConstantString.Title, + ConstantString.Url, + ConstantString.MapDeviceBean, + ConstantString.Type, + ConstantString.DeviceAuthorized, + ConstantString.PetAuthorized, + ConstantString.DeviceId, + ConstantString.DeviceOutId, + ConstantString.FenceId, + ConstantString.Fence, + ConstantString.CurrentFence, + ConstantString.Edit, + ConstantString.PetLabels, + ConstantString.Pet, + ConstantString.PetHeight, + ConstantString.Man, + ConstantString.WoMan, + ConstantString.PetId, + ConstantString.PetHeadUrl, + ConstantString.DeviceInfo, + ConstantString.RechargeDevice, + ConstantString.SetMeal, + ConstantString.HistoryDataBean, + ConstantString.Mac, + ConstantString.TodayActiveTime, + ConstantString.Index, + ConstantString.Photo, + ConstantString.OrderId, + ConstantString.LkSetMeal, + ConstantString.PartnerCode, + ConstantString.JumpActivity, + ConstantString.WiFi, + ConstantString.WiFiZone, + ConstantString.Latitude, + ConstantString.Longitude, + ConstantString.UserBean, + ConstantString.BleDevice, + ConstantString.isFirstBind, + ConstantString.RechargeType, + ConstantString.Id, + ConstantString.PetWeight, + ConstantString.Upgrade, + ConstantString.PackageUnitMonth, + ConstantString.PackageUnitYear, + ConstantString.SetMealIndex +) +@Retention(AnnotationRetention.SOURCE) +annotation class ConstantString { + companion object { + + const val DeviceAuthorized = "isDeviceAuthorized" + const val PetAuthorized = "isPetAuthorized" + const val DeviceId = "deviceId" + const val DeviceOutId = "deviceOutId" + const val Mac = "mac" + const val FenceId = "fenceId" + const val Fence = "fence" + const val WiFi = "wifi" + const val WiFiZone = "wifiZone" + const val CurrentFence = "currentFence" + const val Edit = "isEdit" + const val PetLabels = "petLabels" + const val Pet = "pet" + const val PetHeadUrl = "petHeadUrl" + const val PetId = "petId" + const val PetHeight = "petHeight" + const val PetWeight = "petWeight" + const val TodayActiveTime = "todayActiveTime" + const val Man = "1" + const val WoMan = "0" + const val DeviceInfo = "deviceInfo" + const val BleDevice = "bleDevice" + const val RechargeDevice = "rechargeDevice" + const val RechargeType = "rechargeType" + const val PartnerCode = "partnerCode" + const val SetMeal = "setMeal" + const val SetMealIndex = "setMealIndex" + const val LkSetMeal = "LksetMeal" + const val JumpActivity = "jumpActivity" + const val Latitude = "latitude" + const val Longitude = "longitude" + const val isFirstBind = "isFirstBind" + + const val isCheck = "isCheck" + const val Amount = "amount" + const val Card = "card" + const val Title = "title" + const val Id = "id" + const val Url = "url" + const val OrderId = "orderId" + const val MapDeviceBean = "mapDeviceBean" + const val UserBean = "userBean" + const val HistoryDataBean = "historyDataBean" + const val Upgrade = "upgrade" + + //套餐单位 + const val PackageUnitMonth = "MONTH" + const val PackageUnitYear = "YEAR" + + //只是用来区分类型 + const val Type = "type" + const val Photo = "photo" + const val Index = "index" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/constant/GetResultCallback.kt b/app/src/main/java/com/abbidot/tracker/constant/GetResultCallback.kt new file mode 100644 index 0000000..43ec746 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/constant/GetResultCallback.kt @@ -0,0 +1,13 @@ +package com.abbidot.tracker.constant + +/** + *Created by .yzq on 2022/2/7/007. + * @link + * @description: + */ +interface GetResultCallback { + fun onResult(any: Any) + + //默认可以不重写,相当于java中的default修饰的 + fun onRequestError(exceptionCode:String?) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/constant/KeyNames.kt b/app/src/main/java/com/abbidot/tracker/constant/KeyNames.kt new file mode 100644 index 0000000..3ce9de2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/constant/KeyNames.kt @@ -0,0 +1,17 @@ +package com.abbidot.tracker.constant + +/** + *Created by .yzq on 2023/7/13/013. + * @link + * @description: + */ +class KeyNames { + companion object { + private const val COMMON_PREFIX = "com.abbidot.metadata" + const val BUG_LY_KEY_NAME = "$COMMON_PREFIX.bugly.key" + const val MAPBOX_ACCESS_TOKEN = "$COMMON_PREFIX.mapbox.access.token" + const val HTTP_REQUEST_KEY = "$COMMON_PREFIX.http.request.key" + const val STRIPE_RELEASE_KEY = "$COMMON_PREFIX.stripe.release.key" + const val STRIPE_TEST_KEY = "$COMMON_PREFIX.stripe.test.key" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/constant/MultipleEntity.kt b/app/src/main/java/com/abbidot/tracker/constant/MultipleEntity.kt new file mode 100644 index 0000000..9dae90a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/constant/MultipleEntity.kt @@ -0,0 +1,23 @@ +package com.abbidot.tracker.constant + +import androidx.annotation.IntDef + +/** + *Created by .yzq on 2021/12/20/020. + * @link + * @description:多布局类型 + */ +@IntDef(MultipleEntity.IMG, MultipleEntity.TEXT, MultipleEntity.IMG_TEXT, MultipleEntity.IMG_IMG, + MultipleEntity.IMG_TEXT_IMG, MultipleEntity.TEXT_TEXT_IMG, MultipleEntity.TEXT_NAME) +@Retention(AnnotationRetention.SOURCE) +annotation class MultipleEntity { + companion object { + const val TEXT = 0 + const val IMG = 1 + const val IMG_TEXT = 2 + const val IMG_IMG = 3 + const val IMG_TEXT_IMG = 4 + const val TEXT_TEXT_IMG = 5 + const val TEXT_NAME = 6 + } +} diff --git a/app/src/main/java/com/abbidot/tracker/database/MyDatabase.kt b/app/src/main/java/com/abbidot/tracker/database/MyDatabase.kt new file mode 100644 index 0000000..660139e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/database/MyDatabase.kt @@ -0,0 +1,68 @@ +package com.abbidot.tracker.database + +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase +import com.abbidot.tracker.MyApplication +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.bean.MessageBean +import com.abbidot.tracker.database.dao.CreditCardDao +import com.abbidot.tracker.database.dao.DeviceDao +import com.abbidot.tracker.database.dao.MessageDao + +/** + *Created by .yzq on 2021/11/6/006. + * @link + * @description:RoomDatabase实例 + */ +//exportSchema = false,防止改数据库内容版本自动增加 +@Database( + entities = [DeviceBean::class, CreditCardBean::class, MessageBean::class], + version = 4, +// autoMigrations = [AutoMigration(from = 1, to = 2)], + exportSchema = false, +) +abstract class MyDatabase : RoomDatabase() { + abstract fun deviceDao(): DeviceDao + abstract fun creditCardDao(): CreditCardDao + abstract fun messageDao(): MessageDao + + companion object { + + //https://blog.csdn.net/shulianghan/article/details/131040728 +// val MIGRATION_1_2 = object : Migration(1, 2) { +// override fun migrate(database: SupportSQLiteDatabase) { +// // 执行从旧版本升级到新版本的 SQL 语句 +//// database.execSQL("ALTER TABLE $DeviceTableName ADD COLUMN new_column INTEGER DEFAULT 0 NOT NULL") +//// database.execSQL("ALTER TABLE $CreditCardTableName ADD COLUMN new_column INTEGER DEFAULT 0 NOT NULL") +// database.execSQL("CREATE TABLE $MessageTableName(messageId TEXT NOT NULL PRIMARY KEY,title TEXT,message TEXT,sendTime TEXT,messageType INTEGER,imgUrl TEXT,isRead BIT)") +// } +// } + + //MyDatabase单例模式 + private val db: MyDatabase by lazy { + Room.databaseBuilder(MyApplication.instance, MyDatabase::class.java, "tracker_dp") +// .addMigrations(MIGRATION_1_2) + .fallbackToDestructiveMigration()//删除旧表格并创建新表格,的方式升级数据库版本 + .build() + } + + //表名 + const val CreditCardTableName = "creditCard" + const val DeviceTableName = "device" + const val MessageTableName = "message" + + fun deviceDao(): DeviceDao { + return db.deviceDao() + } + + fun creditCardDao(): CreditCardDao { + return db.creditCardDao() + } + + fun messageDao(): MessageDao { + return db.messageDao() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/database/dao/BaseDao.kt b/app/src/main/java/com/abbidot/tracker/database/dao/BaseDao.kt new file mode 100644 index 0000000..b134263 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/database/dao/BaseDao.kt @@ -0,0 +1,119 @@ +package com.abbidot.tracker.database.dao + +import android.util.Log +import androidx.room.* +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.sqlite.db.SupportSQLiteQuery + +/** + *Created by .yzq on 2022/4/1/001. + * @link + * @description:数据库Dao基类 + */ +interface BaseDao { + /** + * 获取表名 + */ +// val tableName: String +// get() { +// val clazz = +// (javaClass.superclass.genericSuperclass as ParameterizedType).actualTypeArguments[0] as Class<*> +// val tableName = clazz.simpleName +// LogUtil.d("ez", "getTableName: -->$tableName") +// return tableName +// } + + /** + * 添加单个对象 + * OnConflictStrategy.REPLACE新数据替换旧数据 + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(obj: T): Long + + /** + * 添加数组对象数据 + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(vararg objs: T): LongArray? + + /** + * 添加对象集合 + */ + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insert(personList: List): List + + /** + * 根据对象中的主键删除(主键是自动增长的,无需手动赋值) + */ + @Delete + fun delete(obj: T) + + /** + * 根据对象中的主键更新(主键是自动增长的,无需手动赋值) + */ + @Update + fun update(vararg obj: T): Int + + fun deleteAll(tableName:String): Int { + val query = SimpleSQLiteQuery("delete from $tableName") + return doDeleteAll(query) + } + + fun findAll(tableName:String): List? { + val query = SimpleSQLiteQuery("select * from $tableName") + return doFindAll(query) + } + + fun find(id: Long,tableName:String): T? { + val query = SimpleSQLiteQuery("select * from $tableName where id = ?", arrayOf(id)) + return doFind(query) + } + + /** + * [params] 列名 + * [value] 列的值 + */ + fun deleteByParams(params: String, value: String,tableName:String): Int { + val query = SimpleSQLiteQuery("delete from $tableName where $params='${value}'") + Log.d("ez", "deleteByParams: ${"delete from $tableName where $params='${value}'"}") + return doDeleteByParams(query) + } + + /** + * 分页查询,支持传入多个字段,但必须要按照顺序传入 + * key = value,key = value 的形式,一一对应(可以使用 stringbuilder 去构造一下,这里就不演示了) + */ + fun doQueryByLimit(tableName:String,vararg string: String, limit: Int = 10, offset: Int = 0): List? { + val query = SimpleSQLiteQuery( + "SELECT * FROM $tableName WHERE ${string[0]} = '${string[1]}' limit $limit offset $offset") + return doQueryByLimit(query) + } + + /** + * 降序分页查询 + */ + fun doQueryByOrder(tableName:String,vararg string: String, limit: Int = 10, offset: Int = 10): List? { + val query = SimpleSQLiteQuery( + "SELECT * FROM $tableName ORDER BY ${string[0]} desc limit '${limit}' offset '${offset}'") + return doQueryByOrder(query) + } + + + @RawQuery + fun doFindAll(query: SupportSQLiteQuery): List? + + @RawQuery + fun doFind(query: SupportSQLiteQuery): T + + @RawQuery + fun doDeleteAll(query: SupportSQLiteQuery): Int + + @RawQuery + fun doDeleteByParams(query: SupportSQLiteQuery): Int + + @RawQuery + fun doQueryByLimit(query: SupportSQLiteQuery): List? + + @RawQuery + fun doQueryByOrder(query: SupportSQLiteQuery): List? +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/database/dao/CreditCardDao.kt b/app/src/main/java/com/abbidot/tracker/database/dao/CreditCardDao.kt new file mode 100644 index 0000000..03131aa --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/database/dao/CreditCardDao.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.database.dao + +import androidx.room.Dao +import com.abbidot.tracker.bean.CreditCardBean + +/** + *Created by .yzq on 2022/4/1/001. + * @link + * @description: + */ +@Dao +interface CreditCardDao : BaseDao \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/database/dao/DeviceDao.kt b/app/src/main/java/com/abbidot/tracker/database/dao/DeviceDao.kt new file mode 100644 index 0000000..ff09513 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/database/dao/DeviceDao.kt @@ -0,0 +1,18 @@ +package com.abbidot.tracker.database.dao + +import androidx.room.Dao +import androidx.room.Query +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.database.MyDatabase + +/** + *Created by .yzq on 2021/11/8/008. + * @link + * @description:DeviceBean表的dao + */ +@Dao +interface DeviceDao : BaseDao { + + @Query("SELECT * FROM ${MyDatabase.DeviceTableName} WHERE macId LIKE :mac LIMIT 1") + fun findByMac(mac: String): DeviceBean? +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/database/dao/MessageDao.kt b/app/src/main/java/com/abbidot/tracker/database/dao/MessageDao.kt new file mode 100644 index 0000000..ac8bfc5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/database/dao/MessageDao.kt @@ -0,0 +1,12 @@ +package com.abbidot.tracker.database.dao + +import androidx.room.Dao +import com.abbidot.tracker.bean.MessageBean + +/** + *Created by .yzq on 2024/7/19/019. + * @link + * @description: + */ +@Dao +interface MessageDao : BaseDao \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/adapter/RechargeAmountCNAdapter.kt b/app/src/main/java/com/abbidot/tracker/deprecated/adapter/RechargeAmountCNAdapter.kt new file mode 100644 index 0000000..5d21f27 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/adapter/RechargeAmountCNAdapter.kt @@ -0,0 +1,62 @@ +package com.abbidot.tracker.deprecated.adapter + +import android.content.Context +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2022/7/23/023. + * @link + * @description: + */ +class RechargeAmountCNAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + + private var mSelectIndex = -1 + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_recharge_amount_cn + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: SetMealBean) { + val view = holder!!.itemView + if (position == mSelectIndex) { + view.background = ContextCompat.getDrawable(mContext, R.drawable.shape10_yellow_bg) + } else { + view.background = ContextCompat.getDrawable(mContext, R.drawable.shape12_gray_white_bg) + } + ViewUtil.instance.setShapeRadius(view, 6) + + if (item.hotType == ConstantInt.Type1) { + holder.getImageView(R.id.recharge_amount_cn_normal_hot_image).visibility = View.VISIBLE + } else { + holder.getImageView(R.id.recharge_amount_cn_normal_hot_image).visibility = View.GONE + } + val period = item.period + val perMonth = if (period < 2) { + String.format(mContext.getString(R.string.pet_managet_age), period) + } else { + String.format(mContext.getString(R.string.pet_managet_age), period) + } + holder.setText(R.id.recharge_amount_cn_two_time, perMonth) + holder.setText(R.id.recharge_amount_cn_two_money, item.accountFee.toString()) + + val averageMonth = Utils.formatDecimal(item.accountFee / (12 * period), 2) + val monthString = String.format(mContext.getString(R.string.txt_perMonth), averageMonth) + holder.setText(R.id.recharge_amount_cn_two_description, monthString) + } + + fun setSelectIndex(index: Int) { + mSelectIndex = index + notifyDataSetChanged() + } + + fun getSelectIndex() = mSelectIndex +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/adapter/RechargeHistoryCNAdapter.kt b/app/src/main/java/com/abbidot/tracker/deprecated/adapter/RechargeHistoryCNAdapter.kt new file mode 100644 index 0000000..9c111f5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/adapter/RechargeHistoryCNAdapter.kt @@ -0,0 +1,31 @@ +package com.abbidot.tracker.deprecated.adapter + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.RechargeHistoryBean + +/** + *Created by .yzq on 2022/1/29/029. + * @link + * @description:充值记录适配器 + */ +class RechargeHistoryCNAdapter(ctx: Context, list: MutableList?) : + BaseRecyclerAdapter(ctx, list) { + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_recharge_history_cn + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: RechargeHistoryBean) { + holder!!.setText(R.id.recharge_history_cn_pay_time, item.payTime) + val totalString = String.format(mContext.getString(R.string.txt_total_money), + "+${mContext.getString(R.string.txt_month_until)}${item.cost}") + holder.setText(R.id.recharge_history_cn_pay_money, totalString) + holder.setText(R.id.recharge_history_cn_meal_name, item.feePackage) + val deviceName = String.format(mContext.getString(R.string.txt_tracker_name), + item.deviceOutId) + holder.setText(R.id.recharge_history_cn_device_name, deviceName) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/adapter/SelectPaymentMethodAdapter.kt b/app/src/main/java/com/abbidot/tracker/deprecated/adapter/SelectPaymentMethodAdapter.kt new file mode 100644 index 0000000..4b852b4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/adapter/SelectPaymentMethodAdapter.kt @@ -0,0 +1,51 @@ +package com.abbidot.tracker.deprecated.adapter + +import android.content.Context +import androidx.appcompat.widget.AppCompatCheckedTextView +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.util.ViewUtil + + +/** + *Created by .yzq on 2022/3/29/029. + * @link + * @description: + */ +class SelectPaymentMethodAdapter( + ctx: Context, list: MutableList?, showPaypal: Boolean = true +) : BaseRecyclerAdapter(ctx, list) { + + private val mShowPaypal = showPaypal + + override fun getEmptyLayoutId(viewType: Int) = 0 + + override fun getItemLayoutId(viewType: Int) = R.layout.item_payment_method_layout + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: MenuTxtBean) { + ViewUtil.instance.setShapeRadius(holder!!.itemView, 6) + val textView = holder.getTextView(R.id.item_payment_method_text_view) + textView.setCompoundDrawablesWithIntrinsicBounds( + item.imageId, 0, 0, 0 + ) + (holder.getView(R.id.item_payment_method_checkbox) as AppCompatCheckedTextView).isChecked = + item.isSwitch + + if (mShowPaypal && (position == 0 || position == getData().size - 1)) { + textView.text = item.menuName + } else if (position == getData().size - 1) { + textView.text = item.menuName + } else { + val str = item.menuName + var cardFormat = str.substring(0, 4) + for (i in 0 until str.length - 8) { + cardFormat = "$cardFormat*" + } + cardFormat += str.substring(str.length - 4, str.length) + textView.text = cardFormat + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/DetailActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/DetailActivity.kt new file mode 100644 index 0000000..787db4b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/DetailActivity.kt @@ -0,0 +1,99 @@ +package com.abbidot.tracker.deprecated.ui.activity + +import android.widget.Toast +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.databinding.ActivityDetailBinding +import com.abbidot.tracker.dialog.DialogUtils +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIStatusBarHelper +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + +class DetailActivity : BaseActivity(ActivityDetailBinding::inflate) { + + private lateinit var mAdapter: BaseRecyclerAdapter + private lateinit var list: MutableList + + override fun getTopBar() = mViewBinding.titleGg.titleTopBar + + override fun initData() { + super.initData() + //设置状态栏黑色字体与图标 + QMUIStatusBarHelper.setStatusBarLightMode(this) + XEventBus.post(EventName.Test, "cccccccccccccccccccc") + + setTopBarTitle(R.string.app_name) + +// list = mutableListOf( +// DeviceBean(5, 4, "tesdfd", conState = ConState.CONNECTED, "7547sgdgdgy64e4r"), +// DeviceBean(3, 2, "65757", conState = ConState.DISCONNECTED, "gujghj")) + + mAdapter = object : BaseRecyclerAdapter(this, list) { + override fun getEmptyLayoutId(viewType: Int)=R.layout.layout_empty_view + + override fun getItemLayoutId(viewType: Int) = R.layout.layout_list_item + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: DeviceBean) { + holder!!.setText(R.id.sdagfdsgg, item.deviceName) + } + } + + ViewUtil.instance.setRecyclerViewVerticalLinearLayout(mContext,mViewBinding.recyclerView, mAdapter) + + Toast.makeText(application, AppUtils.isDebug().toString(), Toast.LENGTH_LONG).show() + +// val newList: MutableList = ArrayList() + mViewBinding.btnTest.setOnClickListener { + val newList: MutableList = ArrayList() + list = mAdapter.getData() + for (i in 0 until list.size) { + newList.add(list[i].copy()) + } + +// newList.add(DeviceBean(list.size, 4, Math.random().toString(), +// conState = ConState.CONNECTED, "7547sgdgdgy64e4r")) + onRefreshData(newList) + + showToast("add") + } + + mViewBinding.testRemove.setOnClickListener { + val newList: MutableList = ArrayList() + list = mAdapter.getData() + for (i in 0 until list.size) { + newList.add(list[i].copy()) + } + newList.removeAt(2) + onRefreshData(newList) + DialogUtils.showTipDialog(mActivity!!, mViewBinding.testRemove, "remove succes", + QMUITipDialog.Builder.ICON_TYPE_SUCCESS) + } + } + + private fun onRefreshData(newList: MutableList) { + mAdapter.setData(newList) + + + val diffResult = + DiffUtil.calculateDiff(object : BaseDiffCallBack(list, newList) { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition].deviceId == newList[newItemPosition].deviceId + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition].deviceName == newList[newItemPosition].deviceName + + }, true) + + + diffResult.dispatchUpdatesTo(mAdapter) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/LoginRegisterCNActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/LoginRegisterCNActivity.kt new file mode 100644 index 0000000..0c93b1a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/LoginRegisterCNActivity.kt @@ -0,0 +1,126 @@ +package com.abbidot.tracker.deprecated.ui.activity + +import androidx.appcompat.widget.AppCompatCheckBox +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityLoginRegisterCnBinding +import com.abbidot.tracker.deprecated.ui.fragment.forgot.ForgetPasswordCNFinishFragment +import com.abbidot.tracker.deprecated.ui.fragment.forgot.ForgetPasswordCNStartFragment +import com.abbidot.tracker.deprecated.ui.fragment.login.BindPhoneCNFragment +import com.abbidot.tracker.deprecated.ui.fragment.login.LoginCNAccountFragment +import com.abbidot.tracker.deprecated.ui.fragment.login.LoginCNCodeFragment +import com.abbidot.tracker.deprecated.ui.fragment.login.LoginCNFirstFragment +import com.abbidot.tracker.deprecated.ui.fragment.register.RegisterCNFinishFragment +import com.abbidot.tracker.deprecated.ui.fragment.register.RegisterCNStartFragment + +/** + * 国内登录界面 + */ +class LoginRegisterCNActivity : + BaseActivity(ActivityLoginRegisterCnBinding::inflate) { + + var mWXId = "" + var mPhone = "" + + //区分忘记密码 和找回密码 + var mType = ConstantInt.Close + + companion object { + const val Fragment_First = 0 + const val Fragment_Account_Login = 1 + const val Fragment_Code_Login = 2 + const val Fragment_Register_Start = 3 + const val Fragment_Register_Finish = 4 + const val Fragment_Bind_Phone = 5 + const val Fragment_Forget_Password_Start = 6 + const val Fragment_Forget_Password_Finish = 7 + } + + + private lateinit var mFragmentList: MutableList + + override fun getTopBar() = mViewBinding.loginCnTopBar.titleTopBar + + override fun initData() { + super.initData() + setTranslucentTopBar() + mViewBinding.loginCnTopBar.titleTopBarLayout.setBackgroundResource(R.color.btn_yellow_color) + + mFragmentList = mutableListOf( + LoginCNFirstFragment.newInstance(mContext), + LoginCNAccountFragment.newInstance(mContext), + LoginCNCodeFragment.newInstance(mContext), + RegisterCNStartFragment.newInstance(mContext), + RegisterCNFinishFragment.newInstance(mContext), + BindPhoneCNFragment.newInstance(mContext), + ForgetPasswordCNStartFragment.newInstance(mContext), + ForgetPasswordCNFinishFragment.newInstance(mContext) + ) + + var index = 0 + intent.extras?.let { + index = it.getInt(ConstantString.Index, Fragment_First) + mType = it.getInt(ConstantString.Type, ConstantInt.Close) + } + switchFragment(index) + } + + fun switchFragment(index: Int) { + + setTopTitle(getString(R.string.login_txt_welcome)) + setTopContent(getString(R.string.login_txt_seeyou)) + + supportFragmentManager.commit { + + hideFragment(this, mFragmentList[Fragment_First]) + hideFragment(this, mFragmentList[Fragment_Account_Login]) + hideFragment(this, mFragmentList[Fragment_Code_Login]) + hideFragment(this, mFragmentList[Fragment_Register_Start]) + hideFragment(this, mFragmentList[Fragment_Register_Finish]) + hideFragment(this, mFragmentList[Fragment_Bind_Phone]) + hideFragment(this, mFragmentList[Fragment_Forget_Password_Start]) + hideFragment(this, mFragmentList[Fragment_Forget_Password_Finish]) + + //为FragmentTransaction添加指定的自定义属性动画 + setCustomAnimations( + R.anim.slide_in_right, + R.anim.slide_out_left, + R.anim.slide_still, + R.anim.slide_out_right + ) + + if (mFragmentList[index].isAdded) { + show(mFragmentList[index]) + } else { + add(R.id.login_cn_steps_frame, mFragmentList[index]) + } + } + } + + fun setTopTitle(title: String) { + mViewBinding.loginCnTopTitle.text = title + } + + fun setTopContent(content: String) { + mViewBinding.loginCnTopContent.text = content + } + + /** + * 是否同意协议 + */ + fun isAgreePolicy(checkBox: AppCompatCheckBox): Boolean { + if (!checkBox.isChecked) { + val tips = getString(R.string.register_txt_agree1) + getString( + R.string.register_txt_policy + ) + getString(R.string.register_txt_agree2) + getString( + R.string.register_txt_terms + ) + showToast(tips) + } + return checkBox.isChecked + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/SwipeActionActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/SwipeActionActivity.kt new file mode 100644 index 0000000..653a3e4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/SwipeActionActivity.kt @@ -0,0 +1,158 @@ +package com.abbidot.tracker.deprecated.ui.activity + +import android.graphics.Color +import android.widget.TextView +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.base.BaseQMUIRVItemSwipeAction +import com.abbidot.tracker.base.BaseSwipeMenuAdapter +import com.abbidot.tracker.databinding.ActivitySwipeActionBinding +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.recyclerView.QMUIRVItemSwipeAction +import com.qmuiteam.qmui.recyclerView.QMUISwipeAction +import com.qmuiteam.qmui.recyclerView.QMUISwipeViewHolder +import com.qmuiteam.qmui.widget.pullRefreshLayout.QMUIPullRefreshLayout +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class SwipeActionActivity : + BaseActivity(ActivitySwipeActionBinding::inflate) { + + private lateinit var mAdapter: BaseSwipeMenuAdapter + private lateinit var list: MutableList + + override fun getTopBar() = mViewBinding.swipeTestBar.titleTopBar + + override fun initData() { + setTopBarTitle("滑动菜单") + + list = mutableListOf() + + initMenu() + + ViewUtil.instance.setRecyclerViewDecorationLinearLayoutManager(mContext!!, + mViewBinding.swipeTestPull.pullRefreshRv,mAdapter) + + mViewBinding.swipeTestPull.onlyPullToRefresh.setOnPullListener(object : + QMUIPullRefreshLayout.OnPullListener { + override fun onMoveTarget(offset: Int) { + } + + override fun onMoveRefreshView(offset: Int) { + } + + override fun onRefresh() { + mViewBinding.swipeTestPull.onlyPullToRefresh.postDelayed(Runnable { + list.clear() + onDataLoaded() + mViewBinding.swipeTestPull.onlyPullToRefresh.finishRefresh() + }, 1000) + } + }) + mAdapter.setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + showLoading(true) + mViewBinding.swipeTestPull.pullRefreshRv.postDelayed(Runnable { + onDataLoaded() + showLoading(false) + }, 1500) + } + }) + +// onDataLoaded() + } + + private fun initMenu() { + val menu: MutableList = mutableListOf() + mAdapter = object : BaseSwipeMenuAdapter(mContext!!, list) { + + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_empty_view + override fun getItemLayoutId(viewType: Int) = R.layout.layout_list_item + + override fun getSwipeActions(): MutableList { + return menu + } + + override fun bindData(holder: QMUISwipeViewHolder?, position: Int, item: String) { + holder!!.itemView.findViewById(R.id.sdagfdsgg).text = item + } + + } + val deleteAction = mAdapter.mActionBuilder.text("删除").backgroundColor(Color.RED).build() + val writeReviewAction = + mAdapter.mActionBuilder.text("写想法").backgroundColor(Color.BLUE).build() + menu.add(deleteAction) +// menu.add(writeReviewAction) + + val itemSwipeAction = object : BaseQMUIRVItemSwipeAction(QMUIRVItemSwipeAction.SWIPE_LEFT) { + override fun removeItem(position: Int) { + val newList = mAdapter.remove(mAdapter.getData(), position) + updateAdapter(mAdapter.getData(), newList) + } + + override fun onClickAction(swipeAction: QMUIRVItemSwipeAction?, + selected: RecyclerView.ViewHolder?, + action: QMUISwipeAction?) { + super.onClickAction(swipeAction, selected, action) + if (action == deleteAction) { + removeItem(selected!!.adapterPosition) + } else { + swipeAction!!.clear() + } + } + } + val swipeAction = QMUIRVItemSwipeAction(true, itemSwipeAction) + swipeAction.attachToRecyclerView(mViewBinding.swipeTestPull.pullRefreshRv) + } + + private fun onDataLoaded() { + val data: MutableList = ArrayList( + listOf("Helps", "Maintain", "Liver", "Health", "Function", "Supports", "Healthy", + "Fat", "Metabolism", "Nuturally", "Bracket", "Refrigerator", "Bathtub", + "Wardrobe", "Comb", "Apron", "Carpet", "Bolster", "Pillow", "Cushion")) + data.shuffle() + list = mAdapter.getData() + + updateAdapter(list, data) + } + + private fun updateAdapter(oldList: MutableList, newList: MutableList) { + if (oldList.size == 0) { + mAdapter.setData(newList) + mAdapter.notifyDataSetChanged() + return + } + lifecycleScope.launch { + //异步新旧数据比较 + withContext(Dispatchers.Default) { + useDiffUtil(oldList, newList) + }.dispatchUpdatesTo(mAdapter) + + //刷新完成重新定位到第一个数据 + mViewBinding.swipeTestPull.pullRefreshRv.scrollToPosition(0) + } + + } + + /** + * 数据比较 + */ + private fun useDiffUtil(oldList: MutableList, + newList: MutableList): DiffUtil.DiffResult { + mAdapter.setData(newList) + return DiffUtil.calculateDiff(object : BaseDiffCallBack(oldList, newList) { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] + + }, true) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/data/HistoryDataActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/data/HistoryDataActivity.kt new file mode 100644 index 0000000..0479edf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/data/HistoryDataActivity.kt @@ -0,0 +1,186 @@ +package com.abbidot.tracker.deprecated.ui.activity.data + +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import androidx.fragment.app.commit +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityHistoryDataBinding +import com.abbidot.tracker.ui.common.map.HistoryDataMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.vm.DataDetailViewModel +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.MapMarkerInfoView +import com.abbidot.tracker.widget.VerticalTopToBottomSeekBar + +/** + * 历史数据 + */ +class HistoryDataActivity : + BaseActivity(ActivityHistoryDataBinding::inflate) { + + val mDataDetailViewModel: DataDetailViewModel by viewModels() + val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + //方便保存地理反编译信息 + private lateinit var mHistoryDataList: MutableList + + lateinit var mPet: PetBean + + private lateinit var mHistoryDataMapCommon: HistoryDataMapCommon + + override fun getTopBar() = mViewBinding.activeHistoryDataBar.titleTopBar + + override fun initData() { + super.initData() + //设置沉浸状态栏颜色 + mViewBinding.activeHistoryDataBar.titleTopBarLayout.setBackgroundResource( + R.color.sleep_time_gray_color + ) + setTopBarTitle(R.string.data_active_history) + + mPet = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java)!! + mHistoryDataList = mutableListOf() + + mHistoryDataMapCommon = HistoryDataMapCommon() + val fragment = mHistoryDataMapCommon.getMapFragment( + this, MapMarkerInfoView(mContext), mGeoCoderViewModel + ) {} + supportFragmentManager.commit { + add(R.id.history_data_map_view, fragment) + } + + val today = Utils.getCurrentTime(Utils.DATE_FORMAT_PATTERN_CN) + mViewBinding.historyDataHorizontalCalenderView.setOnDaySelectedListener { hasChanged, year, month, day, week, item -> + if (hasChanged) { + val date = if (item.isCurrent) { + today + } else { + mViewBinding.historyDataHorizontalCalenderView.fillingDate( + "-", year, month, day + ) + } + mViewBinding.historyDataDate.text = date + initState() + mDataDetailViewModel.getHistoryByDay(this, mPet.deviceId, 1, 1) + } + } + + val type = MMKVUtil.getInt(MMKVKey.LineSelectPosition, ConstantInt.Line) + if (type == ConstantInt.Line) { + mViewBinding.historyDateMapLine.isChecked = true + } else { + mViewBinding.historyDateMapNumber.isChecked = true + } + + initEven() + + initState() + } + + override fun liveDataObserve() { + mDataDetailViewModel.mHistoryDetailLive.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + setLatLngData(data) + } + }) + } + + //地理反编译成功返回 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(this) { + mViewBinding.historyDataAddress.apply { + visibility = if (TextUtils.isEmpty(it)) { + View.GONE + } else { + View.VISIBLE + } + text = it + mHistoryDataList[mViewBinding.seekMapLine.progress].address = it + } + } + } + + private fun initEven() { + mViewBinding.seekMapLine.setOnSeekBarChangeProgressListener(object : + VerticalTopToBottomSeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(progress: Int) { + mHistoryDataMapCommon.slideChanged(progress) + } + + override fun onStopChanged(progress: Int) { + val historyDataBean = mHistoryDataList[progress] + showDate(historyDataBean.timeStamp * 1000) + + val address = historyDataBean.address + if (TextUtils.isEmpty(address)) { + mHistoryDataMapCommon.slideStopChanged(progress) + } else { + mViewBinding.historyDataAddress.text = address + } + } + }) + + mViewBinding.historyDateMapTypeGroup.setOnCheckedChangeListener { _, checkedId -> + mViewBinding.seekMapLine.progress = mViewBinding.seekMapLine.max + when (checkedId) { + mViewBinding.historyDateMapLine.id -> { + MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Line) + mHistoryDataMapCommon.addSetLine() + } + + mViewBinding.historyDateMapNumber.id -> { + MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Point) + mHistoryDataMapCommon.addSetNumber() + } + } + } + } + + private fun initState() { + mViewBinding.apply { + historyDataAddress.visibility = View.GONE + historyDataDate.visibility = View.GONE + seekMapLine.progress = 0 + } + mHistoryDataMapCommon.clearAllMarker() + setSeekBarMax(0) + } + + private fun setLatLngData(data: MutableList) { + mHistoryDataList = data + if (data.size == 0) return + + val max = data.size - 1 + setSeekBarMax(max) + showDate(data[max].timeStamp * 1000) + + mHistoryDataMapCommon.setLatLngData(data) + } + + private fun showDate(time: Long) { + val date = Utils.formatTime(time, Utils.DATE_FORMAT_PATTERN_CN3) + mViewBinding.historyDataDate.apply { + visibility = View.VISIBLE + text = date + } + } + + private fun setSeekBarMax(max: Int) { + mViewBinding.seekMapLine.let { + it.max = max + //定位中心显示最后一个经纬度 + it.progress = max + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/map/HomeMapNavigationActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/map/HomeMapNavigationActivity.kt new file mode 100644 index 0000000..6a45265 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/map/HomeMapNavigationActivity.kt @@ -0,0 +1,234 @@ +package com.abbidot.tracker.deprecated.ui.activity.map + +import android.animation.ObjectAnimator +import android.animation.ValueAnimator +import android.content.Intent +import android.text.TextUtils +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import android.view.animation.Animation +import androidx.activity.viewModels +import androidx.fragment.app.commit +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.SelectMapListDialogAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityHomeMapNavigationBinding +import com.abbidot.tracker.dialog.SelectMapListDialog +import com.abbidot.tracker.ui.common.map.NavigationMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FencesMapViewModel +import com.abbidot.tracker.vm.GeoCoderViewModel + + +/** + * 首页跳转导航 + */ +class HomeMapNavigationActivity : + BaseActivity(ActivityHomeMapNavigationBinding::inflate) { + + val mFencesMapViewModel: FencesMapViewModel by viewModels() + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + private val mMapGooglePackageName = "com.google.android.apps.maps" + private val mMapGaoDePackageName = "com.autonavi.minimap" + + private lateinit var mSelectMapListDialogAdapter: SelectMapListDialogAdapter + private var mSelectMapListDialog: SelectMapListDialog? = null + var mMapDeviceBean: MapDeviceBean? = null + + private lateinit var mNavigationMapCommon: NavigationMapCommon + + override fun getTopBar() = mViewBinding.homeMapNavigationBar.titleTopBar + + override fun initData() { + super.initData() + var historyDataBean: HistoryDataBean? = null + var pet: PetBean? = null + intent.extras?.apply { + pet = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + historyDataBean = Util.getParcelableAdaptive( + intent, ConstantString.HistoryDataBean, HistoryDataBean::class.java + ) + mMapDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.MapDeviceBean, MapDeviceBean::class.java + ) + } + if (null == historyDataBean) historyDataBean = HistoryDataBean() + if (null == pet) pet = PetBean() + if (null == mMapDeviceBean) mMapDeviceBean = MapDeviceBean() +// +// val title = String.format(getString(R.string.map_navigate), pet!!.petName) +// setTopBarTitle(title) + + mNavigationMapCommon = NavigationMapCommon() + val fragment = mNavigationMapCommon.getMapFragment(mContext, pet!!.imgurl) + supportFragmentManager.commit { + add(R.id.home_map_navigation_map_view, fragment) + } + + val mapList = mutableListOf() + ViewUtil.instance.addMenuBean( + mapList, + getString(R.string.map_navigate_map_google), + mMapGooglePackageName, + R.drawable.ico_map_google + ) + ViewUtil.instance.addMenuBean( + mapList, + getString(R.string.map_navigate_map_gaode), + mMapGaoDePackageName, + R.drawable.ico_map_gaode + ) + + mSelectMapListDialogAdapter = SelectMapListDialogAdapter(mContext!!, mapList) + mSelectMapListDialogAdapter.setOnItemClickListener(object : + BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + selectMapNavigation(mapList[pos]) + } + }) + + setOnClickListenerViews(mViewBinding.selectMapNavigationBtn) + + +// val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN).build() +// val mGoogleSignInClient: GoogleSignInClient = GoogleSignIn.getClient(this, gso) +// val mGoogleApiClient = mGoogleSignInClient.asGoogleApiClient() + + + ViewUtil.instance.imageLoadUrl( + mViewBinding.homeMapNavigationPetHead.appHeadImage, pet!!.imgurl + ) + if (TextUtils.isEmpty(historyDataBean!!.address)) { + mGeoCoderViewModel.getLatLonAddress( + mMapDeviceBean!!.latitude, mMapDeviceBean!!.longitude + ) + } else mViewBinding.homeMapNavigationAddress.text = historyDataBean!!.address + + warningOutFence() + } + + override fun liveDataObserve() { + //地理反编译成功返回 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(this) { + mViewBinding.homeMapNavigationAddress.text = it + } + } + + /*** + * 设置距离 + */ + fun setDistanceData(distanceStr: String, minStr: String) { + mViewBinding.apply { + mapNavigationDistanceValue.text = distanceStr + mapNavigationWalkValue.text = minStr + } + } + + /** + * 宠物出围栏警告 + */ + private fun warningOutFence() { + //没有出围栏 +// if (mMapDeviceBean!!.inFence == ConstantInt.InFence) { +// return +// } + val animation = + ViewUtil.instance.viewLoadAnimation(mContext, mViewBinding.homeMapNavigationPetLayout) + animation.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) { + mViewBinding.homeMapNavigationPetLayout.visibility = View.VISIBLE + } + + override fun onAnimationEnd(animation: Animation?) { + val rotationObjectAnimator = ObjectAnimator.ofFloat( + mViewBinding.homeMapNavigationPetLayout, "rotation", 0f, 1.6f, 0f - 1.6f + ) + rotationObjectAnimator.duration = 120 + rotationObjectAnimator.interpolator = AccelerateDecelerateInterpolator() + rotationObjectAnimator.repeatMode = ValueAnimator.REVERSE + rotationObjectAnimator.repeatCount = ValueAnimator.INFINITE + rotationObjectAnimator.start() + + val animation2 = ViewUtil.instance.viewLoadAnimation( + mContext!!, + mViewBinding.homeMapNavigationPetLayout, + R.anim.scale_out_center, + startOffset = 8000 + ) + animation2.setAnimationListener(object : Animation.AnimationListener { + override fun onAnimationStart(animation: Animation?) { + } + + override fun onAnimationEnd(animation: Animation?) { + mViewBinding.homeMapNavigationPetLayout.visibility = View.GONE + } + + override fun onAnimationRepeat(animation: Animation?) { + } + }) + } + + override fun onAnimationRepeat(animation: Animation?) { + } + }) + } + + /** + * 选择地图开始跳转 + */ + private fun selectMapNavigation(item: MenuTxtBean) { + mSelectMapListDialog?.dismiss() + //自动弹出手机中已经有安装地图的应用 +// val mUri: Uri = Uri.parse("geo:227.15" + ",212.55" + "?q=fhfhfg") +// val mIntent = Intent(Intent.ACTION_VIEW, mUri) +// startActivity(mIntent) + if (AppUtils.isAppInstalled(mContext!!, item.menuValue)) { + if (null == mMapDeviceBean) { + + } else { + when (item.menuValue) { + //选择高德地图 + mMapGaoDePackageName -> { + val uri = "androidamap://navi?sourceApplication=" + getString( + R.string.app_name + ) + "&lat=${mMapDeviceBean!!.latitude}&lon=${mMapDeviceBean!!.longitude}&dev=0" + val intent = Intent.parseUri(uri, 0) + startActivity(intent) + } + //谷歌地图 + mMapGooglePackageName -> { +// val latitude = 22.56025 +// val longitude = 113.954032 + val uri = + "google.navigation:q=${mMapDeviceBean!!.latitude},${mMapDeviceBean!!.longitude}" + val intent = Intent.parseUri(uri, 0) + startActivity(intent) + } + } + } + } else { + showToast(getString(R.string.txt_no_install)) + } + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.selectMapNavigationBtn -> { + if (null == mSelectMapListDialog) { + mSelectMapListDialog = + SelectMapListDialog(mContext!!, mSelectMapListDialogAdapter) + } + mSelectMapListDialog!!.show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/map/MapLiveActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/map/MapLiveActivity.kt new file mode 100644 index 0000000..b674827 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/map/MapLiveActivity.kt @@ -0,0 +1,276 @@ +package com.abbidot.tracker.deprecated.ui.activity.map + +import android.animation.ValueAnimator +import android.view.View +import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import coil.load +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityMapLiveBinding +import com.abbidot.tracker.vm.MapLiveViewModel +import com.abbidot.tracker.vm.FencesManageViewModel +import com.abbidot.tracker.vm.FencesMapViewModel +import com.abbidot.tracker.vm.FindBleDeviceViewModel +import com.abbidot.tracker.vm.TrackerSetViewModel +import com.abbidot.tracker.ui.common.map.MapLiveCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager +import com.clj.fastble.data.BleDevice +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo +import com.qmuiteam.qmui.widget.QMUITopBar + +/** + *MapLive实时监测位置 + */ +class MapLiveActivity : BaseActivity(ActivityMapLiveBinding::inflate) { + + private val mMapLiveViewModel: MapLiveViewModel by viewModels() + val mFencesMapViewModel: FencesMapViewModel by viewModels() + val mFencesManageViewModel: FencesManageViewModel by viewModels() + private val mTrackerManageViewModel: TrackerSetViewModel by viewModels() + private val mFindBleDeviceViewModel: FindBleDeviceViewModel by viewModels() + lateinit var mPet: PetBean + + private lateinit var mFragment: Fragment + private lateinit var mMapLiveCommon: MapLiveCommon + + //宠物是否在围栏里 + private var isInFence = -1 + + //围栏数据集 + private val mFencesList = mutableListOf() + private var mHistoryDataBean: HistoryDataBean? = null + private var mBleDevice: BleDevice? = null + + //动画效果,方便清除动画 + private var mGpsYoYoAnimString: YoYo.YoYoString? = null + private var mBatteryYoYoAnimString: YoYo.YoYoString? = null + private var mLiveStatus = 1 + + override fun getTopBar(): QMUITopBar? = null + + override fun initData() { + super.initData() + + mPet = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java)!! + + mMapLiveCommon = MapLiveCommon() + mFragment = mMapLiveCommon.getMapFragment(mContext, mPet.imgurl) + supportFragmentManager.commit { + add(R.id.map_live_map_view, mFragment) + } + + mViewBinding.apply { + setOnClickListenerViews( + btnStopMapLive, mapLiveDeviceLedSwitch, mapLiveRefreshLocationBtn, btnFindDevice + ) + } + + mMapLiveViewModel.setupDeviceLiveStatus(this, mPet.deviceId, mLiveStatus) + } + + override fun liveDataObserve() { + mMapLiveViewModel.mMapLiveStatusLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showLoading(false) + if (mLiveStatus == 0) finish() + } + }) + } + + mMapLiveViewModel.mMapLiveLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! +// setData(data) + } + }) + } + + mFencesManageViewModel.mFencesLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! +// mFencesList.addAll(data) + //获取完围栏数据 再获取宠物 经纬度点 的数据 + mMapLiveViewModel.getPetLivePoint(mPet.deviceId) + } + }) + } + + + mTrackerManageViewModel.mLedSwitchLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mViewBinding.mapLiveDeviceLedSwitch.isChecked = + !mViewBinding.mapLiveDeviceLedSwitch.isChecked + } + }) + } + } + + /** + * 测试经纬度移动 + */ + private var indexX = 0.0 + private var indexY = 0.0 + + private fun setData(historyDataBean: HistoryDataBean) { + mHistoryDataBean = historyDataBean + if (isInFence != historyDataBean.inFance) { + isInFence = historyDataBean.inFance + mFencesMapViewModel.setFencesData(mContext, mFencesList, mFragment) + } + //测试经纬度移动 +// mFencesViewModel.setFencesData(mContext!!, isInFence, mFencesList, mFragment) + + mViewBinding.apply { + mapLivePetLayout.mapPetName.text = historyDataBean.petName + ViewUtil.instance.imageLoadUrl( + mapLivePetLayout.mapPetHead.appHeadImage, historyDataBean.petImgurl + ) + val batteryString = + String.format(getString(R.string.txt_battery_unit), historyDataBean.batteryLevel) + mapLivePetLayout.mapPetBattery.text = batteryString + + val batteryResId = if (historyDataBean.batteryLevel > ConstantInt.LowBattery20) { + mBatteryYoYoAnimString?.stop() + R.drawable.ico_map_battery + } else { + mBatteryYoYoAnimString = null + mBatteryYoYoAnimString = + YoYo.with(Techniques.FadeIn).repeat(ValueAnimator.INFINITE).duration(1000) + .playOn(mapLivePetLayout.mapPetBatteryImage) + R.drawable.ico_map_battery_low + } + mapLivePetLayout.mapPetBatteryImage.load(batteryResId) + + val drawableResId = if (historyDataBean.gnssSignal > ConstantInt.NoSignal) { + mGpsYoYoAnimString?.stop() + mapLivePetLayout.mapPetGps.text = getString(R.string.txt_online) + R.drawable.ico_map_gps + } else { + mapLivePetLayout.mapPetGps.text = getString(R.string.txt_offline) + mGpsYoYoAnimString = null + mGpsYoYoAnimString = + YoYo.with(Techniques.FadeIn).repeat(ValueAnimator.INFINITE).duration(1000) + .playOn(mapLivePetLayout.mapPetGpsImage) + R.drawable.ico_gps_no + } + mapLivePetLayout.mapPetGpsImage.load(drawableResId) + + mapLiveDeviceLedSwitch.isChecked = historyDataBean.ledSwitch == ConstantInt.Open + + mMapLiveCommon.addLine(historyDataBean.latitude, historyDataBean.longitude) + } + + //测试经纬度移动 +// if ((Math.random() * 100).toInt() % 2 == 0) { +// indexX += (Math.random() * 0.02) +// indexY -= (Math.random() * 0.02) +// isInFence = ConstantInt.InFence +// } else { +// indexX -= (Math.random() * 0.03) +// indexY += (Math.random() * 0.03) +// isInFence = ConstantInt.OutFence +// } +// mMapLiveBaiduMapFragment?.addLine(indexX, indexY) +// mMapLiveGoogleMapFragment?.addLine(LatLng(indexX, indexY)) + } + + override fun onBackPressed() { + stopMapLive() + } + + private fun stopMapLive() { + ViewUtil.instance.showDialog( + mContext, R.string.txt_stop_live_tips, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mLiveStatus = 0 + mMapLiveViewModel.setupDeviceLiveStatus( + this@MapLiveActivity, mPet.deviceId, mLiveStatus + ) + } + }, okTextResId = R.string.txt_sure + ) + } + + /** + * 设置led开关 + */ + private fun setLedSwitch() { + //屏蔽点击就变状态 + mViewBinding.mapLiveDeviceLedSwitch.isChecked = + !mViewBinding.mapLiveDeviceLedSwitch.isChecked + val ledSwitch = if (mViewBinding.mapLiveDeviceLedSwitch.isChecked) { + setBleLedMode(mPet.macID, 0) + ConstantInt.Close + } else { + setBleLedMode(mPet.macID, mHistoryDataBean!!.lightMode) + ConstantInt.Open + } + mTrackerManageViewModel.setLedSwitch(this, mHistoryDataBean!!.deviceInfoId, ledSwitch) + } + + /** + *发送设备蓝牙指令 + */ + private fun setBleLedMode(macId: String, mode: Int) { + if (null == mBleDevice) { + SRBleUtil.instance.getConnectMacDevice(macId)?.apply { + mBleDevice = bleDevice + } + } + mBleDevice?.let { + if (BleManager.getInstance().isConnected(it)) { + SRBleUtil.instance.writeData( + it, SRBleCmdUtil.instance.ledState(SRBleCmdUtil.CMD_WRITE, mode) + ) + } + } + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnStopMapLive -> { + stopMapLive() + } + + mViewBinding.btnFindDevice -> { +// mFindBleDeviceViewModel.startScanFind(this, mPet.macID) + } + + mViewBinding.mapLiveDeviceLedSwitch -> { + setLedSwitch() + } + + mViewBinding.mapLiveRefreshLocationBtn -> { + ViewUtil.instance.viewAlphaAndRotationObjectAnimator( + mViewBinding.mapLiveRefreshLocationBtn + ) + mMapLiveCommon.refreshPetCurrentLocation() + } + } + } + + override fun onDestroy() { + super.onDestroy() + mBatteryYoYoAnimString?.stop() + mGpsYoYoAnimString?.stop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/FencesAddActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/FencesAddActivity.kt new file mode 100644 index 0000000..688ed59 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/FencesAddActivity.kt @@ -0,0 +1,502 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile + +import android.content.Intent +import android.graphics.Point +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import androidx.fragment.app.commit +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityFencesAddBinding +import com.abbidot.tracker.ui.common.map.FencesMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FencesManageViewModel +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.vm.ScreenshotsViewModel +import com.abbidot.tracker.vm.UserProfileViewModel +import com.abbidot.tracker.widget.FencesPolygonView +import com.abbidot.tracker.widget.OnCircleViewScaleClickListener +import com.abbidot.tracker.widget.OnRectViewRotateScaleClickListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/*** + * 添加围栏 + */ +class FencesAddActivity : BaseActivity(ActivityFencesAddBinding::inflate), + FencesMapCommon.OnActivityCallBack, OnRectViewRotateScaleClickListener, + OnCircleViewScaleClickListener { + + private val mFencesManageViewModel: FencesManageViewModel by viewModels() + private val mScreenshotsViewModel: ScreenshotsViewModel by viewModels() + private val mUserInfoViewModel: UserProfileViewModel by viewModels() + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + var isEdit = false + private lateinit var mDeviceId: String + private var mFenceId = "" + private var mDeviceServiceId = "" + + //围栏样式 + var mFenceShapeType = ConstantInt.CircleShapeType + + private lateinit var mFencesMapCommon: FencesMapCommon + + //圆形半径距离 + private var mCircleRadiusDistance = 0 + + //矩形长距离 + private var mRectWidthDistance = 0 + + //矩形宽距离 + private var mRectHeightDistance = 0 + + + override fun getTopBar() = mViewBinding.fencesAddBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_add_fences) + + isEdit = intent.extras!!.getBoolean(ConstantString.Edit, false) + mDeviceId = intent.extras!!.getString(ConstantString.DeviceId, "") + + mFencesMapCommon = FencesMapCommon() + + mViewBinding.apply { + + val fragment = mFencesMapCommon.getMapFragment( + mContext, + searchBaiduMapResultsView, + mapboxSearchResultsView, + fencesAddSearchAddress, + this@FencesAddActivity + ) + supportFragmentManager.commit { + add(R.id.fences_add_map_view, fragment) + } + + setOnClickListenerViews( + fencesSearchAddressBtn, addFencesOkBtn, root + ) + radiogroupFencesShowShapeType.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + R.id.selector_fence_circle_shape -> { + if (mFenceShapeType == ConstantInt.CircleShapeType) return@setOnCheckedChangeListener + mFenceShapeType = ConstantInt.CircleShapeType + } + + R.id.selector_fence_rectangle_shape -> { + if (mFenceShapeType == ConstantInt.RectangleShapeType) return@setOnCheckedChangeListener + mFenceShapeType = ConstantInt.RectangleShapeType + } + } + setFenceShape(mFenceShapeType) + //延迟计算距离 + if ((mFenceShapeType == ConstantInt.CircleShapeType && mCircleRadiusDistance == 0) || (mFenceShapeType == ConstantInt.RectangleShapeType && mRectHeightDistance == 0 && mRectWidthDistance == 0)) { + mViewBinding.fencesAddMapRect.postDelayed( + { + mFencesMapCommon.refreshDistance() + }, 500 + ) + } else { + showLoading(false) + } + } + fencesAddMapRect.setOnRectViewRotateScaleClickListener(this@FencesAddActivity) + + fencesAddMapCircle.setOnCircleViewScaleClickListener(this@FencesAddActivity) + + + //编辑 + if (isEdit) { + setTopBarTitle(R.string.txt_edit_fences) + mRightImageButton = addRightImageButton(R.drawable.ico_delete) + setOnClickListenerViews(mRightImageButton) + + Util.getParcelableAdaptive(intent, ConstantString.Fence, FencesBean::class.java) + ?.run { + mFenceId = fenceId + mDeviceServiceId = deviceServiceId + addFencesAddress.text = address + addFencesNameEdit.setText(name) + addActivateFencesCheckbox.isChecked = isOn == 1 + mFenceShapeType = fenceType + if (fenceType == ConstantInt.CircleShapeType) { + fencesAddMapRect.initDefaultWidthHeight() + selectorFenceCircleShape.isChecked = true + mCircleRadiusDistance = radius + mFencesMapCommon.setCenterLatLng(latitude, longitude, radius.toDouble()) + } else { + fencesAddMapCircle.initDefaultRadius() + selectorFenceRectangleShape.isChecked = true + mRectWidthDistance = longDistance + mRectHeightDistance = shortDistance + mFencesMapCommon.let { + it.setCenterLatLng(latitudeCenter, longitudeCenter) + it.setRectABCDLatLng( + mContext, + longitudeA, + latitudeA, + longitudeB, + latitudeB, + longitudeC, + latitudeC, + longitudeD, + latitudeD + ) + } + } + } + } else { + fencesAddMapCircle.initDefaultRadius() + fencesAddMapRect.initDefaultWidthHeight() + } + setFenceShape(mFenceShapeType) + } + } + + override fun liveDataObserve() { + //上传图片 + mUserInfoViewModel.mUploadImageLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + saveFences(it.getOrNull()!!) + } + }) + } + + if (isEdit) { + mFencesManageViewModel.mDeleteFenceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + successTips(R.string.txt_delete_success) + } + }) + } + mFencesManageViewModel.mUpdateFenceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + successTips(R.string.txt_save_successful) + } + }) + } + + } else { + mFencesManageViewModel.mFencesAddLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + successTips(R.string.txt_save_successful) + } + }) + } + } + + //反地理编码 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(this) { + mViewBinding.addFencesAddress.apply { + visibility = if (TextUtils.isEmpty(it)) { + View.INVISIBLE + } else { + View.VISIBLE + } + text = it + } + } + + //截屏后压缩的图片路径 + mScreenshotsViewModel.mMapSnapshotPathLiveData.observe(this) { + if (!TextUtils.isEmpty(it)) { + mUserInfoViewModel.uploadImage(this, it) + } + } + } + + /** + * 设置显示哪种围栏样式 + */ + private fun setFenceShape(shapeType: Int) { + showLoading(true) + when (shapeType) { + ConstantInt.CircleShapeType -> { + mViewBinding.fencesAddMapCircle.visibility = View.VISIBLE + mViewBinding.fencesAddMapRect.visibility = View.GONE + } + + ConstantInt.RectangleShapeType -> { + mViewBinding.fencesAddMapCircle.visibility = View.GONE + mViewBinding.fencesAddMapRect.visibility = View.VISIBLE + } + } + } + + /** + * 修改成功提示 + */ + private fun successTips(resStringId: Int) { + XEventBus.post(EventName.UpdateData + "FencesActivity") + XEventBus.post(EventName.UpdateData + "MapFences") + XEventBus.post(EventName.UpdateData + "TrackerManageSetActivity") + showToast(resStringId, isFinish = true) + } + + /** + * 获取圆形/矩形中心点 + */ + fun getCentrePoint() = if (mFenceShapeType == ConstantInt.CircleShapeType) { + Point( + mViewBinding.fencesAddMapCircle.getCircleCentrePoint() + ) + } else { + Point( + mViewBinding.fencesAddMapRect.getRectCentrePoint() + ) + } + + /** + * 获取圆形直虚线的最后一个点 + */ + fun getCircleLineEndPoint() = mViewBinding.fencesAddMapCircle.getCircleLineEndPoint() + + /** + * 获取矩形4个点的某一个点 + */ + fun getRectABCDPoint(wherePoint: Int) = + mViewBinding.fencesAddMapRect.getRectABCDPoint(wherePoint) + + /** + * 保存围栏 + */ + private fun saveSure() { + val fenceName = mViewBinding.addFencesNameEdit.text.toString() + val address = mViewBinding.addFencesAddress.text.toString() + if (TextUtils.isEmpty(fenceName) || TextUtils.isEmpty(address)) { + showToast(R.string.txt_not_null) + return + } + + val hideDelayMillis = 400L + when (mFenceShapeType) { + ConstantInt.CircleShapeType -> { + mViewBinding.fencesAddMapCircle.apply { + //隐藏距离,不被截图 + setShowHideDisDistance(false) + //截屏并上传图片 + mFencesMapCommon.snapshotImage( + this@FencesAddActivity, mScreenshotsViewModel, this + ) + postDelayed({ + //几秒后开始显示,截屏成功 + setShowHideDisDistance(true) + }, hideDelayMillis) + } + } + + ConstantInt.RectangleShapeType -> { + mViewBinding.fencesAddMapRect.apply { + //隐藏距离,不被截图 + setShowHideDisDistance(false) + //截屏并上传图片 + mFencesMapCommon.snapshotImage( + this@FencesAddActivity, mScreenshotsViewModel, this + ) + postDelayed({ + //几秒后开始显示,截屏成功 + setShowHideDisDistance(true) + }, hideDelayMillis) + } + + } + } + } + + private fun saveFences(imageUrl: String) { + val fenceName = mViewBinding.addFencesNameEdit.text.toString() + val address = mViewBinding.addFencesAddress.text.toString() + val isOn = if (mViewBinding.addActivateFencesCheckbox.isChecked) { + 1 + } else { + 0 + } + + val latLon = mFencesMapCommon.getCenterLatLng() + val latitude = latLon[0] + val longitude = latLon[1] + if (mFenceShapeType == ConstantInt.CircleShapeType) { + val radius = mCircleRadiusDistance + LogUtil.d("最后的经纬度是,$latitude,$longitude") + if (isEdit) { + mFencesManageViewModel.updateCircleFence( + this, FencesBean() + ) + } else { + mFencesManageViewModel.addCircleFence( + this, FencesBean() + ) + } + } else { + val latLonA = mFencesMapCommon.getRectABCDLatLng(FencesPolygonView.POINT_A) + val latitudeA = latLonA[0] + val longitudeA = latLonA[1] + val latLonB = mFencesMapCommon.getRectABCDLatLng(FencesPolygonView.POINT_B) + val latitudeB = latLonB[0] + val longitudeB = latLonB[1] + val latLonC = mFencesMapCommon.getRectABCDLatLng(FencesPolygonView.POINT_C) + val latitudeC = latLonC[0] + val longitudeC = latLonC[1] + val latLonD = mFencesMapCommon.getRectABCDLatLng(FencesPolygonView.POINT_D) + val latitudeD = latLonD[0] + val longitudeD = latLonD[1] + if (isEdit) { + mFencesManageViewModel.updateRectFence( + this, FencesBean() + ) + } else { + mFencesManageViewModel.addRectFence( + this, FencesBean() + ) + } + } + + } + + /** + * 删除围栏 + */ + private fun deleteFences() { + ViewUtil.instance.showDialog( + mContext, R.string.txt_delete_tips, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mFencesManageViewModel.deleteFence( + this@FencesAddActivity, mFenceId, mDeviceServiceId + ) + } + }, okTextResId = R.string.txt_sure + ) + } + + override fun onClick(v: View?) { + when (v!!) { + mRightImageButton -> { + deleteFences() + } + //点任意位置隐藏搜索结果 + mViewBinding.root -> { + mFencesMapCommon.setSearchResultsViewGone() + } + + mRightImageButton -> { + startActivity(Intent(mContext, FencesAddActivity::class.java)) + } + + mViewBinding.fencesSearchAddressBtn -> { + mFencesMapCommon.searchAddress(mViewBinding.fencesAddSearchAddress.text.toString()) + } + + mViewBinding.addFencesOkBtn -> { + saveSure() + } + } + } + + /** + * 选点计算距离返回 + */ + override fun calculationDistance(distances: Array) { + LogUtil.e("选点计算距离返回") + if (mFenceShapeType == ConstantInt.CircleShapeType) { + mCircleRadiusDistance = Utils.roundOffToInt(distances[0].toDouble()) + mViewBinding.fencesAddMapCircle.setShowDisDistance("${mCircleRadiusDistance}m") + } else { + mRectWidthDistance = Utils.roundOffToInt(distances[0].toDouble()) + mRectHeightDistance = Utils.roundOffToInt(distances[1].toDouble()) + mViewBinding.fencesAddMapRect.setShowDisDistance( + "${mRectWidthDistance}m", "${mRectHeightDistance}m" + ) + } + //经纬度转成地址显示 + mFencesMapCommon.getLatLonAddress(mGeoCoderViewModel) + showLoading(false) + } + + /** + * 地图加载完成返回 + */ + override fun mapCompletion() { + LogUtil.e("地图加载完成返回") + //编辑 + if (isEdit) { + lifecycleScope.launch(Dispatchers.Main) { + //等待地图移动摄像头动画播放结束 + delay(500) + LogUtil.e("等待地图移动摄像头动画播放结束") + if (mFenceShapeType == ConstantInt.RectangleShapeType) { + mFencesMapCommon.let { + val pointA = it.latLngToPotion(FencesPolygonView.POINT_A) + val pointB = it.latLngToPotion(FencesPolygonView.POINT_B) + val pointC = it.latLngToPotion(FencesPolygonView.POINT_C) + val pointD = it.latLngToPotion(FencesPolygonView.POINT_D) + pointA?.apply { + mViewBinding.fencesAddMapRect.apply { + setRectABCDXYPoint(pointA, pointA, pointB!!, pointC!!, pointD!!) + setShowDisDistance( + "${mRectWidthDistance}m", "${mRectHeightDistance}m" + ) + } + } + LogUtil.e("$pointA,$pointB,$pointC,$pointD") + } + } else if (mFenceShapeType == ConstantInt.CircleShapeType) { + val px = mFencesMapCommon.getCircleRadiusPx(mCircleRadiusDistance.toDouble()) + mViewBinding.fencesAddMapCircle.apply { + setCircleRadiusPx(px.toFloat()) + setShowDisDistance("${mCircleRadiusDistance}m") + } + } + + //经纬度转成地址显示,编辑首次进去不需要计算距离 + mFencesMapCommon.getLatLonAddress(mGeoCoderViewModel) + showLoading(false) + } + } + } + + override fun rectRotateClick() { + mFencesMapCommon.refreshDistance() + } + + override fun rectScaleClick() { + mFencesMapCommon.refreshDistance() + } + + override fun rectStartDragClick() { + } + + override fun rectEndDragClick() { + } + + override fun circleScaleClick() { + mFencesMapCommon.refreshDistance() + } + + override fun circleStartDragClick() { + } + + override fun circleEndDragClick() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/AddCreditCardActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/AddCreditCardActivity.kt new file mode 100644 index 0000000..363eda9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/AddCreditCardActivity.kt @@ -0,0 +1,157 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityAddCreditCardBinding +import com.abbidot.tracker.util.Util +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * 添加信用卡 + * card.cardNumber = "4111111111111111" + *card.expirationDate = "08/20" + *card.countryCode = "USA" + */ +class AddCreditCardActivity : + BaseActivity(ActivityAddCreditCardBinding::inflate), + View.OnFocusChangeListener { + + override fun getTopBar() = mViewBinding.addCreditCardBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pay_order_add_card) + + setOnClickListenerViews(mViewBinding.confirmSaveCardBtn) + + mViewBinding.addCreditCardNumberEd.addTextChangedListener { + mViewBinding.addCreditCardCardView.setCardNumber(it.toString()) + } + mViewBinding.addCreditCardNameEd.addTextChangedListener { + val lastName = mViewBinding.addCreditCardLastNameEd.text.toString() + val name = "$lastName " + it.toString() + mViewBinding.addCreditCardCardView.setCardholderName(name) + } + mViewBinding.addCreditCardLastNameEd.addTextChangedListener { + val firstName = mViewBinding.addCreditCardNameEd.text.toString() + val name = it.toString() + " $firstName" + mViewBinding.addCreditCardCardView.setCardholderName(name) + } + mViewBinding.addCreditCardValidityEd.addTextChangedListener { + formatCardExpiry(it.toString()) + } + mViewBinding.addCreditCardCvvEd.addTextChangedListener { + mViewBinding.addCreditCardCardView.setCVV(it.toString()) + } + + mViewBinding.addCreditCardValidityEd.onFocusChangeListener = this + mViewBinding.addCreditCardNameEd.onFocusChangeListener = this + mViewBinding.addCreditCardLastNameEd.onFocusChangeListener = this + mViewBinding.addCreditCardNumberEd.onFocusChangeListener = this + mViewBinding.addCreditCardCvvEd.onFocusChangeListener = this + } + + /** + * 格式化有效期 + */ + private fun formatCardExpiry(cardExpiry: String) { + var formatCardExpiry = cardExpiry + val separator = "/" + if (formatCardExpiry.length == 3) { + formatCardExpiry = if (formatCardExpiry.contains(separator)) { + cardExpiry.substring(0, 2) + } else { + cardExpiry.substring(0, 2) + separator + cardExpiry.last() + } + mViewBinding.addCreditCardValidityEd.apply { + setText(formatCardExpiry) + setSelection(formatCardExpiry.length) + } + } + mViewBinding.addCreditCardCardView.setCardExpiry(formatCardExpiry) + } + + + private fun confirmSaveCard() { + val cardNumber = mViewBinding.addCreditCardNumberEd.text.toString() + val firstName = mViewBinding.addCreditCardNameEd.text.toString() + val lastName = mViewBinding.addCreditCardLastNameEd.text.toString() + val creditCardValidity = mViewBinding.addCreditCardValidityEd.text.toString() + val cvv = mViewBinding.addCreditCardCvvEd.text.toString() + + if (TextUtils.isEmpty(cardNumber) || TextUtils.isEmpty(firstName) || TextUtils.isEmpty( + creditCardValidity) || TextUtils.isEmpty(cvv) || TextUtils.isEmpty(lastName) + ) { + showToast(R.string.txt_not_null) + return + } + + if (!creditCardValidity.contains("/")) { + showToast(R.string.txt_card_validity_format) + return + } else { + val dates = creditCardValidity.split("/") + try { + if (dates[0].toInt() > 12) { + showToast(R.string.txt_card_validity_format) + return + } + } catch (e: Exception) { + showToast(R.string.txt_card_validity_format) + return + } + } + if (Util.getCreditCardType(cardNumber) == 0 || cardNumber.length < 9) { + showToast(R.string.txt_card_number_format) + return + } + + lifecycleScope.launch(Dispatchers.IO) { + val card = CreditCardBean() + card.cardNumber = cardNumber + card.lastName = lastName + card.firstName = firstName + card.expirationDate = creditCardValidity + card.cvv = cvv + MyDatabase.creditCardDao().insert(card) + runOnUiThread { + val intent = Intent() + intent.putExtra(ConstantString.Card, card) + setResult(ResultCode.ResultCode_1, intent) + showToast(R.string.txt_save_successful, isFinish = true) + } + } + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.confirmSaveCardBtn -> { + confirmSaveCard() + } + } + } + + override fun onFocusChange(v: View, hasFocus: Boolean) { + if (hasFocus) { + when (v) { + mViewBinding.addCreditCardNumberEd, mViewBinding.addCreditCardNameEd, mViewBinding.addCreditCardLastNameEd, mViewBinding.addCreditCardValidityEd -> { + mViewBinding.addCreditCardCardView.showFrontAnimation() + } + mViewBinding.addCreditCardCvvEd -> { + mViewBinding.addCreditCardCardView.showBehindAnimation() + } + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentMethodPayPalActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentMethodPayPalActivity.kt new file mode 100644 index 0000000..0dee61a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentMethodPayPalActivity.kt @@ -0,0 +1,391 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.deprecated.adapter.SelectPaymentMethodAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivitySelectPaymentMethodBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.BraintreePalViewModel +import com.abbidot.tracker.diff.PaymentMethodDiffCallBack +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.braintreepayments.api.BraintreeClient +import com.braintreepayments.api.Card +import com.braintreepayments.api.CardClient +import com.braintreepayments.api.PayPalAccountNonce +import com.braintreepayments.api.PayPalClient +import com.braintreepayments.api.PayPalListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * 选择支付方式 + */ +class PaymentMethodPayPalActivity : + BaseActivity(ActivitySelectPaymentMethodBinding::inflate), + PayPalListener { + + private val mBraintreePalViewModel: BraintreePalViewModel by viewModels() + + private lateinit var mSelectPaymentMethodAdapter: SelectPaymentMethodAdapter + + //设备 + private var mRechargeDeviceBean: RechargeDeviceBean? = null + + //套餐 + private var mSetMealBean: SetMealBean? = null + + //已选中的支付方式下标 + private var mSelectPaymentMethodIndex = 0 + + private var mBraintreeClient: BraintreeClient? = null + + //payPal支付 + private var mPayPalClient: PayPalClient? = null + + //信用卡支付 + private var mCardClient: CardClient? = null + + //订单号 + private var mOrderNo = "" + + //0待支付 1是支付成功 2是取消支付/失败 + private var mOrderStatus = 0 + + override fun getTopBar() = mViewBinding.selectPaymentMethodBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pay_order_method) + + intent.extras?.apply { + mRechargeDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.RechargeDevice, RechargeDeviceBean::class.java + ) + mSetMealBean = + Util.getParcelableAdaptive(intent, ConstantString.SetMeal, SetMealBean::class.java) + } + + setData() + + setOnClickListenerViews(mViewBinding.sureSelectPaymentMethodBtn) + } + + private fun setData() { + lifecycleScope.launch { + + val paymentMethodList = mutableListOf() + + withContext(Dispatchers.IO) { + ViewUtil.instance.addMenuBean(paymentMethodList, "", "", R.drawable.ico_lay_paypal) + + val list = MyDatabase.creditCardDao().findAll(MyDatabase.CreditCardTableName) + list?.forEach { + val imageId = Util.getCreditCardType(it.cardNumber) + ViewUtil.instance.addMenuBean( + paymentMethodList, it.cardNumber, it.expirationDate, imageId + ) + } + + ViewUtil.instance.addMenuBean( + paymentMethodList, + getString(R.string.txt_pay_order_add_card), + "", + R.drawable.ico_lay_newcard + ) + } + + mSelectPaymentMethodAdapter = SelectPaymentMethodAdapter(mContext!!, paymentMethodList) + mSelectPaymentMethodAdapter.setOnItemClickListener(object : + BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + selectPaymentMethod(pos) + } + }) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, + mViewBinding.paymentMethodRecyclerView, + mSelectPaymentMethodAdapter, + bottom = 40 + ) + } + + } + + override fun liveDataObserve() { + + mBraintreePalViewModel.mPayPalOrderNoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mOrderNo = it.getOrNull()!!.orderNo + mBraintreePalViewModel.getBraintreeClientToken(this@PaymentMethodPayPalActivity) + } + }) + } + + /** + * 获取Braintree客户端令牌key + */ + mBraintreePalViewModel.mBraintreeClientTokenLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + lifecycleScope.launch { + withContext(Dispatchers.IO) { + if (null == mBraintreeClient) { + //显示支付 UI 之前,使用您的令牌化密钥初始化 SDK + mBraintreeClient = BraintreeClient(mContext, data.tokenId) + } + } + if (null != mBraintreeClient) { + if (mSelectPaymentMethodIndex == 0) { + launchPayPal() + } else { + launchCardPay() + } + } else { + showToast(R.string.txt_pay_fail) + } + } + } + }, showLoading = true) + } + + XEventBus.observe(this, EventName.PayBack) { + finish() + } + + /** + * 更新订单 + */ + mBraintreePalViewModel.mUpdateOrderStatusLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + if (mOrderStatus == 1) { + payPalSuccess() + } else { + payPalFail() + } + } + }) + } + + //信用卡支付成功 + mBraintreePalViewModel.mCardPaymentResultLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + if (!TextUtils.isEmpty(data)) { + payPalSuccess() +// if (data == ErrorCode.CREDIT_CARD_PAY_SUCCESS.toString()) { +// payPalSuccess() +// } else { +// payPalFail() +// } + } + } + }) + } + } + + override fun onNewIntent(newIntent: Intent) { + super.onNewIntent(newIntent) + // required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance" + intent = newIntent + } + + /** + * 选择一种支付方式 + */ + private fun selectPaymentMethod(pos: Int) { + val oldList = mSelectPaymentMethodAdapter.getData() + //处理最后一个选项 直接添跳转加信用卡 + if (pos == oldList.size - 1) { + val intent = Intent(mContext, AddCreditCardActivity::class.java) + launchActivity(mActivityResultLauncher, intent) + return + } + if (oldList[pos].isSwitch) { + return + } + + val newList = mutableListOf() + oldList.forEach { + ViewUtil.instance.addMenuBean( + newList, it.menuName, it.menuValue, it.imageId, isSwitch = it.isSwitch + ) + } + newList[mSelectPaymentMethodIndex].isSwitch = false + newList[pos].isSwitch = true + mSelectPaymentMethodIndex = pos + lifecycleScope.launch { + withContext(Dispatchers.Default) { + mSelectPaymentMethodAdapter.setData(newList) + DiffUtil.calculateDiff(PaymentMethodDiffCallBack(oldList, newList), true) + }.dispatchUpdatesTo(mSelectPaymentMethodAdapter) + } + } + + /** + * 接收添加信用卡返回结果 + */ + private val mActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (null != it.data) { + if (it.resultCode == ResultCode.ResultCode_1) { + val card = Util.getParcelableAdaptive( + it.data!!, ConstantString.Card, CreditCardBean::class.java + ) + if (null != card) { + mBraintreePalViewModel.addCreditCard( + this, card, mSelectPaymentMethodAdapter + ) + } + } + } + } + + + /** + * 确认支付 + */ + private fun surePay() { + val list = mSelectPaymentMethodAdapter.getData() + var selectPaymentMethodIndex = -1 + for (i in 0 until list.size) { + if (list[i].isSwitch) { + selectPaymentMethodIndex = i + break + } + } + + if (selectPaymentMethodIndex == -1) { + showToast(R.string.txt_select_payment_method) + return + } + mSelectPaymentMethodIndex = selectPaymentMethodIndex + + if (mSelectPaymentMethodIndex == 0) { + mBraintreePalViewModel.getPaypalOrderNo( + this, mSetMealBean!!, mRechargeDeviceBean!!.deviceId + ) + } else { + //信用卡支付不需要订单号 + mBraintreePalViewModel.getBraintreeClientToken(this@PaymentMethodPayPalActivity) + } + } + + /** + * 开始PayPal支付 + */ + private fun launchPayPal() { + if (null == mPayPalClient) { + mPayPalClient = PayPalClient(this, mBraintreeClient!!) + mPayPalClient!!.setListener(this) + } + mBraintreePalViewModel.launchPayPal( + this, mBraintreeClient, mPayPalClient, mSetMealBean, mOrderNo + ) + } + + /** + * 开始信用卡支付 + */ + private fun launchCardPay() { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + if (null == mCardClient) { + mCardClient = CardClient(mBraintreeClient!!) + } + } + val creditCardBean = mSelectPaymentMethodAdapter.getData()[mSelectPaymentMethodIndex] + val card = Card() + card.number = creditCardBean.menuName + card.expirationDate = creditCardBean.menuValue + card.countryCode = "USA" + mBraintreePalViewModel.launchCardPay( + this@PaymentMethodPayPalActivity, + mCardClient, + card, + mSetMealBean, + mRechargeDeviceBean!!.deviceId + ) + } + } + + /** + * 支付成功 + */ + private fun payPalSuccess() { + val intent = Intent(mContext!!, PaymentResultActivity::class.java) + intent.putExtra(ConstantString.Amount, mSetMealBean!!.accountFee) + intent.putExtra(ConstantString.Type, ConstantInt.Type1) + startActivityFinish(intent) + } + + /** + * 支付失败 + */ + private fun payPalFail() { + val intent = Intent(mContext!!, PaymentResultActivity::class.java) + intent.putExtra(ConstantString.Type, ConstantInt.Type2) + startActivity(intent) + } + + /** + * PayPal支付回调 + */ + override fun onPayPalSuccess(payPalAccountNonce: PayPalAccountNonce) { + LogUtil.e("PayPal支付成功${payPalAccountNonce}") + mOrderStatus = 1 +// showToast(R.string.txt_pay_success) + mBraintreePalViewModel.updateOrderStatus( + this, mOrderNo, mOrderStatus, mRechargeDeviceBean!!.deviceId, mSetMealBean!!.days + ) + } + + /** + * PayPal支付回调 + */ + override fun onPayPalFailure(error: Exception) { + val message = error.message + mOrderStatus = 2 + LogUtil.e("PayPal支付失败$message") +// if (message?.contains("cancel") == true) { +// showToast(R.string.txt_user_canceled_paypal) +// } else { +// showToast(R.string.txt_pay_fail) +// } + mBraintreePalViewModel.updateOrderStatus( + this, mOrderNo, mOrderStatus, mRechargeDeviceBean!!.deviceId, mSetMealBean!!.days + ) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.sureSelectPaymentMethodBtn -> { + surePay() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentMethodStripeActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentMethodStripeActivity.kt new file mode 100644 index 0000000..df58bff --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentMethodStripeActivity.kt @@ -0,0 +1,485 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.bean.LKSimSetMealBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivitySelectPaymentMethodBinding +import com.abbidot.tracker.deprecated.adapter.SelectPaymentMethodAdapter +import com.abbidot.tracker.deprecated.ui.activity.vm.BraintreePalViewModel +import com.abbidot.tracker.deprecated.ui.activity.vm.StripePalViewModel +import com.abbidot.tracker.diff.PaymentMethodDiffCallBack +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.braintreepayments.api.BraintreeClient +import com.braintreepayments.api.Card +import com.braintreepayments.api.CardClient +import com.braintreepayments.api.PayPalAccountNonce +import com.braintreepayments.api.PayPalClient +import com.braintreepayments.api.PayPalListener +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + * 选择支付方式 + */ +class PaymentMethodStripeActivity : + BaseActivity(ActivitySelectPaymentMethodBinding::inflate), + PayPalListener { + + private val mBraintreePalViewModel: BraintreePalViewModel by viewModels() + private val mStripePalViewModel: StripePalViewModel by viewModels() + + private lateinit var mSelectPaymentMethodAdapter: SelectPaymentMethodAdapter + + //设备 + private var mRechargeDeviceBean: RechargeDeviceBean? = null + + //套餐 + private var mLKSimSetMealBean: LKSimSetMealBean? = null + + //已选中的支付方式下标 + private var mSelectPaymentMethodIndex = 0 + + private var mBraintreeClient: BraintreeClient? = null + + //payPal支付 + private var mPayPalClient: PayPalClient? = null + + //信用卡支付 + private var mCardClient: CardClient? = null + + //订单号 + private var mOrderNo = "" + private var mPartnerCode = "" + + //0待支付 1是支付成功 2是取消支付/失败 + private var mOrderStatus = 0 + + //mealType套餐类型 + private var mSetMealIndex = 0 + + override fun getTopBar() = mViewBinding.selectPaymentMethodBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pay_order_method) + + intent.extras?.apply { + mRechargeDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.RechargeDevice, RechargeDeviceBean::class.java + ) + mLKSimSetMealBean = Util.getParcelableAdaptive( + intent, ConstantString.LkSetMeal, LKSimSetMealBean::class.java + ) + mPartnerCode = getString(ConstantString.PartnerCode).toString() + mSetMealIndex = getInt(ConstantString.SetMealIndex, 0) + + setData() + + setOnClickListenerViews(mViewBinding.sureSelectPaymentMethodBtn) + + //Stripe普通支付 + if (mLKSimSetMealBean!!.specAndPriceList!![0].autoRenewal == "0") { + mStripePalViewModel.init(this@PaymentMethodStripeActivity) + } + } + } + + private fun setData() { + lifecycleScope.launch { + + val paymentMethodList = mutableListOf() + + withContext(Dispatchers.IO) { +// ViewUtil.instance.addMenuBean(paymentMethodList, "", "", R.drawable.ico_lay_paypal) + + val list = MyDatabase.creditCardDao().findAll(MyDatabase.CreditCardTableName) + list?.forEach { + val imageId = Util.getCreditCardType(it.cardNumber) + ViewUtil.instance.addMenuBean( + paymentMethodList, it.cardNumber, it.expirationDate, imageId + ) + } + + ViewUtil.instance.addMenuBean( + paymentMethodList, + getString(R.string.txt_pay_order_add_card), + "", + R.drawable.ico_lay_newcard + ) + } + + mSelectPaymentMethodAdapter = + SelectPaymentMethodAdapter(mContext!!, paymentMethodList, false) + mSelectPaymentMethodAdapter.setOnItemClickListener(object : + BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + selectPaymentMethod(pos) + } + }) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, + mViewBinding.paymentMethodRecyclerView, + mSelectPaymentMethodAdapter, + bottom = 40 + ) + } + + } + + override fun liveDataObserve() { + /*************************Stripe支付************************************/ + mStripePalViewModel.mStripePayOrderNoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val clientSecret = it.getOrNull() + clientSecret?.let { secret -> + mStripePalViewModel.mPaymentIntentClientSecret = secret + launchStripePal() + } + } + }) + } + +// mStripePalViewModel.mStripeSubscriptionCustomerLiveData.observe(this) { +// dealRequestResult(it, object : GetResultCallback { +// override fun onResult(any: Any) { +// val data = it.getOrNull() +// data?.apply { +// mStripePalViewModel.createSubscription( +// this@PaymentMethodStripeActivity, +// mLKSimSetMealBean!!, +// this, +// mRechargeDeviceBean!!, +// mPartnerCode +// ) +// } +// } +// }) +// } + + mStripePalViewModel.mStripeSubscriptionLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + if (!TextUtils.isEmpty(data)) { + payPalSuccess() + } + } + }) + } + + //Stripe支付结果 + mStripePalViewModel.mStripePayResultLiveData.observe(this) { + showLoading(false) + it?.apply { + when (it) { + 0 -> payPalSuccess() + 1 -> payPalFail() + -1 -> { + showToast(R.string.txt_user_canceled_paypal, true) + } + } + } + } + /*************************Stripe支付************************************/ + + mBraintreePalViewModel.mPayPalOrderNoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mOrderNo = it.getOrNull()!!.orderNo + mBraintreePalViewModel.getBraintreeClientToken(this@PaymentMethodStripeActivity) + } + }) + } + + /** + * 获取Braintree客户端令牌key + */ + mBraintreePalViewModel.mBraintreeClientTokenLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + lifecycleScope.launch { + withContext(Dispatchers.IO) { + if (null == mBraintreeClient) { + //显示支付 UI 之前,使用您的令牌化密钥初始化 SDK + mBraintreeClient = BraintreeClient(mContext!!, data.tokenId) + } + } + if (null != mBraintreeClient) { + if (mSelectPaymentMethodIndex == 0) { + launchPayPal() + } else { + launchCardPay() + } + } else { + showToast(R.string.txt_pay_fail) + } + } + } + }, showLoading = true) + } + + XEventBus.observe(this, EventName.PayBack) { + finish() + } + + /** + * 更新订单 + */ + mBraintreePalViewModel.mUpdateOrderStatusLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + if (mOrderStatus == 1) { + payPalSuccess() + } else { + payPalFail() + } + } + }) + } + + //信用卡支付成功 + mBraintreePalViewModel.mCardPaymentResultLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + if (!TextUtils.isEmpty(data)) { + payPalSuccess() +// if (data == ErrorCode.CREDIT_CARD_PAY_SUCCESS.toString()) { +// payPalSuccess() +// } else { +// payPalFail() +// } + } + } + }) + } + } + + override fun onNewIntent(newIntent: Intent) { + super.onNewIntent(newIntent) + // required if your activity's launch mode is "singleTop", "singleTask", or "singleInstance" + intent = newIntent + } + + /** + * 选择一种支付方式 + */ + private fun selectPaymentMethod(pos: Int) { + val oldList = mSelectPaymentMethodAdapter.getData() + //处理最后一个选项 直接添跳转加信用卡 + if (pos == oldList.size - 1) { + val intent = Intent(mContext, AddCreditCardActivity::class.java) + launchActivity(mActivityResultLauncher, intent) + return + } + if (oldList[pos].isSwitch) { + return + } + + val newList = mutableListOf() + oldList.forEach { + ViewUtil.instance.addMenuBean( + newList, it.menuName, it.menuValue, it.imageId, isSwitch = it.isSwitch + ) + } + newList[mSelectPaymentMethodIndex].isSwitch = false + newList[pos].isSwitch = true + mSelectPaymentMethodIndex = pos + //更新选中的某个支付方式 + lifecycleScope.launch { + withContext(Dispatchers.Default) { + mSelectPaymentMethodAdapter.setData(newList) + DiffUtil.calculateDiff(PaymentMethodDiffCallBack(oldList, newList), true) + }.dispatchUpdatesTo(mSelectPaymentMethodAdapter) + } + } + + /** + * 接收添加信用卡返回结果 + */ + private val mActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (null != it.data) { + if (it.resultCode == ResultCode.ResultCode_1) { + val card = Util.getParcelableAdaptive( + it.data!!, ConstantString.Card, CreditCardBean::class.java + ) + if (null != card) { + mBraintreePalViewModel.addCreditCard( + this, card, mSelectPaymentMethodAdapter + ) + } + } + } + } + + + /** + * 确认支付 + */ + private fun surePay() { + val list = mSelectPaymentMethodAdapter.getData() + var selectPaymentMethodIndex = -1 + for (i in 0 until list.size) { + if (list[i].isSwitch) { + selectPaymentMethodIndex = i + break + } + } + + if (selectPaymentMethodIndex == -1) { + showToast(R.string.txt_select_payment_method) + return + } + mSelectPaymentMethodIndex = selectPaymentMethodIndex + + //判断是Stripe普通支付还是自动订阅 + if (mLKSimSetMealBean!!.specAndPriceList!![0].autoRenewal == "0") { + //普通支付 + mStripePalViewModel.getStripePayOrderNo( + this, mLKSimSetMealBean!!, mRechargeDeviceBean!!, mPartnerCode + ) + } else { + //自动订阅 + val card = mSelectPaymentMethodAdapter.getData()[mSelectPaymentMethodIndex] +// mStripePalViewModel.createCustomer(this, card, mSetMealIndex + 1) + } + + +// if (mSelectPaymentMethodIndex == 0) { +// mBraintreePalViewModel.getPaypalOrderNo( +// this, mSetMealBean!!, mRechargeDeviceBean!!.deviceId +// ) +// } else { +// //信用卡支付不需要订单号 +// mBraintreePalViewModel.getBraintreeClientToken(this@PaymentMethodStripeActivity) +// } + } + + /** + * 开始PayPal支付 + */ + private fun launchPayPal() { + if (null == mPayPalClient) { + mPayPalClient = PayPalClient(this, mBraintreeClient!!) + mPayPalClient!!.setListener(this) + } +// mBraintreePalViewModel.launchPayPal( +// this, mBraintreeClient, mPayPalClient, mSetMealBean, mOrderNo +// ) + } + + /** + * 开始信用卡支付 + */ + private fun launchCardPay() { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + if (null == mCardClient) { + mCardClient = CardClient(mBraintreeClient!!) + } + } + val creditCardBean = mSelectPaymentMethodAdapter.getData()[mSelectPaymentMethodIndex] + val card = Card() + card.number = creditCardBean.menuName + card.expirationDate = creditCardBean.menuValue + card.countryCode = "USA" +// mBraintreePalViewModel.launchCardPay( +// this@PaymentMethodStripeActivity, +// mCardClient, +// card, +// mSetMealBean, +// mRechargeDeviceBean!!.deviceId +// ) + } + } + + /** + * 开始Stripe普通支付 + */ + private fun launchStripePal() { + val card = mSelectPaymentMethodAdapter.getData()[mSelectPaymentMethodIndex] + mStripePalViewModel.startCheckout(this, card) + } + + /** + * 支付成功 + */ + private fun payPalSuccess() { + val intent = Intent(mContext!!, PaymentResultActivity::class.java) + intent.putExtra( + ConstantString.Amount, mLKSimSetMealBean!!.specAndPriceList!![0].discountPriceUSD + ) + intent.putExtra(ConstantString.Type, ConstantInt.Type1) + startActivityFinish(intent) + } + + /** + * 支付失败 + */ + private fun payPalFail() { + val intent = Intent(mContext!!, PaymentResultActivity::class.java) + intent.putExtra(ConstantString.Type, ConstantInt.Type2) + startActivity(intent) + } + + /** + * PayPal支付回调 + */ + override fun onPayPalSuccess(payPalAccountNonce: PayPalAccountNonce) { + LogUtil.e("PayPal支付成功${payPalAccountNonce}") + mOrderStatus = 1 +//// showToast(R.string.txt_pay_success) +// mBraintreePalViewModel.updateOrderStatus( +// this, mOrderNo, mOrderStatus, mRechargeDeviceBean!!.deviceId, mSetMealBean!!.days +// ) + } + + /** + * PayPal支付回调 + */ + override fun onPayPalFailure(error: Exception) { + val message = error.message + mOrderStatus = 2 + LogUtil.e("PayPal支付失败$message") +//// if (message?.contains("cancel") == true) { +//// showToast(R.string.txt_user_canceled_paypal) +//// } else { +//// showToast(R.string.txt_pay_fail) +//// } +// mBraintreePalViewModel.updateOrderStatus( +// this, mOrderNo, mOrderStatus, mRechargeDeviceBean!!.deviceId, mSetMealBean!!.days +// ) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.sureSelectPaymentMethodBtn -> { + surePay() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentResultActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentResultActivity.kt new file mode 100644 index 0000000..abf82a7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/PaymentResultActivity.kt @@ -0,0 +1,64 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge + +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityPaymentResultBinding +import com.abbidot.tracker.deprecated.ui.fragment.profile.PaymentFailFragment +import com.abbidot.tracker.deprecated.ui.fragment.profile.PaymentSuccessFragment + +/** + * 付款结果显示 + */ +class PaymentResultActivity : + BaseActivity(ActivityPaymentResultBinding::inflate) { + + var mAmount = 0.0 + + //成功支付的标识 + private var isPaymentSuccess = false + + override fun getTopBar() = mViewBinding.paymentResultBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pay_order_payment_title) + setLeftBackImage(R.drawable.icon_white_back_svg) + + var type = ConstantInt.Type1 + intent.extras?.apply { + type = getInt(ConstantString.Type) + mAmount = getDouble(ConstantString.Amount, 0.0) + } + + if (type == ConstantInt.Type1) { + isPaymentSuccess = true + switchFragment(PaymentSuccessFragment.newInstance(mContext), 0) + } else { + switchFragment(PaymentFailFragment.newInstance(mContext), 1) + } + } + + override fun leftBackOnClick() { + XEventBus.post(EventName.PayBack) + if (isPaymentSuccess) { + XEventBus.post(EventName.UpdateData + "PaymentSuccess") + } + super.leftBackOnClick() + } + + fun switchFragment(fragment: Fragment, index: Int) { + supportFragmentManager.commit { + if (fragment.isAdded) { + show(fragment) + } else { + add(R.id.payment_result_frame, fragment) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeAmountCNActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeAmountCNActivity.kt new file mode 100644 index 0000000..519be7a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeAmountCNActivity.kt @@ -0,0 +1,100 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge.cn + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityRechargeAmountCnBinding +import com.abbidot.tracker.deprecated.adapter.RechargeAmountCNAdapter +import com.abbidot.tracker.deprecated.ui.activity.vm.RechargeAmountViewModel +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + * 国内套餐列表 + */ +class RechargeAmountCNActivity : + BaseActivity(ActivityRechargeAmountCnBinding::inflate) { + + private val mRechargeAmountViewModel: RechargeAmountViewModel by viewModels() + + private lateinit var mRechargeAmountCNAdapter: RechargeAmountCNAdapter + private var mRechargeDeviceBean: RechargeDeviceBean? = null + + override fun getTopBar() = mViewBinding.rechargeAmountCnBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pay_amount) + + //充值设备数据 + mRechargeDeviceBean = Util.getParcelableAdaptive( + intent, + ConstantString.RechargeDevice, + RechargeDeviceBean::class.java + ) + + mRechargeAmountCNAdapter = RechargeAmountCNAdapter(this, null) + mRechargeAmountCNAdapter.setOnItemClickListener(object : + BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + mRechargeAmountCNAdapter.setSelectIndex(pos) + } + }) + ViewUtil.instance.setRecyclerViewGridLayout( + mContext!!, + mViewBinding.rechargeAmountCnRecyclerView, + mRechargeAmountCNAdapter, + 2, + bottom = QMUIDisplayHelper.dpToPx(28), + ) + + setOnClickListenerViews(mViewBinding.btnRechargeAmountCnSubmitOrder) + + mRechargeAmountViewModel.getSetMealList(this) + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.PayBack) { + finish() + } + + mRechargeAmountViewModel.mSetMealListLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + mRechargeAmountCNAdapter.setData(data, true) + } + }) + } + } + + /** + * 提交订单 + */ + private fun submitOrder() { + val index = mRechargeAmountCNAdapter.getSelectIndex() + if (index == -1) { + showToast(R.string.txt_pay_amount) + return + } + val intent = Intent(mContext, RechargeOrderDetailCNActivity::class.java) + intent.putExtra(ConstantString.RechargeDevice, mRechargeDeviceBean) + intent.putExtra(ConstantString.SetMeal, mRechargeAmountCNAdapter.getData()[index]) + startActivity(intent) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnRechargeAmountCnSubmitOrder -> submitOrder() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeDeviceDetailCNActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeDeviceDetailCNActivity.kt new file mode 100644 index 0000000..e02f916 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeDeviceDetailCNActivity.kt @@ -0,0 +1,127 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge.cn + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityRechargeDeviceDetailCnBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.RechargeDeviceViewModel +import com.abbidot.tracker.util.ViewUtil + +/** + * 设备充值国内详情 + */ +class RechargeDeviceDetailCNActivity : BaseActivity( + ActivityRechargeDeviceDetailCnBinding::inflate) { + + private val mRechargeDeviceViewModel: RechargeDeviceViewModel by viewModels() + + private var mDeviceId = "" + + //传递给下个页面需要用的值 + private var mRechargeDeviceBean: RechargeDeviceBean? = null + + override fun getTopBar() = mViewBinding.rechargeDeviceDetailCnBar.titleTopBar + + override fun initData() { + super.initData() + mDeviceId = intent.extras?.getString(ConstantString.DeviceId, "").toString() + setTopBarTitle(mDeviceId) + + ViewUtil.instance.setShapeRadius(mViewBinding.rechargeDeviceDetailCnDevice.root, 6) + + mViewBinding.rechargeDeviceDetailCnHistory.apply { + ViewUtil.instance.showQMUICommonListItemView(mContext!!, this, { + val intent = Intent(mContext, RechargeHistoryCNActivity::class.java) + intent.putExtra(ConstantString.DeviceId, mDeviceId) + startActivity(intent) + }, getString(R.string.txt_card_history)) + detailText = "" + } + + setOnClickListenerViews(mViewBinding.btnRechargeDeviceDetailCnRecharge) + + mRechargeDeviceViewModel.getCards(this) + } + + override fun liveDataObserve() { + mRechargeDeviceViewModel.mDeviceCardLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + setData(data) + } + }) + } + + //充值成功刷新天数 + XEventBus.observe(this, EventName.UpdateData + "PaymentSuccess") { + mRechargeDeviceViewModel.getCards(this) + } + } + + private fun setData(list: MutableList) { + for (i in 0 until list.size) { + val rechargeDeviceBean = list[i] + if (mDeviceId == rechargeDeviceBean.deviceId) { + mRechargeDeviceBean = rechargeDeviceBean + mTopBar?.setTitle(rechargeDeviceBean.deviceOutId) + mViewBinding.rechargeDeviceDetailCnDevice.apply { + itemCardDeviceName.text = rechargeDeviceBean.deviceOutId + itemCardDayNumber.text = rechargeDeviceBean.surplusDays.toString() + val overdueDate = rechargeDeviceBean.overdueDate + val asOfString = if (TextUtils.isEmpty(overdueDate)) { + "" + } else { + String.format(getString(R.string.txt_card_list_end), overdueDate) + } + itemCardAsOf.text = asOfString + if (null == rechargeDeviceBean.pet) { + itemCardPetHead.appHeadImage.visibility = View.GONE + itemCardPetName.visibility = View.GONE + } else { + itemCardPetHead.appHeadImage.let { + it.visibility = View.VISIBLE + ViewUtil.instance.imageLoadUrl(it, rechargeDeviceBean.pet!!.imgurl) + } + itemCardPetName.let { + it.visibility = View.VISIBLE + it.text = rechargeDeviceBean.pet!!.petName + } + } + } + + mViewBinding.rechargeDeviceDetailCnDescribe.let { + if (TextUtils.isEmpty(rechargeDeviceBean.period)) { + rechargeDeviceBean.period = "0" + } + val subscriptionPlanString = + String.format(getString(R.string.txt_surplus_day_how), + rechargeDeviceBean.surplusDays) + it.tvDescriptionSurplusDayContent.text = subscriptionPlanString + it.tvDescriptionExpiryDateContent.text = rechargeDeviceBean.overdueDate + } + break + } + } + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnRechargeDeviceDetailCnRecharge -> { + if (null != mRechargeDeviceBean) { + val intent = Intent(mContext, RechargeAmountCNActivity::class.java) + intent.putExtra(ConstantString.RechargeDevice, mRechargeDeviceBean) + startActivity(intent) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeHistoryCNActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeHistoryCNActivity.kt new file mode 100644 index 0000000..bae8cdf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeHistoryCNActivity.kt @@ -0,0 +1,53 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge.cn + +import androidx.activity.viewModels +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import com.abbidot.tracker.R +import com.abbidot.tracker.deprecated.adapter.RechargeHistoryCNAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityRechargeHistoryCnBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.RechargeHistoryViewModel + +/** + * 国内充值记录 + */ +class RechargeHistoryCNActivity : + BaseActivity(ActivityRechargeHistoryCnBinding::inflate) { + + private val mRechargeHistoryViewModel: RechargeHistoryViewModel by viewModels() + + private lateinit var mRechargeHistoryCNAdapter: RechargeHistoryCNAdapter + + override fun getTopBar() = mViewBinding.rechargeDeviceDetailCnBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_card_history) + + val deviceId = intent.extras?.getString(ConstantString.DeviceId, "").toString() + + mRechargeHistoryCNAdapter = RechargeHistoryCNAdapter(mContext!!, null) + + mViewBinding.rechargeHistoryCnRecyclerView.apply { + layoutManager = LinearLayoutManager(mContext) + addItemDecoration(DividerItemDecoration(mContext, DividerItemDecoration.VERTICAL)) + adapter = mRechargeHistoryCNAdapter + } + + mRechargeHistoryViewModel.getRechargeHistory(this, deviceId) + } + + override fun liveDataObserve() { + mRechargeHistoryViewModel.mRechargeHistoryListLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val list = it.getOrNull()!! + mRechargeHistoryCNAdapter.setData(list, true) + } + }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeOrderDetailCNActivity.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeOrderDetailCNActivity.kt new file mode 100644 index 0000000..f923e5e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/profile/recharge/cn/RechargeOrderDetailCNActivity.kt @@ -0,0 +1,257 @@ +package com.abbidot.tracker.deprecated.ui.activity.profile.recharge.cn + +import android.os.Handler +import android.os.HandlerThread +import android.os.Message +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.alipay.PayResult +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityRechargeOrderDetailCnBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.PayCNViewModel +import com.abbidot.tracker.util.ThirdPartyUtil +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.alipay.sdk.app.PayTask +import com.qmuiteam.qmui.widget.dialog.QMUIBottomSheet +import com.tencent.mm.opensdk.modelpay.PayReq +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + + +/** + * 国内充值订单详情 + */ +class RechargeOrderDetailCNActivity : BaseActivity( + ActivityRechargeOrderDetailCnBinding::inflate +) { + + private val mPayCNViewModel: PayCNViewModel by viewModels() + + //设备 + private var mRechargeDeviceBean: RechargeDeviceBean? = null + + //套餐 + private var mSetMealBean: SetMealBean? = null + private var mQMUIBottomSheet: QMUIBottomSheet? = null + + private val alipaySDKPayFlag = 1 + + //支付方式 + private var mPayWay = 0 + + //支付宝支付子线程 + private lateinit var mHandlerThread: HandlerThread + private lateinit var mHandler: Handler + + override fun getTopBar() = mViewBinding.rechargeOrderDetailCnBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pay_order_title) + + mRechargeDeviceBean = Util.getParcelableAdaptive( + intent, + ConstantString.RechargeDevice, + RechargeDeviceBean::class.java + ) + mSetMealBean = + Util.getParcelableAdaptive(intent, ConstantString.SetMeal, SetMealBean::class.java) + + mViewBinding.apply { + ViewUtil.instance.setShapeRadius(rechargeOrderDetailCnDevice.root, 6) + ViewUtil.instance.setShapeRadius(rechargeOrderDetailCnAmount.root, 6) + rechargeOrderDetailCnPayWay.let { + it.text = getString(R.string.txt_select_payment_method) + it.detailTextView.visibility = View.VISIBLE + it.detailTextView.background = + ContextCompat.getDrawable(mContext!!, R.drawable.pic_wechatpay) + + setOnClickListenerViews(startPayMoneyBtnCn, it) + } + } + + //底部弹出菜单 + val languageList = mutableListOf(R.drawable.pic_wechatpay, R.drawable.pic_alipay) + val bottomListSheetBuilder = + ViewUtil.instance.showBottomSheetList(mContext!!, { dialog, _, position, _ -> + dialog.dismiss() + mPayWay = position + mViewBinding.rechargeOrderDetailCnPayWay.detailTextView.background = + ContextCompat.getDrawable(mContext, languageList[position]) + }) + for (i in 0 until languageList.size) { + bottomListSheetBuilder.addItem(languageList[i], "", "") + } + mQMUIBottomSheet = bottomListSheetBuilder.build() + + setRechargeDeviceData() + setSetMeal() + + initAlipayThread() + } + + /** + * 初始化支付宝支付线程 + */ + private fun initAlipayThread() { + mHandlerThread = HandlerThread("mHandlerThread") + mHandlerThread.start() + //支付宝支付结果回调 + mHandler = Handler(mHandlerThread.looper) { + when (it.what) { + alipaySDKPayFlag -> { + @Suppress("UNCHECKED_CAST") val payResult = + PayResult(it.obj as Map) + //同步返回需要验证的信息 + LogUtil.e("支付宝支付结果回调", payResult.toString()) + mPayCNViewModel.alipayResultCallback( + this, payResult.resultStatus, mSetMealBean!!.accountFee + ) + } + } + true + } + } + + override fun liveDataObserve() { + mPayCNViewModel.mGetWxPayOrderLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + val request = PayReq() + request.appId = appid + request.partnerId = partnerid + request.prepayId = prepayid + request.packageValue = packageStr + request.nonceStr = noncestr + request.timeStamp = timestamp + request.sign = sign + ThirdPartyUtil.mApi.sendReq(request) + } + } + }) + } + + mPayCNViewModel.mGetAlipayOrderLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.let { + //支付宝支付必须异步调用 + lifecycleScope.launch(Dispatchers.IO) { + val alipay = PayTask(mActivity) + //订单信息 + val result = alipay.payV2(data.orderString, true) + val msg = Message() + msg.what = alipaySDKPayFlag + msg.obj = result + mHandler.sendMessage(msg) + } + } + } + }) + } + + //支付成功返回 + XEventBus.observe(this, EventName.PayBack) { + finish() + } + } + + private fun setRechargeDeviceData() { + mRechargeDeviceBean?.apply { + mViewBinding.rechargeOrderDetailCnDevice.let { + it.itemCardDeviceName.text = this.deviceOutId + it.itemCardDayNumber.text = this.surplusDays.toString() + val overdueDate = this.overdueDate + val asOfString = if (TextUtils.isEmpty(overdueDate)) { + "" + } else { + String.format(getString(R.string.txt_card_list_end), overdueDate) + } + it.itemCardAsOf.text = asOfString + + this.pet?.apply { + it.itemCardPetHead.appHeadImage.visibility = View.VISIBLE + it.itemCardPetName.visibility = View.VISIBLE + it.itemCardPetName.text = pet!!.petName + ViewUtil.instance.imageLoadUrl(it.itemCardPetHead.appHeadImage, pet!!.imgurl) + } + } + } + } + + private fun setSetMeal() { + mSetMealBean?.apply { + val str = getString(R.string.txt_month_until) + "\t$accountFee" + mViewBinding.tvNeedPayMoneyCn.text = str + mViewBinding.rechargeOrderDetailCnAmount.let { + if (hotType == ConstantInt.Type1) { + it.rechargeAmountNormalHotImage.visibility = View.VISIBLE + } else { + it.rechargeAmountNormalHotImage.visibility = View.GONE + } + it.rechargeAmountTwoMoney.text = accountFee.toString() + + it.txtRechargeAmountName.text = name + + val perMonth = if (period > 1) { + String.format(getString(R.string.pet_managet_age), period) + } else { + String.format(getString(R.string.pet_managet_age_1), period) + } + it.rechargeAmountTwoTime.text = perMonth + +// val subscriptionPlan = +// String.format(getString(R.string.txt_pay_amount_detail1), subscriptionFee) + it.txtSubscriptionPlanContent.text = "数量:x1" + + val averageMonth = Utils.formatDecimal(accountFee / (12 * period), 2) + val monthString = String.format(getString(R.string.txt_perMonth), averageMonth) + it.rechargeAmountTwoDescription.text = monthString + } + } + } + + /** + * 去支付 + */ + private fun goPay() { + when (mPayWay) { + 0 -> mPayCNViewModel.getWxPayOrder(this, mSetMealBean!!, mRechargeDeviceBean!!.deviceId) + + 1 -> mPayCNViewModel.getAlipayOrder( + this, mSetMealBean!!, mRechargeDeviceBean!!.deviceId + ) + + } + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.startPayMoneyBtnCn -> goPay() + mViewBinding.rechargeOrderDetailCnPayWay -> mQMUIBottomSheet?.show() + } + } + + override fun onDestroy() { + super.onDestroy() + //线程退出释放 + mHandlerThread.quit() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AddPetLabelsViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AddPetLabelsViewModel.kt new file mode 100644 index 0000000..88666a5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AddPetLabelsViewModel.kt @@ -0,0 +1,64 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import android.text.TextUtils +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.bean.LabelBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + *Created by .yzq on 2022/3/3/003. + * @link + * @description: + */ +class AddPetLabelsViewModel : ViewModel() { + + val mPetLabelsLiveData = MutableLiveData>>() + val mLabelListLiveData = MutableLiveData>() + + fun getPetLabels(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getPetLabels() + mPetLabelsLiveData.value = result + } + } + + /** + * 多个标签,隔开的字符串转数组 + */ + fun getLabelData(label: String) { + if (TextUtils.isEmpty(label)) return + val labels = label.split(",") + if (labels.isNotEmpty()) { + val labelList = mutableListOf() + labels.forEach { str -> + val labelBean = LabelBean(str) + labelList.add(labelBean) + } + mLabelListLiveData.value = labelList + } + } + + fun setLabelData(adapter: BaseRecyclerAdapter, newList: MutableList) { + val oldList = adapter.getData() + if (oldList.size == 0) { + adapter.setData(newList, true) + return + } + viewModelScope.launch { + withContext(Dispatchers.Default) { + adapter.setData(newList) + DiffUtil.calculateDiff(BaseDiffCallBack(oldList, newList), true) + }.dispatchUpdatesTo(adapter) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AddTrackerDeviceViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AddTrackerDeviceViewModel.kt new file mode 100644 index 0000000..47bbffc --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AddTrackerDeviceViewModel.kt @@ -0,0 +1,84 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.OrderBean +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/4/9/009. + * @link + * @description:添加绑定设备 + */ +class AddTrackerDeviceViewModel : ViewModel() { + + val mGetMacLiveData = MutableLiveData>() + val mGetIccIdLiveData = MutableLiveData>() + val mActiveDeviceLiveData = MutableLiveData>() + val mUpdatePayDeviceStateLiveData = MutableLiveData>() + val mDeviceOrderStatusLiveData = MutableLiveData>() + + /** + * 获取设备mac地址 + */ + fun getMacId(activity: BaseActivity<*>, deviceOutId: String) { + activity.showLoading(true) + val userId = MMKVUtil.getString(MMKVKey.UserId) + viewModelScope.launch { + val result = NetworkApi.getMacId(userId, deviceOutId) + mGetMacLiveData.value = result + } + } + + /** + * 获取设备iccid + */ + fun getIccId(activity: BaseActivity<*>, deviceOutId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getIccId(deviceOutId) + mGetIccIdLiveData.value = result + } + } + + /** + * 上电激活设备,更新状态 + *@param addStatus 设备添加状态,1 为上电激活成功 + */ + fun activeDevice(activity: BaseActivity<*>, deviceId: String, addStatus: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.updateStatus(deviceId, addStatus) + mActiveDeviceLiveData.value = result + } + } + + /** + * 支付成功后,更新绑定状态 + */ + fun updatePayDeviceState(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.updatePayDeviceState(deviceId, userId) + mUpdatePayDeviceStateLiveData.value = result + } + } + + /** + * 获取设备订单状态 + */ + fun getDeviceOrderStatus(activity: BaseActivity<*>, deviceOutId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getDeviceOrderStatus(deviceOutId) + mDeviceOrderStatusLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AutomaticSubscribeViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AutomaticSubscribeViewModel.kt new file mode 100644 index 0000000..922e7d3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/AutomaticSubscribeViewModel.kt @@ -0,0 +1,43 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PayResultBean +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/26/026. + * @link + * @description:开通/关闭自动订阅,自动续费 + */ +class AutomaticSubscribeViewModel : ViewModel() { + val mCancelSubscribeLiveData = MutableLiveData>() + fun cancelSubscriptions(activity: BaseActivity<*>, rechargeDeviceBean: RechargeDeviceBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.cancelSubscriptions(rechargeDeviceBean.orderId, + rechargeDeviceBean.payType, rechargeDeviceBean.subscriptionsId) + mCancelSubscribeLiveData.value = result + } + } + + val mSubscribeLiveData = MutableLiveData>() + fun subscription(activity: BaseActivity<*>, setMealBean: SetMealBean, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId ) + val username = MMKVUtil.getString(MMKVKey.UserName) + val result = NetworkApi.subscription(setMealBean.name, deviceId, + setMealBean.accountFee.toString(), setMealBean.subscriptionFee.toString(), + setMealBean.period.toString(), userId, username) + mSubscribeLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/BraintreePalViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/BraintreePalViewModel.kt new file mode 100644 index 0000000..36701e3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/BraintreePalViewModel.kt @@ -0,0 +1,250 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.* +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.diff.PaymentMethodDiffCallBack +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.braintreepayments.api.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + *Created by .yzq on 2022/3/28/028. + * @link + * @description:braintree支付 + */ +class BraintreePalViewModel : ViewModel() { + + //是否是沙盒环境 + private val isSandbox = true + + /** + * 标记化密钥 初始化sdk + */ + //沙盒测试 + private val sandboxTokenizationKey = "sandbox_csgnn5ww_dp75dg57ptvqwz9b" + + //生产正式 + private val productionTokenizationKey = "sandbox_csgnn5ww_dp75dg57ptvqwz9b" + + val mBraintreeClientTokenLiveData = MutableLiveData>() + val mPayPalOrderNoLiveData = MutableLiveData>() + val mUpdateOrderStatusLiveData = MutableLiveData>() + + //信用卡支付结果 + val mCardPaymentResultLiveData = MutableLiveData>() + + + fun getTokenizationKey(): String { + if (isSandbox) { + return sandboxTokenizationKey + } + return productionTokenizationKey + } + + + /** + * 显示添加的信用卡 + */ + fun addCreditCard(activity: BaseActivity<*>, + card: CreditCardBean, + adapter: BaseRecyclerAdapter) { + val oldList = adapter.getData() + val newList = mutableListOf() + newList.addAll(oldList) + //判断是否卡号一样 + var isSame = false + for (i in 0 until newList.size) { + val menuTxtBean = newList[i] + if (menuTxtBean.menuName == card.cardNumber) { + isSame = true + menuTxtBean.menuValue = card.expirationDate + break + } + } + if (!isSame) { + newList.removeLast() + val imageId = Util.getCreditCardType(card.cardNumber) + ViewUtil.instance.addMenuBean(newList, card.cardNumber, card.expirationDate, imageId) + ViewUtil.instance.addMenuBean(newList, + activity.getString(R.string.txt_pay_order_add_card), "", + R.drawable.ico_lay_newcard) + } + + viewModelScope.launch { + withContext(Dispatchers.Default) { + adapter.setData(newList) + DiffUtil.calculateDiff(PaymentMethodDiffCallBack(oldList, newList), true) + }.dispatchUpdatesTo(adapter) + } + } + + fun getBraintreeClientToken(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getBraintreeClientToken() + mBraintreeClientTokenLiveData.value = result + } + } + + /** + * 获取订单号 + */ + fun getPaypalOrderNo(activity: BaseActivity<*>, setMealBean: SetMealBean, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { +// val result = +// NetworkApi.getPaypalOrderNo(setMealBean.mealPackage, setMealBean.packageType, +// setMealBean.cost.toString(), deviceId) +// mPayPalOrderNoLiveData.value = result + } + } + + /** + *@param orderStatus:0待支付 1是支付成功 2是取消支付/失败 + */ + fun updateOrderStatus(activity: BaseActivity<*>, + orderId: String, + orderStatus: Int,//0待支付 1是支付成功 2是取消支付/失败 + deviceId: String, + mealDays: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.updateOrderStatus(orderId, orderStatus, deviceId, mealDays) + mUpdateOrderStatusLiveData.value = result + } + } + + /** + * 信用卡发起支付 + */ + fun cardPayment(activity: BaseActivity<*>, + mealName: String, + mealDays: Int, + totalCost: String, + paymentMethodNonce: String, + deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = + NetworkApi.cardPayment(mealName, mealDays, totalCost, paymentMethodNonce, deviceId) + mCardPaymentResultLiveData.value = result + } + } + + /** + * 信用卡订阅支付 + */ + private fun cardSubscription(activity: BaseActivity<*>, + userId: String, + paymentMethodNonce: String, + firstName: String, + lastName: String, + username: String, + deviceId: String, + setMealBean: SetMealBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = + NetworkApi.cardSubscription(userId, paymentMethodNonce, firstName, lastName, + username, deviceId, setMealBean) + mCardPaymentResultLiveData.value = result + } + } + + + /** + * 开始调起payPal支付 + */ + fun launchPayPal(activity: BaseActivity<*>, + braintreeClient: BraintreeClient?, + payPalClient: PayPalClient?, + setMealBean: SetMealBean?, + orderNo: String) { + activity.showLoading(true) + viewModelScope.launch(Dispatchers.IO) { + braintreeClient?.getConfiguration { _, _ -> + payPalClient?.apply { +// val payPalCheckoutRequest = PayPalCheckoutRequest("${setMealBean!!.cost}") +// val payPalLineItem = +// PayPalLineItem(PayPalLineItem.KIND_DEBIT, setMealBean.mealPackage, "1", +// "${setMealBean.cost}") +// payPalLineItem.setProductCode(orderNo) +// val payPalLineItems = ArrayList() +// payPalLineItems.add(payPalLineItem) +// payPalCheckoutRequest.currencyCode = "USD" +// payPalCheckoutRequest.setLineItems(payPalLineItems) +// tokenizePayPalAccount(activity, payPalCheckoutRequest) + } + } + } + } + + /** + * 开始调起信用卡支付,自动订阅 + */ + fun launchCardPay(activity: BaseActivity<*>, + cardClient: CardClient?, + card: Card, + setMealBean: SetMealBean?, + deviceId: String) { + if (null == setMealBean) return + + activity.apply { + if (!isShowLoading()) { + showLoading(true) + } + } + + val callback = CardTokenizeCallback { cardNonce, error -> + LogUtil.e("调起信用卡支付error=${error?.message}") + if (null != cardNonce) { + viewModelScope.launch { + val nonce = cardNonce.string + LogUtil.e("cardNonce:$nonce") +// activity.showLoading(true) +// cardPayment(activity, setMealBean.mealPackage, setMealBean.days, +// setMealBean.cost.toString(), nonce, deviceId) + var list: List? + withContext(Dispatchers.IO) { + list = MyDatabase.creditCardDao().findAll(MyDatabase.CreditCardTableName) + } + if (null != list) { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val userName = MMKVUtil.getString(MMKVKey.UserName) + for (creditCard in list!!) { + if (card.number == creditCard.cardNumber) { + //信用卡订阅支付 + cardSubscription(activity, userId, nonce, creditCard.firstName, + creditCard.lastName, userName, deviceId, setMealBean) + break + } + } + } + } + } else { + activity.apply { + showToast(R.string.txt_pay_fail) + showLoading(false) + } + } + } + + viewModelScope.launch(Dispatchers.IO) { + cardClient?.tokenize(card, callback) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/ChangePasswordViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/ChangePasswordViewModel.kt new file mode 100644 index 0000000..88ddf68 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/ChangePasswordViewModel.kt @@ -0,0 +1,29 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/24/024. + * @link + * @description: + */ +class ChangePasswordViewModel : ViewModel() { + + val mResetPasswordLiveData = MutableLiveData>() + + /** + * 重置密码 + */ + fun resetPassword(activity: BaseActivity<*>, email: String, password: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.resetPassword(email, password) + mResetPasswordLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/LedLightViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/LedLightViewModel.kt new file mode 100644 index 0000000..3ae47ac --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/LedLightViewModel.kt @@ -0,0 +1,33 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/4/9/009. + * @link + * @description: + */ +class LedLightViewModel : ViewModel() { + val mSetLedLightLiveData = MutableLiveData>() + + /** + * 设置light灯光 + */ + fun setLedLight(activity: BaseActivity<*>, mapDeviceBean: MapDeviceBean) { + activity.showLoading(true) + mapDeviceBean.apply { + viewModelScope.launch { + val result = NetworkApi.setLedLight( + deviceServerId, ledRedValue, ledGreenValue, ledBlueValue, ledMode, ledSwitch + ) + mSetLedLightLiveData.value = result + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/NotificationsViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/NotificationsViewModel.kt new file mode 100644 index 0000000..37fe92b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/NotificationsViewModel.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MessageBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/2/24/024. + * @link + * @description: + */ +class NotificationsViewModel : ViewModel() { + + val mNotificationsLiveData = MutableLiveData>>() + val mDeleteMessageLiveData = MutableLiveData>() + + fun getMessageList(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getMessageList(userId) + mNotificationsLiveData.value = result + } + } + + fun deleteMessage(activity: BaseActivity<*>, messageIds: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.deleteMessage(messageIds) + mDeleteMessageLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/PayCNViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/PayCNViewModel.kt new file mode 100644 index 0000000..42c1116 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/PayCNViewModel.kt @@ -0,0 +1,80 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.AlipayOrderBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.bean.WXPayOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.deprecated.ui.activity.profile.recharge.PaymentResultActivity +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/7/29/029. + * @link + * @description: + */ +class PayCNViewModel : ViewModel() { + + val mGetWxPayOrderLiveData = MutableLiveData>() + val mGetAlipayOrderLiveData = MutableLiveData>() + + /** + * 获取微信支付Prepay_id + */ + fun getWxPayOrder(activity: BaseActivity<*>, setMealBean: SetMealBean, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val userName = MMKVUtil.getString(MMKVKey.UserName) + val result = NetworkApi.getWxPayOrder(setMealBean, deviceId, userId, userName) + mGetWxPayOrderLiveData.value = result + } + } + + /** + * 获取支付宝支付订单 + */ + fun getAlipayOrder(activity: BaseActivity<*>, setMealBean: SetMealBean, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val userName = MMKVUtil.getString(MMKVKey.UserName) + val result = NetworkApi.getAlipayOrder(setMealBean, deviceId, userId, userName) + mGetAlipayOrderLiveData.value = result + } + } + + /** + * 支付宝支付回调 + */ + fun alipayResultCallback(activity: BaseActivity<*>, resultStatus: String?, price: Double) { + //返回参数说明:https://opendocs.alipay.com/open/204/105302 + when (resultStatus) { + "9000" -> { + activity.showToast(R.string.txt_pay_success) + //支付成功 + LogUtil.e("WXPayEntryActivity", "支付成功") + val intent = Intent(activity, PaymentResultActivity::class.java) + intent.putExtra(ConstantString.Amount, price) + intent.putExtra(ConstantString.Type, ConstantInt.Type1) + activity.startActivity(intent) + } + //"8000"代表支付结果因为支付渠道原因或者系统原因还在等待支付结果确认,最终交易是否成功以服务端异步通知为准 + "8000" -> activity.showToast("支付结果确认中") + "6001" -> activity.showToast(R.string.txt_cancel_pay) + "6002" -> activity.showToast("网络异常") + "5000" -> activity.showToast("重复请求") + else -> activity.showToast(R.string.txt_pay_fail) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeAmountViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeAmountViewModel.kt new file mode 100644 index 0000000..ce124f5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeAmountViewModel.kt @@ -0,0 +1,42 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.LKSimListBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/26/026. + * @link + * @description: + */ +class RechargeAmountViewModel : ViewModel() { + val mSetMealListLiveData = MutableLiveData>>() + val mLKSimSetMealListLiveData = MutableLiveData>() + + /** + * 获取套餐列表 + */ + fun getSetMealList(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getSetMealList() + mSetMealListLiveData.value = result + } + } + + /** + * 获取sim卡餐列表 + */ + fun getSimSetMealList(activity: BaseActivity<*>, iccId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getSimSetMealList(iccId, "CN00000827") + mLKSimSetMealListLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeDeviceViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeDeviceViewModel.kt new file mode 100644 index 0000000..2a16dc6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeDeviceViewModel.kt @@ -0,0 +1,29 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/25/025. + * @link + * @description:可以充值的设备 + */ +class RechargeDeviceViewModel : ViewModel() { + val mDeviceCardLiveData = MutableLiveData>>() + + fun getCards(activity: BaseActivity<*>) { + activity.showLoading(true) + val userId = MMKVUtil.getString(MMKVKey.UserId) + viewModelScope.launch { + val result = NetworkApi.getCards(userId) + mDeviceCardLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeHistoryViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeHistoryViewModel.kt new file mode 100644 index 0000000..ae9299b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/RechargeHistoryViewModel.kt @@ -0,0 +1,26 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.RechargeHistoryBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/26/026. + * @link + * @description: + */ +class RechargeHistoryViewModel : ViewModel() { + val mRechargeHistoryListLiveData = MutableLiveData>>() + + fun getRechargeHistory(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getRechargeHistory(deviceId) + mRechargeHistoryListLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/StripePalViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/StripePalViewModel.kt new file mode 100644 index 0000000..305a15a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/activity/vm/StripePalViewModel.kt @@ -0,0 +1,223 @@ +package com.abbidot.tracker.deprecated.ui.activity.vm + +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.LKSimSetMealBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.bean.StripeSubscriptionPriceBean +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.retrofit2.NetworkApi +import com.stripe.android.PaymentConfiguration +import com.stripe.android.model.CardParams +import com.stripe.android.model.ConfirmPaymentIntentParams +import com.stripe.android.model.PaymentMethodCreateParams +import com.stripe.android.payments.paymentlauncher.PaymentLauncher +import com.stripe.android.payments.paymentlauncher.PaymentResult +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + + +/** + *Created by .yzq on 2023/7/14/014. + * @link + * @description:Stripe支付 + */ +class StripePalViewModel : ViewModel() { + //服务器返回的支付凭证 + var mPaymentIntentClientSecret = "" + private lateinit var mPaymentLauncher: PaymentLauncher + + //获取支付凭证 + val mStripePayOrderNoLiveData = MutableLiveData>() + val mStripeSubscriptionLiveData = MutableLiveData>() + + //支付结果 + val mStripePayResultLiveData = MutableLiveData() + + /** + * Stripe支付初始化 + */ + fun init(activity: AppCompatActivity) { + val paymentConfiguration = PaymentConfiguration.getInstance(activity.applicationContext) + mPaymentLauncher = PaymentLauncher.create( + activity, + paymentConfiguration.publishableKey, + paymentConfiguration.stripeAccountId, + ::onPaymentResult + ) + } + + /** + * 开始提交Stripe支付 + */ + fun startCheckout(activity: BaseActivity<*>, card: MenuTxtBean) { + activity.showLoading(true) + viewModelScope.launch(Dispatchers.IO) { + val list = MyDatabase.creditCardDao().findAll(MyDatabase.CreditCardTableName) + if (null != list) { + for (it in list) { + if (card.menuName == it.cardNumber) { + val dates = it.expirationDate.split("/") + val name = "${it.lastName} ${it.firstName}" + val params = PaymentMethodCreateParams.createCard( + CardParams( + it.cardNumber, dates[0].toInt(), dates[1].toInt(), it.cvv, name + ) + ) + val confirmParams = + ConfirmPaymentIntentParams.createWithPaymentMethodCreateParams( + params, mPaymentIntentClientSecret + ) + mPaymentLauncher.confirm(confirmParams) + break + } + } + } + } + } + + /** + * Stripe支付结果 + */ + private fun onPaymentResult(paymentResult: PaymentResult) { + var message = "" + val result = when (paymentResult) { + is PaymentResult.Completed -> { + message = "Completed!" + 0 + } + + is PaymentResult.Canceled -> { + message = "Canceled!" + -1 + } + + is PaymentResult.Failed -> { + // This string comes from the PaymentIntent's error message. + // See here: https://stripe.com/docs/api/payment_intents/object#payment_intent_object-last_payment_error-message + message = "Failed: " + paymentResult.throwable.message + 1 + } + } + LogUtil.e("Stripe支付结果:$message") + mStripePayResultLiveData.value = result + } + + /** + * Stripe支付创建订阅 + */ + fun createSubscription( + activity: BaseActivity<*>, + lkSimSetMealBean: LKSimSetMealBean, + stripeSubscriptionPriceBean: StripeSubscriptionPriceBean, + rechargeDeviceBean: RechargeDeviceBean, + partnerCode: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val userName = MMKVUtil.getString(MMKVKey.UserName) + lkSimSetMealBean.let { + it.specAndPriceList?.run { + val spec = get(0) + val amount = (spec.discountPriceUSD * 100).toInt() + val result = NetworkApi.createSubscription( + userId, + it.servicePlanShowName, + rechargeDeviceBean.deviceId, + userName, + spec.discountPriceUSD.toString(), + stripeSubscriptionPriceBean.priceId, + stripeSubscriptionPriceBean.customerId, + amount.toString(), + partnerCode, + it.packageCode, + it.servicePlanCode, + spec.orderPeriod, + it.servicePlanShowName, + rechargeDeviceBean.iccid, + spec.autoRenewal + ) + mStripeSubscriptionLiveData.value = result + } + + } + } + } + + /** + * Stripe支付取消订阅 + */ + fun cancelStripeSubscription( + activity: BaseActivity<*>, mRechargeDeviceBean: RechargeDeviceBean + ) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.cancelStripeSubscription( + mRechargeDeviceBean.orderId, + mRechargeDeviceBean.subscriptionsId, + mRechargeDeviceBean.iccid + ) + mStripePayOrderNoLiveData.value = result + } + } + + /** + * Stripe支付 Rsa算法解密测试 + */ + fun rsaDecodeTest(encryptedData: String) { + viewModelScope.launch { + NetworkApi.rsaDecodeTest(encryptedData) + } + } + + /** + * 获取Stripe支付凭证 + * priceId 套餐的产品ID(订阅时传) + */ + fun getStripePayOrderNo( + activity: BaseActivity<*>, + lkSimSetMealBean: LKSimSetMealBean, + rechargeDeviceBean: RechargeDeviceBean, + partnerCode: String, + priceId: String = "" + ) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val username = MMKVUtil.getString(MMKVKey.UserName) + lkSimSetMealBean.let { + it.specAndPriceList?.run { + val spec = get(0) + val amount = (spec.discountPriceUSD * 100).toInt() + val result = NetworkApi.getStripePayOrder( + amount.toString(), + userId, + it.servicePlanShowName, + rechargeDeviceBean.deviceId, + username, + spec.discountPriceUSD.toString(), + priceId, + partnerCode, + it.packageCode, + it.servicePlanCode, + spec.orderPeriod, + it.servicePlanShowName, + rechargeDeviceBean.iccid, + spec.autoRenewal + ) + mStripePayOrderNoLiveData.value = result + } + + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/forgot/ForgetPasswordCNFinishFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/forgot/ForgetPasswordCNFinishFragment.kt new file mode 100644 index 0000000..08decbb --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/forgot/ForgetPasswordCNFinishFragment.kt @@ -0,0 +1,98 @@ +package com.abbidot.tracker.deprecated.ui.fragment.forgot + +import android.content.Context +import android.graphics.Paint +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentForgetPasswordCnFinishBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.RegisterCNViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + + +/** + * 国内忘记密码-设置新密码 + */ +class ForgetPasswordCNFinishFragment : BaseFragment( + FragmentForgetPasswordCnFinishBinding::inflate +) { + + private val mRegisterCNViewModel: RegisterCNViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = ForgetPasswordCNFinishFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + + val activity = activity as LoginRegisterCNActivity + activity.apply { + if (mType != ConstantInt.Close) { + mViewBinding.setNewPasswordReturnLoginCn.visibility = View.GONE + } + setTopTitle(getString(R.string.txt_set_new_password)) + setTopContent("") + } + + mViewBinding.apply { + //添加下划线 + setNewPasswordReturnLoginCn.paint.flags = Paint.UNDERLINE_TEXT_FLAG + setOnClickListenerViews(btnFinishSetNewPassword, setNewPasswordReturnLoginCn) + } + } + + override fun liveDataObserve() { + mRegisterCNViewModel.mSetNewPasswordResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + lifecycleScope.launch { + val activity = activity as LoginRegisterCNActivity + if (activity.mType != ConstantInt.Close) { + withContext(Dispatchers.IO) { + MMKVUtil.clearAll() + } + XEventBus.post(EventName.ChangePassword) + } + showToast(R.string.txt_change_successful) + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + } + }) + } + } + + private fun setNewPassword() { + val password = mViewBinding.setNewCnPasswordEd.text.toString() + if (TextUtils.isEmpty(password)) { + showToast(R.string.txt_not_null) + return + } + val activity = activity as LoginRegisterCNActivity + mRegisterCNViewModel.resetNewPassword(this, activity.mPhone, password) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnFinishSetNewPassword -> setNewPassword() + mViewBinding.setNewPasswordReturnLoginCn -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/forgot/ForgetPasswordCNStartFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/forgot/ForgetPasswordCNStartFragment.kt new file mode 100644 index 0000000..1589acc --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/forgot/ForgetPasswordCNStartFragment.kt @@ -0,0 +1,139 @@ +package com.abbidot.tracker.deprecated.ui.fragment.forgot + +import android.content.Context +import android.graphics.Paint +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentForgetPasswordCnStartBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.RegisterCNViewModel +import com.abbidot.tracker.vm.CountDownTimerViewModel + + +/** + * 国内忘记密码 + */ +class ForgetPasswordCNStartFragment : BaseFragment( + FragmentForgetPasswordCnStartBinding::inflate +) { + + private val mRegisterCNViewModel: RegisterCNViewModel by viewModels() + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = ForgetPasswordCNStartFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + mViewBinding.apply { + //添加下划线 + forgetPasswordReturnLoginCn.paint.flags = Paint.UNDERLINE_TEXT_FLAG + + setOnClickListenerViews( + btnForgetPasswordCnNext, + llForgetPasswordGetCode.btnGetCode, + forgetPasswordReturnLoginCn + ) + } + + val activity = activity as LoginRegisterCNActivity + activity.apply { + if (mType == ConstantInt.Close) { + setTopTitle(getString(R.string.txt_find_password)) + } else { + setTopTitle(getString(R.string.txt_change_pwd)) + mViewBinding.let { + val phone = MMKVUtil.getString(MMKVKey.Phone) + it.llForgetPasswordGetCode.etGetCodePhone.setText(phone) + it.llForgetPasswordGetCode.etGetCodePhone.isFocusable = false + it.forgetPasswordReturnLoginCn.visibility = View.GONE + } + } + setTopContent("") + } + } + + override fun liveDataObserve() { + mRegisterCNViewModel.mSendCodeResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mCountDownTimerViewModel.startCountDown(60) + showToast(R.string.txt_code_success) + } + }) + } + + //倒计时 + mCountDownTimerViewModel.mCountDowningLiveData.observe(viewLifecycleOwner) { + mViewBinding.llForgetPasswordGetCode.btnGetCode.apply { + isEnabled = false + text = "${it}s" + } + } + mCountDownTimerViewModel.mCountDownEndLiveData.observe(viewLifecycleOwner) { + mViewBinding.llForgetPasswordGetCode.btnGetCode.apply { + isEnabled = true + text = getString(R.string.register_txt_get_code) + } + } + + mRegisterCNViewModel.mValidateCodeResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Forget_Password_Finish) + } + }) + } + } + + /** + * 忘记密码下一步 + */ + private fun forgetPasswordNext() { + val phone = mViewBinding.llForgetPasswordGetCode.etGetCodePhone.text.toString() + val code = mViewBinding.llForgetPasswordGetCode.etPhoneCode.text.toString() + if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(code)) { + showToast(R.string.txt_not_null) + return + } + val activity = activity as LoginRegisterCNActivity + activity.mPhone = phone + mRegisterCNViewModel.validatePhoneCode(this, phone, code, 4) + } + + /** + * 获取验证码 + */ + private fun getPhoneCode() { + val phone = mViewBinding.llForgetPasswordGetCode.etGetCodePhone.text.toString() + if (TextUtils.isEmpty(phone)) { + showToast(R.string.txt_register_phone) + return + } + mRegisterCNViewModel.sendCode(this, phone, 4) + } + + override fun onClick(v: View?) { + when (v) { + mViewBinding.btnForgetPasswordCnNext -> forgetPasswordNext() + mViewBinding.forgetPasswordReturnLoginCn -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + + mViewBinding.llForgetPasswordGetCode.btnGetCode -> getPhoneCode() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/BindPhoneCNFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/BindPhoneCNFragment.kt new file mode 100644 index 0000000..21e4d1b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/BindPhoneCNFragment.kt @@ -0,0 +1,114 @@ +package com.abbidot.tracker.deprecated.ui.fragment.login + +import android.content.Context +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentBindPhoneCnBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.LoginCNViewModel +import com.abbidot.tracker.deprecated.ui.fragment.vm.RegisterCNViewModel +import com.abbidot.tracker.vm.CountDownTimerViewModel + + +/** + * 绑定手机号 + */ +class BindPhoneCNFragment : + BaseFragment(FragmentBindPhoneCnBinding::inflate) { + + private val mRegisterCNViewModel: RegisterCNViewModel by viewModels() + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + private val mLoginCNViewModel: LoginCNViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = BindPhoneCNFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + val activity = activity as LoginRegisterCNActivity + activity.setTopTitle(getString(R.string.login_txt_login_first)) + activity.setTopContent(getString(R.string.login_txt_login_bind_phone)) + + mViewBinding.apply { + setOnClickListenerViews(llBindPhoneGetPhoneCode.btnGetCode, btnBindBindPhoneCn) + } + } + + override fun liveDataObserve() { + mRegisterCNViewModel.mSendCodeResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mCountDownTimerViewModel.startCountDown(60) + showToast(R.string.txt_code_success) + } + }) + } + + //倒计时 + mCountDownTimerViewModel.mCountDowningLiveData.observe(viewLifecycleOwner) { + mViewBinding.llBindPhoneGetPhoneCode.btnGetCode.apply { + isEnabled = false + text = "${it}s" + } + } + mCountDownTimerViewModel.mCountDownEndLiveData.observe(viewLifecycleOwner) { + mViewBinding.llBindPhoneGetPhoneCode.btnGetCode.apply { + isEnabled = true + text = getString(R.string.register_txt_get_code) + } + } + + mLoginCNViewModel.mWXBindResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + val activity = activity as LoginRegisterCNActivity + mLoginCNViewModel.loginSuccess(activity, this, R.string.txt_bind_success) + } + } + }) + } + } + + /** + * 获取验证码 + */ + private fun getPhoneCode() { + val phone = mViewBinding.llBindPhoneGetPhoneCode.etGetCodePhone.text.toString() + if (TextUtils.isEmpty(phone)) { + showToast(R.string.txt_register_phone) + return + } + mRegisterCNViewModel.sendCode(this, phone, 2) + } + + /** + *微信绑定手机号 + */ + private fun wxBindPhone() { + val phone = mViewBinding.llBindPhoneGetPhoneCode.etGetCodePhone.text.toString() + val code = mViewBinding.llBindPhoneGetPhoneCode.etPhoneCode.text.toString() + if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(code)) { + showToast(R.string.txt_not_null) + return + } + val activity = activity as LoginRegisterCNActivity + mLoginCNViewModel.wxBind(this, phone, code, activity.mWXId) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.llBindPhoneGetPhoneCode.btnGetCode -> getPhoneCode() + mViewBinding.btnBindBindPhoneCn -> wxBindPhone() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNAccountFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNAccountFragment.kt new file mode 100644 index 0000000..4ea147b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNAccountFragment.kt @@ -0,0 +1,152 @@ +package com.abbidot.tracker.deprecated.ui.fragment.login + +import android.content.Context +import android.graphics.Paint +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentLoginCnAccountBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.LoginCNViewModel +import com.abbidot.tracker.util.ThirdPartyUtil +import com.abbidot.tracker.util.ViewUtil + +/** + * 国内帐号登录 + */ +class LoginCNAccountFragment : + BaseFragment(FragmentLoginCnAccountBinding::inflate) { + + private val mLoginCNViewModel: LoginCNViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = LoginCNAccountFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + + mViewBinding.apply { + //添加下划线 + loginCnAccountSignUp.paint.flags = Paint.UNDERLINE_TEXT_FLAG + loginCnAccountForgetPassword.paint.flags = Paint.UNDERLINE_TEXT_FLAG + + setOnClickListenerViews( + btnLoginCnAccountCode, + btnLoginCnWx, + btnStartLoginCnAccount, + loginCnAccountSignUp, + loginCnAccountForgetPassword, + llLoginCnAccountAgree.loginCnPolicy, + llLoginCnAccountAgree.loginCnTerms + ) + } + } + + override fun liveDataObserve() { + //微信登录成功返回 + XEventBus.observe(this, EventName.WXLoginResult) { it: Result -> + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + LogUtil.e("LoginCNAccountFragment微信登录成功返回,页面可见$isVisible") + if (!isVisible) { + return + } + val data = it.getOrNull() + data?.apply { + val activity = activity as LoginRegisterCNActivity + mLoginCNViewModel.loginSuccess(activity, this) + } + } + }) + } + + mLoginCNViewModel.mAccountPwdLoginResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + val activity = activity as LoginRegisterCNActivity + mLoginCNViewModel.loginSuccess(activity, this) + } + } + }) + } + } + + /** + * 微信登录 + */ + private fun wechatLogin() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llLoginCnAccountAgree.loginAgreePrivacyCheckbox)) { + return + } + ThirdPartyUtil.instance.startWXLogin(activity) + } + + /** + *账号密码登录 + */ + private fun accountPwdLogin() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llLoginCnAccountAgree.loginAgreePrivacyCheckbox)) { + return + } + var phone = mViewBinding.loginCnPhoneEd.text.toString() + var pwd = mViewBinding.loginCnPasswordEd.text.toString() + if (AppUtils.isDebug()) { + if (TextUtils.equals("12", phone)) { + phone = "13049437653" + pwd = "123456" + } + } + if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(pwd)) { + showToast(R.string.txt_not_null) + return + } + + mLoginCNViewModel.accountPasswordLogin(this, phone, pwd) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.loginCnAccountSignUp -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Register_Start) + } + + mViewBinding.loginCnAccountForgetPassword -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Forget_Password_Start) + } + + mViewBinding.btnStartLoginCnAccount -> accountPwdLogin() + mViewBinding.btnLoginCnWx -> wechatLogin() + + mViewBinding.btnLoginCnAccountCode -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Code_Login) + } + + mViewBinding.llLoginCnAccountAgree.loginCnPolicy -> { + ViewUtil.instance.goPrivacyPolicy(mActivity!!) + } + + mViewBinding.llLoginCnAccountAgree.loginCnTerms -> { + ViewUtil.instance.goServiceContract(mActivity!!) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNCodeFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNCodeFragment.kt new file mode 100644 index 0000000..ccbb5b0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNCodeFragment.kt @@ -0,0 +1,144 @@ +package com.abbidot.tracker.deprecated.ui.fragment.login + +import android.content.Context +import android.graphics.Paint +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentLoginCnCodeBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.LoginCNViewModel +import com.abbidot.tracker.deprecated.ui.fragment.vm.RegisterCNViewModel +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.CountDownTimerViewModel + + +/** + * 国内验证码登录 + */ +class LoginCNCodeFragment : + BaseFragment(FragmentLoginCnCodeBinding::inflate) { + + private val mRegisterCNViewModel: RegisterCNViewModel by viewModels() + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + private val mLoginCNViewModel: LoginCNViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = LoginCNCodeFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + + mViewBinding.apply { + //添加下划线 + loginCnCodeUserPassword.paint.flags = Paint.UNDERLINE_TEXT_FLAG + + setOnClickListenerViews( + loginCnCodeUserPassword, + btnStartLoginCnCode, + llLoginCnCodeSendCode.btnGetCode, + llLoginCnCodeAgree.loginCnTerms, + llLoginCnCodeAgree.loginCnPolicy + ) + } + } + + override fun liveDataObserve() { + mRegisterCNViewModel.mSendCodeResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mCountDownTimerViewModel.startCountDown(60) + showToast(R.string.txt_code_success) + } + }) + } + + //倒计时 + mCountDownTimerViewModel.mCountDowningLiveData.observe(viewLifecycleOwner) { + mViewBinding.llLoginCnCodeSendCode.btnGetCode.apply { + isEnabled = false + text = "${it}s" + } + } + mCountDownTimerViewModel.mCountDownEndLiveData.observe(viewLifecycleOwner) { + mViewBinding.llLoginCnCodeSendCode.btnGetCode.apply { + isEnabled = true + text = getString(R.string.register_txt_get_code) + } + } + + mLoginCNViewModel.mPhoneLoginResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + val activity = activity as LoginRegisterCNActivity + mLoginCNViewModel.loginSuccess(activity, this) + } + } + }) + } + } + + /** + * 验证码登录 + */ + private fun codeLogin() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llLoginCnCodeAgree.loginAgreePrivacyCheckbox)) { + return + } + + val phone = mViewBinding.llLoginCnCodeSendCode.etGetCodePhone.text.toString() + val code = mViewBinding.llLoginCnCodeSendCode.etPhoneCode.text.toString() + if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(code)) { + showToast(R.string.txt_not_null) + return + } + + mLoginCNViewModel.phoneLogin(this, phone, code) + } + + /** + * 获取验证码 + */ + private fun getPhoneCode() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llLoginCnCodeAgree.loginAgreePrivacyCheckbox)) { + return + } + + val phone = mViewBinding.llLoginCnCodeSendCode.etGetCodePhone.text.toString() + if (TextUtils.isEmpty(phone)) { + showToast(R.string.txt_register_phone) + return + } + mRegisterCNViewModel.sendCode(this, phone, 1) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.loginCnCodeUserPassword -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + + mViewBinding.btnStartLoginCnCode -> codeLogin() + mViewBinding.llLoginCnCodeSendCode.btnGetCode -> getPhoneCode() + mViewBinding.llLoginCnCodeAgree.loginCnPolicy -> { + ViewUtil.instance.goPrivacyPolicy(mActivity!!) + } + + mViewBinding.llLoginCnCodeAgree.loginCnTerms -> { + ViewUtil.instance.goServiceContract(mActivity!!) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNFirstFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNFirstFragment.kt new file mode 100644 index 0000000..fe33e28 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/login/LoginCNFirstFragment.kt @@ -0,0 +1,92 @@ +package com.abbidot.tracker.deprecated.ui.fragment.login + +import android.content.Context +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentLoginCnFirstBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.LoginCNViewModel +import com.abbidot.tracker.util.ThirdPartyUtil +import com.abbidot.tracker.util.ViewUtil + +/** + * 登录第一步 + */ +class LoginCNFirstFragment : + BaseFragment(FragmentLoginCnFirstBinding::inflate) { + + private val mLoginCNViewModel: LoginCNViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = LoginCNFirstFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + + setOnClickListenerViews(mViewBinding.btnLoginCnWechat, mViewBinding.btnLoginCnAccount, + mViewBinding.btnLoginCnCode, mViewBinding.llLoginCnPolicyTerms.loginCnPolicy, + mViewBinding.llLoginCnPolicyTerms.loginCnTerms) + } + + override fun liveDataObserve() { + //微信登录成功返回 + XEventBus.observe(this, EventName.WXLoginResult) { it: Result -> + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + LogUtil.e("LoginCNFirstFragment微信登录成功返回,页面可见$isVisible") + if (!isVisible) { + return + } + val data = it.getOrNull() + data?.apply { + val activity = activity as LoginRegisterCNActivity + mLoginCNViewModel.loginSuccess(activity, this) + } + } + }) + } + } + + /** + * 微信登录 + */ + private fun wechatLogin() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llLoginCnPolicyTerms.loginAgreePrivacyCheckbox)) { + return + } + ThirdPartyUtil.instance.startWXLogin(activity) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnLoginCnWechat -> wechatLogin() + + mViewBinding.btnLoginCnAccount -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + mViewBinding.btnLoginCnCode -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Code_Login) + } + mViewBinding.llLoginCnPolicyTerms.loginCnPolicy -> { + ViewUtil.instance.goPrivacyPolicy(mActivity!!) + } + mViewBinding.llLoginCnPolicyTerms.loginCnTerms -> { + ViewUtil.instance.goServiceContract(mActivity!!) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/baidumap/FencesAddBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/baidumap/FencesAddBaiduMapFragment.kt new file mode 100644 index 0000000..f6675c0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/baidumap/FencesAddBaiduMapFragment.kt @@ -0,0 +1,410 @@ +package com.abbidot.tracker.deprecated.ui.fragment.map.baidumap + +import android.content.Context +import android.graphics.Point +import android.text.TextUtils +import android.view.View +import android.widget.LinearLayout +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.BaiduMapPoiSearchResultAdapter +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.deprecated.ui.activity.profile.FencesAddActivity +import com.abbidot.tracker.ui.common.map.FencesMapCommon +import com.abbidot.tracker.ui.fragment.map.baidumap.BaseBaiduMapFragment +import com.abbidot.tracker.util.LonAndLatUtil.Companion.getMaxLatOrLon +import com.abbidot.tracker.util.LonAndLatUtil.Companion.getMinLatOrLon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.FencesPolygonView +import com.baidu.location.BDLocation +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.model.LatLng +import com.baidu.mapapi.search.core.SearchResult +import com.baidu.mapapi.search.geocode.GeoCodeResult +import com.baidu.mapapi.search.geocode.GeoCoder +import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener +import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption +import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult +import com.baidu.mapapi.search.poi.OnGetPoiSearchResultListener +import com.baidu.mapapi.search.poi.PoiCitySearchOption +import com.baidu.mapapi.search.poi.PoiDetailResult +import com.baidu.mapapi.search.poi.PoiDetailSearchResult +import com.baidu.mapapi.search.poi.PoiIndoorResult +import com.baidu.mapapi.search.poi.PoiResult +import com.baidu.mapapi.search.poi.PoiSearch +import com.hjq.toast.Toaster + + +/** + *Created by .yzq on 2022/7/4/004. + * @link + * @description: + */ +class FencesAddBaiduMapFragment : BaseBaiduMapFragment() { + + var mCenterLatLng: LatLng? = null + private var mEndLatLng: LatLng? = null + + var mRectALatLng: LatLng? = null + var mRectBLatLng: LatLng? = null + var mRectCLatLng: LatLng? = null + var mRectDLatLng: LatLng? = null + + private var mOnActivityCallBack: FencesMapCommon.OnActivityCallBack? = null + private var mCity = "" + private lateinit var mPoiSearchResultAdapter: BaiduMapPoiSearchResultAdapter + private lateinit var mRecyclerView: RecyclerView + + //编辑第一次不用计算测量距离 + private var isEditFirst = true + + companion object { + @JvmStatic + fun newInstance(context: Context) = FencesAddBaiduMapFragment().apply { + mContext = context + } + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { + val fencesAddActivity = mActivity as FencesAddActivity + //设置地图渲染完成回调,拖动动图回调 + mBaiduMap?.setOnMapRenderCallbadk { + if (fencesAddActivity.isEdit) { + //编辑的第一次不去计算距离 + if (isEditFirst) { + isEditFirst = false + } else { + mapOkAndDistanceEdit() + } + } else { + mapOkAndDistance() + } + } + + if (fencesAddActivity.isEdit) { + locationSuccess(null) + } else { + startLocation() + } + + //测试矩形围栏使用的代码 +// if (null != mRectALatLng) { +// val points = mutableListOf() +// points.add(LatLng(mRectALatLng!!.latitude, mRectALatLng!!.longitude)) +// points.add(LatLng(mRectBLatLng!!.latitude, mRectBLatLng!!.longitude)) +// points.add(LatLng(mRectDLatLng!!.latitude, mRectDLatLng!!.longitude)) +// points.add(LatLng(mRectCLatLng!!.latitude, mRectCLatLng!!.longitude)) +// //构造PolygonOptions +// val polygonOptions = PolygonOptions().points(points) +// //是否显示虚线 +// .dottedStroke(true) +// //填充颜色 +// .fillColor(ContextCompat.getColor(requireContext(), R.color.map_circle_color1)) +// //边框宽和边框颜色 +// .stroke( +// Stroke( +// QMUIDisplayHelper.dpToPx(2), +// ContextCompat.getColor(requireContext(), R.color.btn_yellow_color) +// ) +// ) +// mBaiduMap!!.addOverlay(polygonOptions) +// } + } + + /** + * 地图准备好计算距离 + */ + fun mapOkAndDistance() { + if (null == mActivity) return + + val arrayOfDistance = getLatLngAndCalDis() + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + + //构建Marker图标 + mRectALatLng?.apply { +// mBaiduMap!!.clear() +// val bitmap = BitmapDescriptorFactory.fromResource(R.drawable.pic_map_location) +// val optionA: OverlayOptions = MarkerOptions().position(mRectALatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionA) +// val optionB: OverlayOptions = MarkerOptions().position(mRectBLatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionB) +// val optionC: OverlayOptions = MarkerOptions().position(mRectCLatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionC) +// val optionD: OverlayOptions = MarkerOptions().position(mRectDLatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionD) +// + LogUtil.e("新建的4个点经纬度$mRectALatLng,\n$mRectBLatLng,\n$mRectCLatLng,\n$mRectDLatLng,\n$mCenterLatLng") + } + } + + /** + * 获取屏幕上的经纬度并计算距离 + */ + private fun getLatLngAndCalDis(): Array { + val fencesAddActivity = mActivity as FencesAddActivity + //手机坐标点转换为经纬度 + val centreLatLng = + fencesAddActivity.getCentrePoint().let { mBaiduMap!!.projection.fromScreenLocation(it) } + mCenterLatLng = centreLatLng + + // val sss = mBaiduMap!!.projection.toScreenLocation(mCenterLatLng) +// LogUtil.e("errerererererer$sss") + + return if (fencesAddActivity.mFenceShapeType == ConstantInt.CircleShapeType) { + val endLatLng = fencesAddActivity.getCircleLineEndPoint() + .let { mBaiduMap!!.projection.fromScreenLocation(it) } + mEndLatLng = endLatLng + LogUtil.e("BaiduMap,mapOkAndDistance,setOnMapRenderCallbadk ") + LogUtil.e("startLatLng=$centreLatLng,endLatLng=$endLatLng") + val distance = Util.measureLatLonDistance( + centreLatLng.latitude, + centreLatLng.longitude, + endLatLng.latitude, + endLatLng.longitude + ) + arrayOf( + Utils.formatDecimal(distance, 2) + ) + } else { + mRectALatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_A).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + mRectBLatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_B).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + mRectCLatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_C).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + mRectDLatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_D).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val rectWidthDistance = Util.measureLatLonDistance( + mRectALatLng!!.latitude, + mRectALatLng!!.longitude, + mRectBLatLng!!.latitude, + mRectBLatLng!!.longitude + ) + val rectHeightDistance = Util.measureLatLonDistance( + mRectALatLng!!.latitude, + mRectALatLng!!.longitude, + mRectCLatLng!!.latitude, + mRectCLatLng!!.longitude + ) + //长度和高度 + arrayOf( + Utils.formatDecimal(rectWidthDistance, 2), + Utils.formatDecimal(rectHeightDistance, 2) + ) + } + } + + /** + * 编辑围栏返回的距离 + */ + private fun mapOkAndDistanceEdit() { + if (null == mActivity) return + + val arrayOfDistance = getLatLngAndCalDis() + + //测试矩形围栏编辑还原 +// mBaiduMap!!.clear() +// val bitmap = BitmapDescriptorFactory.fromResource(R.drawable.map_live_start) +// val optionA= MarkerOptions().position(mRectALatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionA) +// val optionB = MarkerOptions().position(mRectBLatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionB) +// val optionC = MarkerOptions().position(mRectCLatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionC) +// val optionD = MarkerOptions().position(mRectDLatLng).icon(bitmap) +// mBaiduMap!!.addOverlay(optionD) + + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + } + + fun initRecyclerView(context: Context, recyclerView: RecyclerView) { + mPoiSearchResultAdapter = BaiduMapPoiSearchResultAdapter(context, null) + mPoiSearchResultAdapter.setOnItemClickListener(object : + BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val item = mPoiSearchResultAdapter.getItem(pos) + setSearchResultsViewGone() + moveCameraLocation( + item.location.latitude, item.location.longitude, needConvertGCJ02 = false + ) + } + }) + mRecyclerView = recyclerView + mRecyclerView.addItemDecoration(DividerItemDecoration(context, LinearLayout.VERTICAL)) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + context, mRecyclerView, mPoiSearchResultAdapter + ) + } + + /** + * 经纬度转换为屏幕坐标点 + */ + fun latLngToPotion(wherePoint: Int): Point? { + mBaiduMap?.apply { + when (wherePoint) { + FencesPolygonView.POINT_A -> return projection.toScreenLocation(mRectALatLng) + FencesPolygonView.POINT_B -> return projection.toScreenLocation(mRectBLatLng) + FencesPolygonView.POINT_C -> return projection.toScreenLocation(mRectCLatLng) + FencesPolygonView.POINT_D -> return projection.toScreenLocation(mRectDLatLng) + } + } + return null + } + + + /** + * 设置圆形围栏,地图缩放级别 + */ + fun setCircleMapZoom(distance: Double) { + val zoom = getDistanceZoom(distance) + LogUtil.e("百度地图zoom==$zoom") + setBaiduMapZoom(zoom) + } + + /** + * 设置矩形围栏,地图缩放级别 + */ + fun setRectMapZoom( + rectALatLng: LatLng, rectBLatLng: LatLng, rectCLatLng: LatLng, rectDLatLng: LatLng + ) { + val maxLat = getMaxLatOrLon( + rectALatLng.latitude, rectBLatLng.latitude, rectCLatLng.latitude, rectDLatLng.latitude + ) + val maxLon = getMaxLatOrLon( + rectALatLng.longitude, + rectBLatLng.longitude, + rectCLatLng.longitude, + rectDLatLng.longitude + ) + val minLat = getMinLatOrLon( + rectALatLng.latitude, rectBLatLng.latitude, rectCLatLng.latitude, rectDLatLng.latitude + ) + val minLon = getMinLatOrLon( + rectALatLng.longitude, + rectBLatLng.longitude, + rectCLatLng.longitude, + rectDLatLng.longitude + ) + val zoom = getDistanceZoom(LatLng(minLat, minLon), LatLng(maxLat, maxLon)) + LogUtil.e("百度地图zoom==$zoom") + setBaiduMapZoom(zoom) + } + + /** + * 隐藏搜索检索结果页面 + */ + fun setSearchResultsViewGone() { + mRecyclerView.visibility = View.GONE + } + + /** + * 搜索某个地点 + * 百度地图检索需要城市,把当前定位的经纬度转换城市 + */ + fun searchAddress(keyword: String) { + if (TextUtils.isEmpty(keyword)) { + setSearchResultsViewGone() + return + } + if (TextUtils.isEmpty(mCity)) { + showToast(R.string.txt_load_map) + return + } + showLoading(true) + val poiSearch = PoiSearch.newInstance() + poiSearch.setOnGetPoiSearchResultListener(object : OnGetPoiSearchResultListener { + override fun onGetPoiResult(poiResult: PoiResult?) { + showLoading(false) + if (poiResult == null || poiResult.error === SearchResult.ERRORNO.RESULT_NOT_FOUND) { + Toaster.show("未找到结果") + return + } + try { + val poiInfo = poiResult.allPoi + if (poiInfo.size > 0) { + for (p in poiInfo) { + LogUtil.d(p.toString()) + } + mRecyclerView.visibility = View.VISIBLE + mPoiSearchResultAdapter.setData(poiInfo, true) + } + } catch (e: Exception) { + mRecyclerView.visibility = View.GONE + } + } + + override fun onGetPoiDetailResult(p0: PoiDetailResult?) { + } + + override fun onGetPoiDetailResult(p0: PoiDetailSearchResult?) { + } + + override fun onGetPoiIndoorResult(p0: PoiIndoorResult?) { + } + }) + poiSearch.searchInCity( + PoiCitySearchOption().cityLimit(false).city(mCity).keyword(keyword).pageCapacity(15) + .pageNum(0) + ) + poiSearch.destroy() + } + + /** + * 百度地图检索需要城市,把当前定位的经纬度转换城市 + */ + private fun getCity() { + val coder = GeoCoder.newInstance() + coder.setOnGetGeoCodeResultListener(object : OnGetGeoCoderResultListener { + override fun onGetGeoCodeResult(p0: GeoCodeResult?) { + + } + + override fun onGetReverseGeoCodeResult(result: ReverseGeoCodeResult?) { + if (null == result || result.error != SearchResult.ERRORNO.NO_ERROR) { + //没有找到检索结果 + return + } else { + mCity = try { + result.addressDetail.city + } catch (e: Exception) { + "北京" + } + } + } + }) + coder.reverseGeoCode( + ReverseGeoCodeOption().location(mCenterLatLng) // 设置是否返回新数据 默认值0不返回,1返回 + .newVersion(1) // POI召回半径,允许设置区间为0-1000米,超过1000米按1000米召回。默认值为1000 + .radius(500) + ) + coder.destroy() + } + + override fun locationSuccess(location: BDLocation?) { + if (null != location) { + //初始化开始起点位置(中心圆) + mCenterLatLng = LatLng(location.latitude, location.longitude) + LogUtil.e("初始化开始起点位置$mCenterLatLng") + } + + moveCameraLocation( + mCenterLatLng!!.latitude, mCenterLatLng!!.longitude, needConvertGCJ02 = false + ) + + mOnActivityCallBack?.mapCompletion() + getCity() + } + + fun setOnCalculationDistance(onActivityCallBack: FencesMapCommon.OnActivityCallBack) { + mOnActivityCallBack = onActivityCallBack + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/baidumap/NavigationBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/baidumap/NavigationBaiduMapFragment.kt new file mode 100644 index 0000000..bc215a8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/baidumap/NavigationBaiduMapFragment.kt @@ -0,0 +1,113 @@ +package com.abbidot.tracker.deprecated.ui.fragment.map.baidumap + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.deprecated.ui.activity.map.HomeMapNavigationActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.RoutePlanningViewModel +import com.abbidot.tracker.ui.fragment.map.baidumap.BaseBaiduMapFragment +import com.baidu.location.BDLocation +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.map.OverlayOptions +import com.baidu.mapapi.map.PolylineOptions +import com.baidu.mapapi.model.LatLng +import com.qmuiteam.qmui.util.QMUIDisplayHelper + + +/** + *Created by .yzq on 2022/6/27/027. + * @link + * @description: + */ +class NavigationBaiduMapFragment: BaseBaiduMapFragment() { + + private val mRoutePlanningViewModel: RoutePlanningViewModel by viewModels() + + private lateinit var mMapNavigationActivity: HomeMapNavigationActivity + + /** + * 宠物经纬度 已经转换好的 + */ +// private lateinit var mPetLatLng: LatLng + + companion object { + @JvmStatic + fun newInstance(context: Context) = NavigationBaiduMapFragment().apply { + mContext = context + } + } + + override fun initData() { + mMapNavigationActivity = mActivity as HomeMapNavigationActivity + super.initData() + } + + override fun liveDataObserve() { + mRoutePlanningViewModel.mRouteBaiduMapLiveData.observe(viewLifecycleOwner) { + it?.apply { + val distanceStr = Utils.formatDecimal(it.distance.toDouble(), 2) + val minStr = Utils.formatDecimal((it.duration / 60).toDouble(), 2) + mMapNavigationActivity.setDistanceData(distanceStr, minStr) + } + } + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { +// initFenceList() + + //开启地图的定位图层 + baiduMap.isMyLocationEnabled = true + + mMapNavigationActivity.apply { + //宠物经纬度 + val convertLonLat = getBaiduMapLatLng(mMapDeviceBean!!.latitude, mMapDeviceBean!!.longitude,true) + + mPetLatLng = convertLonLat + + //画围栏 + mFencesMapViewModel.setFencesData( + mContext, mMapDeviceBean!!.fences, this@NavigationBaiduMapFragment + ) + + refreshPetCurrentLocation( + mMapDeviceBean!!.latitude, + mMapDeviceBean!!.longitude, + isAnimMoveCamera = false, + headBgResId = R.drawable.pic_map_location + ) + + startLocation() + } + } + + override fun locationSuccess(location: BDLocation?) { + val locationLatLng = LatLng(location!!.latitude, location.longitude) + val points = mutableListOf() + points.add(locationLatLng) + points.add(mPetLatLng!!) + addNavigationLine(points) + } + + /** + * 画导航直线 + */ + private fun addNavigationLine(points: MutableList) { + if (points.size < 1) return + mRoutePlanningViewModel.getBaiduMapRoutePlan(mMapNavigationActivity, points[0], points[1]) + + //设置折线的属性 + val mOverlayOptions: OverlayOptions = PolylineOptions().width(QMUIDisplayHelper.dpToPx(2)) + .color(ContextCompat.getColor(mContext!!, R.color.navigation_line_color)) + .dottedLine(true).points(points) + mBaiduMap!!.addOverlay(mOverlayOptions) + } + + override fun onDestroy() { + mBaiduMap?.isMyLocationEnabled = false + super.onDestroy() + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/googlemap/FencesAddGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/googlemap/FencesAddGoogleMapFragment.kt new file mode 100644 index 0000000..3afe131 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/googlemap/FencesAddGoogleMapFragment.kt @@ -0,0 +1,313 @@ +package com.abbidot.tracker.deprecated.ui.fragment.map.googlemap + +import android.content.Context +import android.graphics.Point +import android.location.Location +import android.text.TextUtils +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.AppCompatEditText +import androidx.appcompat.widget.LinearLayoutCompat +import androidx.appcompat.widget.SearchView +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.deprecated.ui.activity.profile.FencesAddActivity +import com.abbidot.tracker.ui.common.map.FencesMapCommon +import com.abbidot.tracker.ui.fragment.map.googlemap.BaseGoogleMapFragment +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.widget.FencesPolygonView +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.LatLng +import com.mapbox.search.ui.view.SearchResultsView +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2022/1/13/013. + * @link + * @description:添加围栏map谷歌地图 + */ +class FencesAddGoogleMapFragment : BaseGoogleMapFragment() { + + var mCenterLatLng: LatLng? = null + private var mEndLatLng: LatLng? = null + + var mRectALatLng: LatLng? = null + var mRectBLatLng: LatLng? = null + var mRectCLatLng: LatLng? = null + var mRectDLatLng: LatLng? = null + + private var mOnActivityCallBack: FencesMapCommon.OnActivityCallBack? = null + + private lateinit var mSearchResultsView: SearchResultsView + + //编辑第一次不用计算测量距离 + private var isEditFirst = true + + companion object { + @JvmStatic + fun newInstance(context: Context) = FencesAddGoogleMapFragment().apply { + mContext = context + } + } + + override fun onMapLoadOk(googleMap: GoogleMap) { + val fencesAddActivity = mActivity as FencesAddActivity + //监听摄像机移动中心点,摄像机移动结束时的回调接口。 + mGoogleMap?.setOnCameraIdleListener { + if (fencesAddActivity.isEdit) { + //编辑的第一次不去计算距离 + if (isEditFirst) { + isEditFirst = false + } else { + mapOkAndDistanceEdit() + } + } else { + mapOkAndDistance() + } + } + + if (fencesAddActivity.isEdit) { + locationSuccess(null) + } else { + getLastLocation() + } + } + + /** + * 地图准备好计算距离 + */ + fun mapOkAndDistance() { + if (null == mActivity) return + val arrayOfDistance = getLatLngAndCalDistance() + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + } + + private fun getLatLngAndCalDistance(): Array { + val fencesAddActivity = mActivity as FencesAddActivity + // 经纬度转换为手机坐标点 +// val x = mGoogleMap!!.projection.toScreenLocation(it).x +// val y = mGoogleMap!!.projection.toScreenLocation(it).y + + //手机坐标点转换为经纬度 + val centreLatLng = fencesAddActivity.getCentrePoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + mCenterLatLng = centreLatLng + + return if (fencesAddActivity.mFenceShapeType == ConstantInt.CircleShapeType) { + val endLatLng = fencesAddActivity.getCircleLineEndPoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + mEndLatLng = endLatLng + LogUtil.e("GoogleMap,setOnCameraIdleListener 监听回调") + LogUtil.e("startLatLng=$centreLatLng,endLatLng=$endLatLng") + val distance = Util.measureLatLonDistance( + centreLatLng.latitude, + centreLatLng.longitude, + endLatLng.latitude, + endLatLng.longitude + ) + arrayOf( + Utils.formatDecimal(distance, 2) + ) + } else { + mRectALatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_A).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + mRectBLatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_B).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + mRectCLatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_C).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + mRectDLatLng = fencesAddActivity.getRectABCDPoint(FencesPolygonView.POINT_D).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val rectWidthDistance = Util.measureLatLonDistance( + mRectALatLng!!.latitude, + mRectALatLng!!.longitude, + mRectBLatLng!!.latitude, + mRectBLatLng!!.longitude + ) + val rectHeightDistance = Util.measureLatLonDistance( + mRectALatLng!!.latitude, + mRectALatLng!!.longitude, + mRectCLatLng!!.latitude, + mRectCLatLng!!.longitude + ) + //长度和高度 + arrayOf( + Utils.formatDecimal(rectWidthDistance, 2), + Utils.formatDecimal(rectHeightDistance, 2) + ) + } + } + + /** + * 编辑围栏返回的距离 + */ + private fun mapOkAndDistanceEdit() { + if (null == mActivity) return + val arrayOfDistance = getLatLngAndCalDistance() + + //测试矩形围栏编辑还原 +// mGoogleMap!!.clear() +// val bitmap = BitmapDescriptorFactory.fromResource(R.drawable.ico_map) +// val optionA = MarkerOptions().position(mRectALatLng!!).icon(bitmap) +// mGoogleMap!!.addMarker(optionA) +// val optionB = MarkerOptions().position(mRectBLatLng!!).icon(bitmap) +// mGoogleMap!!.addMarker(optionB) +// val optionC = MarkerOptions().position(mRectCLatLng!!).icon(bitmap) +// mGoogleMap!!.addMarker(optionC) +// val optionD = MarkerOptions().position(mRectDLatLng!!).icon(bitmap) +// mGoogleMap!!.addMarker(optionD) + + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + } + + override fun locationSuccess(location: Location?) { + LogUtil.e("$mCenterLatLng,locationSuccess locationSuccess,$location") + if (null != location) { + //初始化开始起点位置(中心圆) + mCenterLatLng = LatLng(location.latitude, location.longitude) + } + moveCameraLocation(mCenterLatLng!!, false) + + mOnActivityCallBack?.mapCompletion() + } + + private lateinit var mMapBoxSearchView: SearchView + + fun initMapBox( + context: Context, + searchResultsViewLayout: LinearLayoutCompat, + addressEditText: AppCompatEditText + ) { + LogUtil.e("---------------------------initMapBox-------------------") + mMapBoxSearchView = SearchView(context) + //判断有没有初始化 + if (!this::mSearchResultsView.isInitialized) { + mSearchResultsView = SearchResultsView(context) + mSearchResultsView.apply { + layoutParams = LinearLayoutCompat.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT + ) + background = ContextCompat.getDrawable(context, R.drawable.shape20_white_bg) + elevation = QMUIDisplayHelper.dpToPx(10).toFloat() + } + } + + mSearchResultsView.apply { + //单位 +// initialize(CommonSearchViewConfiguration(DistanceUnitType.METRIC)) + isVisible = false + +// isHapticFeedbackEnabled = false + +// addSearchListener(object : SearchResultsView.SearchListener { +// override fun onSearchResult( +// searchResult: SearchResult, responseInfo: ResponseInfo +// ) { +// mSearchResultsView.isVisible = false +// LogUtil.e("Search result: $searchResult") +// //发起请求时的经纬度 +//// val lat = searchResult.requestOptions.options.origin!!.coordinates()[1] +//// val lon = searchResult.requestOptions.options.origin!!.coordinates()[0] +// +// //搜索到的经纬度 +// val resultLat = searchResult.coordinate!!.latitude() +// val resultLon = searchResult.coordinate!!.longitude() +// setCameraUpdateNull() +// moveCameraLocation(LatLng(resultLat, resultLon)) +// } +// +// override fun onHistoryItemClicked(historyRecord: HistoryRecord) { +//// searchView.setQuery(historyRecord.name, true) +//// LogUtil.e("${historyRecord.name}") +// //历史搜索 +// addressEditText.setText(historyRecord.name) +// searchAddress(addressEditText.text.toString()) +// } +// +// override fun onPopulateQueryClicked( +// suggestion: SearchSuggestion, responseInfo: ResponseInfo +// ) { +// mMapBoxSearchView.setQuery(suggestion.name, true) +// LogUtil.e("onPopulateQueryClicked") +// } +// +// override fun onFeedbackClicked(responseInfo: ResponseInfo) { +// // Not implemented +// } +// }) + } + + searchResultsViewLayout.apply { + visibility = View.VISIBLE + addView(mSearchResultsView) + } + } + + /** + * 经纬度转换为屏幕坐标点 + */ + fun latLngToPotion(wherePoint: Int): Point? { + mGoogleMap?.apply { + when (wherePoint) { + FencesPolygonView.POINT_A -> return projection.toScreenLocation(mRectALatLng!!) + FencesPolygonView.POINT_B -> return projection.toScreenLocation(mRectBLatLng!!) + FencesPolygonView.POINT_C -> return projection.toScreenLocation(mRectCLatLng!!) + FencesPolygonView.POINT_D -> return projection.toScreenLocation(mRectDLatLng!!) + } + } + return null + } + + /** + * 设置圆形围栏,地图缩放级别 + */ + fun setCircleMapZoom(distance: Double) { + val zoom = getDistanceZoom(distance) + LogUtil.e("谷歌地图zoom==$zoom") + setGoogleMapZoom(zoom) + } + + /** + * 设置矩形 地图缩放级别 + */ + fun setRectMapZoom( + context: Context, + rectALatLng: LatLng, + rectBLatLng: LatLng, + rectCLatLng: LatLng, + rectDLatLng: LatLng + ) { + setLatLngZoom(context, 300, rectALatLng, rectBLatLng, rectCLatLng, rectDLatLng) + } + + /** + * 搜索地址,地理编码 + */ + fun searchAddress(keyword: String) { + if (TextUtils.isEmpty(keyword)) { + return + } + mSearchResultsView.apply { + isVisible = true +// search(keyword) + } + } + + /** + * 隐藏搜索检索结果页面 + */ + fun setSearchResultsViewGone() { + mSearchResultsView.isVisible = false + } + + fun setOnCalculationDistance(onActivityCallBack: FencesMapCommon.OnActivityCallBack) { + mOnActivityCallBack = onActivityCallBack + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/googlemap/NavigationGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/googlemap/NavigationGoogleMapFragment.kt new file mode 100644 index 0000000..70bef06 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/map/googlemap/NavigationGoogleMapFragment.kt @@ -0,0 +1,148 @@ +package com.abbidot.tracker.deprecated.ui.fragment.map.googlemap + +import android.annotation.SuppressLint +import android.content.Context +import android.location.Location +import androidx.core.content.ContextCompat +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.deprecated.ui.activity.map.HomeMapNavigationActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.RoutePlanningViewModel +import com.abbidot.tracker.ui.fragment.map.googlemap.BaseGoogleMapFragment +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.* + + +/** + *Created by .yzq on 2022/1/13/013. + * @link + * @description:导航谷歌地图 + */ +class NavigationGoogleMapFragment: BaseGoogleMapFragment() { + + private val mRoutePlanningViewModel: RoutePlanningViewModel by viewModels() + + private var mMyLocation: Location? = null + +// /** +// * 宠物经纬度 +// */ +// private var mPetLatLng: LatLng? = null + + private lateinit var mMapNavigationActivity: HomeMapNavigationActivity + + /** + * 导航画的线 + */ + private var mNavigationLine: Polyline? = null + + companion object { + @JvmStatic + fun newInstance(context: Context) = NavigationGoogleMapFragment().apply { + mContext = context + } + } + + override fun initData() { + mMapNavigationActivity = mActivity as HomeMapNavigationActivity + super.initData() + } + + override fun liveDataObserve() { + mRoutePlanningViewModel.mRouteMapBoxLiveData.observe(viewLifecycleOwner) { + val distanceStr: String + val minStr: String + //如果返回为空,就大概算出 + if (null == it) { + //计算2点的距离 + val distance = + com.abbidot.tracker.util.Util.measureLatLonDistance(mMyLocation!!.latitude, + mMyLocation!!.longitude, mPetLatLng!!.latitude, mPetLatLng!!.longitude) + //计算步行需要多久 假设步行速度为1.3m/s 只计算大概数据 + val time = distance / 1.3 + + distanceStr = Utils.formatDecimal(distance, 2) + minStr = Utils.formatDecimal(time / 60, 2) + } else { + distanceStr = Utils.formatDecimal(it.distance(), 2) + minStr = Utils.formatDecimal(it.duration() / 60, 2) + } + mMapNavigationActivity.setDistanceData(distanceStr, minStr) + } + } + + /** + * 移动中心点 + */ + private fun moveLocation(location: Location) { + moveCameraLocation(LatLng(location.latitude, location.longitude)) + } + + + /** + * 画导航直线 + */ + private fun addNavigationLine(startLng: LatLng, endLng: LatLng) { + + mRoutePlanningViewModel.getMapBoxRoutePlan(mMapNavigationActivity, startLng, endLng) + + mNavigationLine?.remove() + + mNavigationLine = mGoogleMap!!.addPolyline( + PolylineOptions().clickable(true).add(startLng, endLng).width(AppUtils.dpToPx(2f)) + .color(ContextCompat.getColor(mContext!!, R.color.navigation_line_color)) + .geodesic(true)) + mNavigationLine!!.startCap = RoundCap() + //连接类型,可以指定棱台或圆角 + mNavigationLine!!.jointType = JointType.ROUND +// mNavigationLine!!.endCap = +// CustomCap(BitmapDescriptorFactory.fromResource(R.drawable.pic_map_location), +// QMUIDisplayHelper.dpToPx(3).toFloat()) + + //设置用户定位位置图标 + mGoogleMap!!.addMarker(MarkerOptions().position(startLng)) + + //自定义描边图案 + val pattern = listOf(Dash(30F), Gap(20F)) + mNavigationLine!!.pattern = pattern + } + + @SuppressLint("MissingPermission") + override fun onMapLoadOk(googleMap: GoogleMap) { +// initFenceList() + //启用“我的位置”图层 + googleMap.apply { + //显示“我的位置”按钮 + isMyLocationEnabled = true + setOnMyLocationButtonClickListener { + mMyLocation?.let { + moveLocation(it) + } + false + } + } + + setGoogleMapZoom(12.5f) + + mMapNavigationActivity.apply { + mPetLatLng = LatLng(mMapDeviceBean!!.latitude, mMapDeviceBean!!.longitude) + + //画围栏 + mFencesMapViewModel.setFencesData( + mContext, + mMapDeviceBean!!.fences, this@NavigationGoogleMapFragment) + + refreshPetCurrentLocation(mPetLatLng!!, isAnimMoveCamera = false, + headBgResId = R.drawable.pic_map_location) + + getLastLocation() + } + } + + override fun locationSuccess(location: Location?) { + mMyLocation = location + addNavigationLine(LatLng(location!!.latitude, location.longitude), mPetLatLng!!) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/profile/PaymentFailFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/profile/PaymentFailFragment.kt new file mode 100644 index 0000000..9d5eaee --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/profile/PaymentFailFragment.kt @@ -0,0 +1,52 @@ +package com.abbidot.tracker.deprecated.ui.fragment.profile + +import android.content.Context +import android.os.Bundle +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.databinding.FragmentPaymentFailBinding +import com.abbidot.tracker.deprecated.ui.activity.profile.recharge.PaymentResultActivity + +/** + * 支付失败 + */ +class PaymentFailFragment : + BaseFragment(FragmentPaymentFailBinding::inflate) { + + companion object { + @JvmStatic + fun newInstance(context: Context) = PaymentFailFragment().apply { + mContext = context + } + } + + override fun initData() { + setOnClickListenerViews(mViewBinding.paymentFailAgain, mViewBinding.paymentFailBack) + mViewBinding.paymentFailLottieAnimation.setAnimation("lottie/fail_anim.json") + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mViewBinding.root.setBackgroundResource(R.color.transparent) + mViewBinding.paymentFailLottieAnimation.playAnimation() + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.paymentFailLottieAnimation.cancelAnimation() + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.paymentFailAgain -> { + finish(mActivity!!) + } + + mViewBinding.paymentFailBack -> { + val activity = mActivity as PaymentResultActivity + activity.leftBackOnClick() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/profile/PaymentSuccessFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/profile/PaymentSuccessFragment.kt new file mode 100644 index 0000000..9016eca --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/profile/PaymentSuccessFragment.kt @@ -0,0 +1,59 @@ +package com.abbidot.tracker.deprecated.ui.fragment.profile + +import android.content.Context +import android.os.Bundle +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.databinding.FragmentPaymentSuccessBinding +import com.abbidot.tracker.deprecated.ui.activity.profile.recharge.PaymentResultActivity + +/** + * 支付成功 + */ +class PaymentSuccessFragment : + BaseFragment(FragmentPaymentSuccessBinding::inflate) { + + companion object { + @JvmStatic + fun newInstance(context: Context) = PaymentSuccessFragment().apply { + mContext = context + } + } + + override fun initData() { + setOnClickListenerViews(mViewBinding.btnPaymentSuccessBack) + mViewBinding.paymentSuccessLottieAnimation.setAnimation("lottie/success_anim.json") + + val activity = mActivity as PaymentResultActivity + activity.apply { + if (mAmount > 0) { + val amount = String.format(getString(R.string.txt_dollar), mAmount) + mViewBinding.paymentSuccessMoneyText.text = amount + } else { + mViewBinding.paymentSuccessMoneyText.visibility = View.GONE + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mViewBinding.root.setBackgroundResource(R.color.transparent) + mViewBinding.paymentSuccessLottieAnimation.playAnimation() + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.paymentSuccessLottieAnimation.cancelAnimation() + } + + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnPaymentSuccessBack -> { + val activity = mActivity as PaymentResultActivity + activity.leftBackOnClick() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/register/RegisterCNFinishFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/register/RegisterCNFinishFragment.kt new file mode 100644 index 0000000..47b20c4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/register/RegisterCNFinishFragment.kt @@ -0,0 +1,71 @@ +package com.abbidot.tracker.deprecated.ui.fragment.register + +import android.content.Context +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentRegisterCnFinishBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.RegisterCNViewModel + + +/** + *国内注册完成 + */ +class RegisterCNFinishFragment : + BaseFragment(FragmentRegisterCnFinishBinding::inflate) { + + private val mRegisterCNViewModel: RegisterCNViewModel by viewModels() +// private val mLoginCNViewModel: LoginCNViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = RegisterCNFinishFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + setOnClickListenerViews(mViewBinding.btnFinishRegisterCn) + } + + override fun liveDataObserve() { + mRegisterCNViewModel.mPhoneRegisterResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { +// val data = it.getOrNull() +// data?.apply { +// val activity = activity as LoginRegisterCNActivity +// mLoginCNViewModel.loginSuccess(activity, this) +// } + showToast(R.string.txt_registered_successfully) + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + }) + } + } + + /** + * 注册设置密码 + */ + private fun registerSetPassword() { + val password = mViewBinding.setLoginCnPasswordEd.text.toString() + if (TextUtils.isEmpty(password)) { + showToast(R.string.txt_not_null) + return + } + val activity = activity as LoginRegisterCNActivity + mRegisterCNViewModel.register(this, activity.mPhone, password) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.btnFinishRegisterCn -> registerSetPassword() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/register/RegisterCNStartFragment.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/register/RegisterCNStartFragment.kt new file mode 100644 index 0000000..59f2cab --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/register/RegisterCNStartFragment.kt @@ -0,0 +1,134 @@ +package com.abbidot.tracker.deprecated.ui.fragment.register + +import android.content.Context +import android.graphics.Paint +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentRegisterCnStartBinding +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.RegisterCNViewModel +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.CountDownTimerViewModel + + +/** + * 国内开始注册 + */ +class RegisterCNStartFragment : + BaseFragment(FragmentRegisterCnStartBinding::inflate) { + + private val mRegisterCNViewModel: RegisterCNViewModel by viewModels() + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + + companion object { + @JvmStatic + fun newInstance(context: Context) = RegisterCNStartFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + mViewBinding.apply { + //添加下划线 + returnToLoginCn.paint.flags = Paint.UNDERLINE_TEXT_FLAG + + setOnClickListenerViews(llRegisterCnGetPhoneCode.btnGetCode, returnToLoginCn, + btnNextRegisterCnCode, llRegisterCnCodeAgree.loginCnTerms, + llRegisterCnCodeAgree.loginCnPolicy) + } + } + + override fun liveDataObserve() { + mRegisterCNViewModel.mSendCodeResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mCountDownTimerViewModel.startCountDown(60) + showToast(R.string.txt_code_success) + } + }) + } + + //倒计时 + mCountDownTimerViewModel.mCountDowningLiveData.observe(viewLifecycleOwner) { + mViewBinding.llRegisterCnGetPhoneCode.btnGetCode.apply { + isEnabled = false + text = "${it}s" + } + } + mCountDownTimerViewModel.mCountDownEndLiveData.observe(viewLifecycleOwner) { + mViewBinding.llRegisterCnGetPhoneCode.btnGetCode.apply { + isEnabled = true + text = getString(R.string.register_txt_get_code) + } + } + + mRegisterCNViewModel.mValidateCodeResultLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Register_Finish) + } + }) + } + } + + /** + * 注册下一步 + */ + private fun registerCNNext() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llRegisterCnCodeAgree.loginAgreePrivacyCheckbox)) { + return + } + val phone = mViewBinding.llRegisterCnGetPhoneCode.etGetCodePhone.text.toString() + val code = mViewBinding.llRegisterCnGetPhoneCode.etPhoneCode.text.toString() + if (TextUtils.isEmpty(phone) || TextUtils.isEmpty(code)) { + showToast(R.string.txt_not_null) + return + } + + activity.mPhone = phone + mRegisterCNViewModel.validatePhoneCode(this, phone, code, 3) + } + + /** + * 获取验证码 + */ + private fun getPhoneCode() { + val activity = activity as LoginRegisterCNActivity + if (!activity.isAgreePolicy(mViewBinding.llRegisterCnCodeAgree.loginAgreePrivacyCheckbox)) { + return + } + val phone = mViewBinding.llRegisterCnGetPhoneCode.etGetCodePhone.text.toString() + if (TextUtils.isEmpty(phone)) { + showToast(R.string.txt_register_phone) + return + } + mRegisterCNViewModel.sendCode(this, phone, 3) + } + + override fun onClick(v: View?) { + when (v!!) { + mViewBinding.returnToLoginCn -> { + val activity = activity as LoginRegisterCNActivity + activity.switchFragment(LoginRegisterCNActivity.Fragment_Account_Login) + } + mViewBinding.btnNextRegisterCnCode -> registerCNNext() + + mViewBinding.llRegisterCnGetPhoneCode.btnGetCode -> getPhoneCode() + + mViewBinding.llRegisterCnCodeAgree.loginCnPolicy -> { + ViewUtil.instance.goPrivacyPolicy(mActivity!!) + } + mViewBinding.llRegisterCnCodeAgree.loginCnTerms -> { + ViewUtil.instance.goServiceContract(mActivity!!) + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/HomeViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/HomeViewModel.kt new file mode 100644 index 0000000..49dacaf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/HomeViewModel.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.deprecated.ui.fragment.vm + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseFragment +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2021/11/8/008. + * @link + * @description: HomeViewModel + */ +class HomeViewModel : ViewModel() { + fun getData() { + viewModelScope.launch { +// MyDatabase.deviceDao().insert(listOf( +// DeviceBean(5, 4, "tesdfd", conState = ConState.CONNECTED, "7547sgdgdgy64e4r"), +// DeviceBean(3, 2, "65757", conState = ConState.DISCONNECTED, "gujghj"), +// DeviceBean())) + } + + } + + +// var userInfo = MutableLiveData() + /** + * 根据经纬度获取地址 + */ + fun getAddress(fragment: BaseFragment<*>, latitude: Double, longitude: Double){ + viewModelScope.launch { + val latlng = "$latitude,$longitude" + val fields = "formatted_address,name,rating,geometry" + val key = "你的Key" +// RetrofitManger.getApiService().getAddress( +// latlng, "zh", fields, key +// ).putValue2Data(GeocodeAddressBean::class.java,userInfo) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/LoginCNViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/LoginCNViewModel.kt new file mode 100644 index 0000000..66de680 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/LoginCNViewModel.kt @@ -0,0 +1,117 @@ +package com.abbidot.tracker.deprecated.ui.fragment.vm + +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import cn.jpush.android.api.JPushInterface +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.deprecated.ui.activity.LoginRegisterCNActivity +import com.abbidot.tracker.ui.activity.HomeV2Activity +import dagger.hilt.android.scopes.ActivityRetainedScoped +import kotlinx.coroutines.launch +import java.util.* +import javax.inject.Inject + +/** + *Created by .yzq on 2022/7/15/015. + * @link + * @description: + */ +@ActivityRetainedScoped +class LoginCNViewModel @Inject constructor() : ViewModel() { + + val mWXLoginResultLiveData = MutableLiveData>() + val mWXBindResultLiveData = MutableLiveData>() + val mPhoneLoginResultLiveData = MutableLiveData>() + val mAccountPwdLoginResultLiveData = MutableLiveData>() + + /** + * 微信登录 + */ + fun wxLogin(code: String) { + viewModelScope.launch { + val result = NetworkApi.wxLogin(code) + mWXLoginResultLiveData.value = result + } + } + + /** + * 处理微信、登录成功返回 + */ + fun loginSuccess(activity: LoginRegisterCNActivity, + userBean: UserBean, + tipRes: Int = R.string.txt_login_successful) { + //判断有没有绑定手机号 + if (userBean.status == ConstantInt.Close) { + activity.apply { + mWXId = userBean.wxId + switchFragment(LoginRegisterCNActivity.Fragment_Bind_Phone) + } + } else { + //通知欢迎页面关闭 + XEventBus.post(EventName.FinishActivity) + activity.showToast(tipRes) + MMKVUtil.putString(MMKVKey.UserId, userBean.userId) + MMKVUtil.putInt(MMKVKey.isBindDevice, userBean.isBindDevice) + MMKVUtil.putString(MMKVKey.Token, userBean.token) + val rand = Random() + //极光推送设置别名 + JPushInterface.setAlias(activity, rand.nextInt(1000), userBean.userId) + + val intent = Intent() + //判断有没有绑定设备 +// if (userBean.isBindDevice == ConstantInt.Close) { +// intent.setClass(activity, AddTrackerDeviceActivity::class.java) +// } else { +// intent.setClass(activity, HomeActivity::class.java) +// } + intent.setClass(activity, HomeV2Activity::class.java) + activity.startActivityFinish(intent) + } + } + + /** + * 微信绑定手机号 + */ + fun wxBind(fragment: BaseFragment<*>, phone: String, verificationCode: String, wxId: String) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.wxBind(phone, verificationCode, wxId) + mWXBindResultLiveData.value = result + } + } + + + /** + * 验证码登录 + */ + fun phoneLogin(fragment: BaseFragment<*>, phone: String, code: String) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.phoneLogin(phone, code) + mPhoneLoginResultLiveData.value = result + } + } + + /** + * 验证码登录 + */ + fun accountPasswordLogin(fragment: BaseFragment<*>, phone: String, password: String) { + fragment.showLoading(true) + viewModelScope.launch { + val pwd = Utils.get32Md5Value(password) + val result = NetworkApi.accountPasswordLogin(phone, pwd) + mAccountPwdLoginResultLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/MyPetsViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/MyPetsViewModel.kt new file mode 100644 index 0000000..fa70a0f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/MyPetsViewModel.kt @@ -0,0 +1,26 @@ +package com.abbidot.tracker.deprecated.ui.fragment.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/7/007. + * @link + * @description: + */ +class MyPetsViewModel : ViewModel() { + val mMyPetsListLiveData = MutableLiveData>>() + + fun getPetList(fragment: BaseFragment<*>, userId: String, type: Int) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getPetList(userId, type) + mMyPetsListLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/RegisterCNViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/RegisterCNViewModel.kt new file mode 100644 index 0000000..bcb911c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/RegisterCNViewModel.kt @@ -0,0 +1,68 @@ +package com.abbidot.tracker.deprecated.ui.fragment.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/7/18/018. + * @link + * @description: + */ +class RegisterCNViewModel : ViewModel() { + val mSendCodeResultLiveData = MutableLiveData>() + val mValidateCodeResultLiveData = MutableLiveData>() + val mPhoneRegisterResultLiveData = MutableLiveData>() + val mSetNewPasswordResultLiveData = MutableLiveData>() + + /** + * 发送验证码 + * @param type 发送类型 1是验证码登录 2微信绑定 3注册 4忘记密码 + */ + fun sendCode(fragment: BaseFragment<*>, phone: String, type: Int) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.sendCode(phone, type) + mSendCodeResultLiveData.value = result + } + } + + /** + * 手机号验证码验证 + */ + fun validatePhoneCode(fragment: BaseFragment<*>, phone: String, code: String, type: Int) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.validatePhoneCode(phone, code, type) + mValidateCodeResultLiveData.value = result + } + } + + /** + * 注册 + */ + fun register(fragment: BaseFragment<*>, phone: String, password: String) { + fragment.showLoading(true) + viewModelScope.launch { + val pwd = Utils.get32Md5Value(password) + val result = NetworkApi.register(phone, pwd) + mPhoneRegisterResultLiveData.value = result + } + } + + /** + * 重置密码 + */ + fun resetNewPassword(fragment: BaseFragment<*>, phone: String, password: String) { + fragment.showLoading(true) + viewModelScope.launch { + val pwd = Utils.get32Md5Value(password) + val result = NetworkApi.resetNewPassword(phone, pwd) + mSetNewPasswordResultLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/RoutePlanningViewModel.kt b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/RoutePlanningViewModel.kt new file mode 100644 index 0000000..0a83ca0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/deprecated/ui/fragment/vm/RoutePlanningViewModel.kt @@ -0,0 +1,159 @@ +package com.abbidot.tracker.deprecated.ui.fragment.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.KeyNames +import com.abbidot.tracker.util.Util +import com.baidu.mapapi.search.route.BikingRouteResult +import com.baidu.mapapi.search.route.DrivingRouteResult +import com.baidu.mapapi.search.route.IndoorRouteResult +import com.baidu.mapapi.search.route.MassTransitRouteResult +import com.baidu.mapapi.search.route.OnGetRoutePlanResultListener +import com.baidu.mapapi.search.route.PlanNode +import com.baidu.mapapi.search.route.RoutePlanSearch +import com.baidu.mapapi.search.route.TransitRouteResult +import com.baidu.mapapi.search.route.WalkingRouteLine +import com.baidu.mapapi.search.route.WalkingRoutePlanOption +import com.baidu.mapapi.search.route.WalkingRouteResult +import com.google.android.gms.maps.model.LatLng +import com.mapbox.api.directions.v5.DirectionsCriteria +import com.mapbox.api.directions.v5.MapboxDirections +import com.mapbox.api.directions.v5.models.DirectionsResponse +import com.mapbox.api.directions.v5.models.DirectionsRoute +import com.mapbox.api.directions.v5.models.RouteOptions +import com.mapbox.geojson.Point +import retrofit2.Call +import retrofit2.Callback +import retrofit2.Response + + +/** + *Created by .yzq on 2022/4/13/013. + * @link + * @description:获取路线规划 + */ +class RoutePlanningViewModel : ViewModel() { + + //mapbox步行路线规划 + val mRouteMapBoxLiveData = MutableLiveData() + + //百度地图步行路线规划 + private var mRoutePlanSearch: RoutePlanSearch? = null + val mRouteBaiduMapLiveData = MutableLiveData() + + + /** + * 获取mapbox路线规划信息 + */ + fun getMapBoxRoutePlan(activity: BaseActivity<*>, startLng: LatLng, endLng: LatLng) { + activity.showLoading(true) + val coordinates = mutableListOf() + coordinates.add(Point.fromLngLat(startLng.longitude, startLng.latitude)) + coordinates.add(Point.fromLngLat(endLng.longitude, endLng.latitude)) + + val routeOptions = RouteOptions.builder().profile(DirectionsCriteria.PROFILE_WALKING) + .coordinatesList(coordinates).build() + val mapboxAccessToken = + Util.getMetadata(activity.applicationContext, KeyNames.MAPBOX_ACCESS_TOKEN) + val client = + MapboxDirections.builder().accessToken(mapboxAccessToken).routeOptions(routeOptions) + .build() + + client.enqueueCall(object : Callback { + override fun onResponse( + call: Call, response: Response + ) { + activity.showLoading(false) + LogUtil.d("Response code: " + response.code()) + + val result = response.body() + if (result == null) { + LogUtil.e( + "RoutePlanningViewModel", + "No routes found, make sure you set the right user and access token." + ) + mRouteMapBoxLiveData.value = null + return + } else if (result.routes().size < 1) { + LogUtil.e("RoutePlanningViewModel", "No routes found") + mRouteMapBoxLiveData.value = null + return + } + val route = result.routes()[0] + LogUtil.e(route.toString()) + mRouteMapBoxLiveData.value = route + client.cancelCall() + } + + override fun onFailure(call: Call, t: Throwable) { + activity.showLoading(false) + LogUtil.e("RoutePlanningViewModel", "Error: " + t.message) + mRouteMapBoxLiveData.value = null + client.cancelCall() + } + }) + } + + /** + * 百度地图步行路线规划,经纬度不用再转化 + */ + fun getBaiduMapRoutePlan( + activity: BaseActivity<*>, + startLng: com.baidu.mapapi.model.LatLng, + endLng: com.baidu.mapapi.model.LatLng + ) { + activity.showLoading(true) + if (null == mRoutePlanSearch) { + mRoutePlanSearch = RoutePlanSearch.newInstance() + mRoutePlanSearch!!.setOnGetRoutePlanResultListener(object : + OnGetRoutePlanResultListener { + override fun onGetWalkingRouteResult(walkingRouteResult: WalkingRouteResult?) { + activity.showLoading(false) + if (null != walkingRouteResult) { + try { + //routeLines=null的情況 + walkingRouteResult.routeLines.size + } catch (e: Exception) { + walkingRouteResult.routeLines = mutableListOf() + } + if (walkingRouteResult.routeLines.size > 0) { + //获取路径规划数据,(以返回的第一条数据为例) + val result = walkingRouteResult.routeLines[0] + mRouteBaiduMapLiveData.value = result + } else { + mRouteBaiduMapLiveData.value = null + } + } else { + mRouteBaiduMapLiveData.value = null + } + } + + override fun onGetTransitRouteResult(p0: TransitRouteResult?) { + } + + override fun onGetMassTransitRouteResult(p0: MassTransitRouteResult?) { + } + + override fun onGetDrivingRouteResult(p0: DrivingRouteResult?) { + } + + override fun onGetIndoorRouteResult(p0: IndoorRouteResult?) { + } + + override fun onGetBikingRouteResult(p0: BikingRouteResult?) { + } + }) + } + + val stNode = PlanNode.withLocation(startLng) + val enNode = PlanNode.withLocation(endLng) + mRoutePlanSearch?.walkingSearch(WalkingRoutePlanOption().from(stNode).to(enNode)) + } + + override fun onCleared() { + super.onCleared() + mRoutePlanSearch?.destroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/CommonDialog1.kt b/app/src/main/java/com/abbidot/tracker/dialog/CommonDialog1.kt new file mode 100644 index 0000000..60bcc0a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/CommonDialog1.kt @@ -0,0 +1,76 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.text.TextUtils +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogCommonLayoutBinding + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:通用弹窗 + */ +class CommonDialog1( + context: Context, + title: String = "", + content: String = "", + showCancelBtn: Boolean = true, + contentTextSize: Float = 0f, + contentTextStyle: Int = 0, + contentTextColor: Int = 0, + cancelable: Boolean = true, + canceledOnTouchOutside: Boolean = false, + okClickListener: OnDialogOkListener? = null, + cancelListener: OnDialogCancelListener? = null +) : BaseDialog(DialogCommonLayoutBinding::inflate, context) { + + private val mTitle = title + private val mContent = content + private val mContentTextSize = contentTextSize + private val mContentTextColor = contentTextColor + private val mContentTextStyle = contentTextStyle + private val isShowCancelBtn = showCancelBtn + private val isCancelable = cancelable + private val mCanceledOnTouchOutside = canceledOnTouchOutside + private val mOkClickListener = okClickListener + private val mCancelListener = cancelListener + + override fun initView() { + setCanceledOnTouchOutside(mCanceledOnTouchOutside) + setCancelable(isCancelable) + mViewBinding.apply { + if (TextUtils.isEmpty(mTitle)) { + tvCommonDialogTitle.visibility = View.GONE + } else { + tvCommonDialogTitle.visibility = View.VISIBLE + tvCommonDialogTitle.text = mTitle + } + tvCommonDialogContent.let { + it.text = mContent + if (mContentTextColor != 0) it.setTextColor( + ContextCompat.getColor( + context, mContentTextColor + ) + ) + if (mContentTextSize > 0) it.textSize = AppUtils.spToPx(mContentTextSize) + if (mContentTextStyle > 0) it.setTypeface(null, mContentTextStyle) + } + tbnCommonDialogNo.visibility = if (isShowCancelBtn) View.VISIBLE + else View.GONE + setOnClickListenerViews(tbnCommonDialogOk, tbnCommonDialogNo) + } + } + + override fun onClick(v: View) { + dismiss() + mViewBinding.apply { + when (v) { + tbnCommonDialogOk -> mOkClickListener?.onOkClick(this@CommonDialog1) + tbnCommonDialogNo -> mCancelListener?.onCancelClick(this@CommonDialog1) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/CommonDialog2.kt b/app/src/main/java/com/abbidot/tracker/dialog/CommonDialog2.kt new file mode 100644 index 0000000..33fb9cd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/CommonDialog2.kt @@ -0,0 +1,50 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.text.TextUtils +import android.view.View +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogPackageUpgradeBinding + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:通用弹窗,没有标题的 + */ +class CommonDialog2( + context: Context, + content: String = "", + okString: String = "", + okClickListener: OnDialogOkListener? = null, + cancelListener: OnDialogCancelListener? = null +) : BaseDialog(DialogPackageUpgradeBinding::inflate, context) { + + private val mContent = content + private val mOkString = okString + private val mOkClickListener = okClickListener + private val mCancelListener = cancelListener + + override fun initView() { + setCanceledOnTouchOutside(false) + mViewBinding.apply { + if (!TextUtils.isEmpty(mContent)) { + tvCommonDialog2Title.text = mContent + } + if (!TextUtils.isEmpty(mOkString)) { + btnDialogPackageUpgradeNow.text = mOkString + } + setOnClickListenerViews(btnDialogPackageUpgradeNow, tvDialogPackageUpgradeLaterBtn) + } + } + + + override fun onClick(v: View) { + dismiss() + mViewBinding.apply { + when (v) { + btnDialogPackageUpgradeNow -> mOkClickListener?.onOkClick(this@CommonDialog2) + tvDialogPackageUpgradeLaterBtn -> mCancelListener?.onCancelClick(this@CommonDialog2) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/CommonListDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/CommonListDialog.kt new file mode 100644 index 0000000..c1ba983 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/CommonListDialog.kt @@ -0,0 +1,42 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogListItemLayoutBinding +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:通用的列表弹窗 + */ +class CommonListDialog( + context: Context, + adapter: BaseRecyclerAdapter<*>, + topDP: Int = 0, + bottomDP: Int = 0, + itemBottomDP: Int = 0, + dialogHeightDp: Int = 0 +) : BaseDialog( + DialogListItemLayoutBinding::inflate, context, dialogHeightDp +) { + + private val mAdapter = adapter + private var mBottom = QMUIDisplayHelper.dpToPx(bottomDP) + private var mTop = QMUIDisplayHelper.dpToPx(topDP) + private val mItemBottomDP = QMUIDisplayHelper.dpToPx(itemBottomDP) + + override fun initView() { + mViewBinding.dialogListRecyclerView.apply { + if (mTop == 0) mTop = paddingTop + if (mBottom == 0) mBottom = paddingBottom + setPadding(paddingLeft, mTop, paddingRight, mBottom) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + context, this, mAdapter, bottom = mItemBottomDP + ) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/DFUNewDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/DFUNewDialog.kt new file mode 100644 index 0000000..3d424b2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/DFUNewDialog.kt @@ -0,0 +1,94 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogDfuNewBinding + +/** + *Created by .yzq on 2024/10/30/030. + * @link + * @description: + */ +class DFUNewDialog(context: Context) : + BaseDialog(DialogDfuNewBinding::inflate, context) { + + private var mVersion = "" + + override fun initView() { + setCanceledOnTouchOutside(false) + mViewBinding.apply { + btnDialogDfuNewUpgradeNow.setChangeAlphaWhenPress(true) + setOnClickListenerViews(tvDialogDfuNewLaterBtn, btnDialogDfuNewDone) + } + } + + fun startDfuState(version: String, onClick: () -> Unit) { + mVersion = version + val ver = String.format(mContext.getString(R.string.txt_new_firmware_version), mVersion) + mViewBinding.apply { + rlDialogDfuNewAfterLayout.visibility = View.GONE + llDialogDfuNewBeforeLayout.visibility = View.VISIBLE + tvDialogDfuNewTitle.text = ver + btnDialogDfuNewUpgradeNow.text = mContext.getString(R.string.txt_upgrade_now) + btnDialogDfuNewUpgradeNow.setOnClickListener { + inDFUState() + onClick() + } +0 } + } + + private fun inDFUState() { + val ver = String.format(mContext.getString(R.string.txt_about_version), " $mVersion") + mViewBinding.apply { + rlDialogDfuNewAfterLayout.visibility = View.VISIBLE + llDialogDfuNewBeforeLayout.visibility = View.GONE + tvDialogDfuNewProgressText.text = "0" + tvDialogDfuNewBottomVersion.text = ver + tvDialogDfuNewBottomVersion.visibility = View.VISIBLE + lavDialogDfuNewAnim.visibility = View.VISIBLE + tvDialogDfuNewTopVersion.text = ver + tvDialogDfuNewTopVersion.visibility = View.GONE + btnDialogDfuNewDone.visibility = View.GONE + lavDialogDfuNewAnim.setAnimation("lottie/dfu_loading.json") + lavDialogDfuNewAnim.playAnimation() + } + } + + fun setDFUProgress(progress: Int) { + if (progress == 0) return + mViewBinding.apply { + tvDialogDfuNewProgressText.text = "$progress" + if (progress == 100) { + lavDialogDfuNewAnim.cancelAnimation() + lavDialogDfuNewAnim.visibility = View.INVISIBLE + tvDialogDfuNewBottomVersion.visibility = View.GONE + tvDialogDfuNewTopVersion.visibility = View.VISIBLE + btnDialogDfuNewDone.visibility = View.VISIBLE + } + } + } + + fun setDFUFail(onClick: () -> Unit) { + mViewBinding.apply { + llDialogDfuNewBeforeLayout.visibility = View.VISIBLE + rlDialogDfuNewAfterLayout.visibility = View.GONE + tvDialogDfuNewTitle.text = mContext.getString(R.string.txt_upgrade_fail) + btnDialogDfuNewUpgradeNow.text = + mContext.getString(R.string.txt_pay_order_payment_again) + btnDialogDfuNewUpgradeNow.setOnClickListener { + inDFUState() + onClick() + } + } + } + + override fun onClick(v: View) { + mViewBinding.apply { + when (v) { + tvDialogDfuNewLaterBtn, btnDialogDfuNewDone -> dismiss() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/DialogUtils.kt b/app/src/main/java/com/abbidot/tracker/dialog/DialogUtils.kt new file mode 100644 index 0000000..61892b2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/DialogUtils.kt @@ -0,0 +1,191 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.content.DialogInterface +import android.text.InputFilter +import android.text.InputFilter.LengthFilter +import android.text.InputType +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.GetResultCallback +import com.qmuiteam.qmui.skin.QMUISkinManager +import com.qmuiteam.qmui.widget.dialog.QMUIDialog +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog + + +/** + *Created by .yzq on 2021/11/16/016. + * @link + * @description:通用提示Dialog + */ +class DialogUtils { + + companion object { + + private const val CURRENT_DIALOG_STYLE = R.style.QMUI_Dialog + + /** + * 信息提示Dialog + * QMUITipDialog.Builder.ICON_TYPE_SUCCESS + * QMUITipDialog.Builder.ICON_TYPE_FAIL + * view:通过view线程自动销毁 + */ + fun showTipDialog(context: Context, + view: View, + tipWord: CharSequence, + @QMUITipDialog.Builder.IconType iconType: Int, + isFinish: Boolean = false) { + + val tipDialog = if (QMUITipDialog.Builder.ICON_TYPE_NOTHING == iconType) { + QMUITipDialog.Builder(context).setTipWord(tipWord).create() + } else { + QMUITipDialog.Builder(context).setIconType(iconType).setTipWord(tipWord).create() + } + val activity = (context as BaseActivity<*>) + //activity前台显示运行才弹出 + if (activity.isFrontRunning) { + tipDialog.show() + view.postDelayed({ + tipDialog.dismiss() + if (isFinish && !activity.isFinishing) activity.finish() + }, 1500) + } + } + + /** + * 信息提示Dialog + * 显示文字提示信息 + * view:通过view线程自动销毁 + */ + fun showTipDialog(context: Context, + view: View, + tipWord: CharSequence, + isFinish: Boolean = false) { + showTipDialog(context, view, tipWord, QMUITipDialog.Builder.ICON_TYPE_NOTHING, + isFinish = isFinish) + } + + + /** + * 弹窗Dialog + */ + fun showMessageNegativeDialog(context: Context, + title: String, + message: String, + okStr: CharSequence, + noStr: CharSequence, + okListener: QMUIDialogAction.ActionListener, + noListener: QMUIDialogAction.ActionListener?) { + + var cancelListener: QMUIDialogAction.ActionListener? = noListener + if (null == noListener) { + cancelListener = QMUIDialogAction.ActionListener { dialog, _ -> dialog.dismiss() } + } + + QMUIDialog.MessageDialogBuilder(context).setTitle(title).setMessage(message) + .setSkinManager(QMUISkinManager.defaultInstance(context)) + .addAction(noStr, cancelListener).setCanceledOnTouchOutside(false) + .setCancelable(false) + //QMUIDialogAction.ACTION_PROP_NEGATIVE 字体红色 + .addAction(0, okStr, QMUIDialogAction.ACTION_PROP_NEGATIVE, okListener) + .create(CURRENT_DIALOG_STYLE).show() + } + + /** + * 没有标题的 + */ + fun showMessageNegativeDialog(context: Context, + message: String, + okStr: CharSequence, + noStr: CharSequence, + okListener: QMUIDialogAction.ActionListener, + noListener: QMUIDialogAction.ActionListener?) { + showMessageNegativeDialog(context, "", message, okStr, noStr, okListener, noListener) + + } + + /** + * 没有标题的 + * 确定按钮文字 + */ + fun showMessageNegativeDialog(context: Context, + message: String, + okListener: QMUIDialogAction.ActionListener, + noListener: QMUIDialogAction.ActionListener?) { + showMessageNegativeDialog(context, "", message, context.getString(R.string.txt_sure), + context.getString(R.string.txt_cancel), okListener, noListener) + } + + /** + * 确定按钮文字 + */ + fun showMessageNegativeDialog(context: Context, + title: String, + message: String, + okListener: QMUIDialogAction.ActionListener, + noListener: QMUIDialogAction.ActionListener?) { + showMessageNegativeDialog(context, title, message, context.getString(R.string.txt_sure), + context.getString(R.string.txt_cancel), okListener, noListener) + } + + /** + * 显示菜单按钮弹窗 + */ + fun showMenuDialog(context: Context, + items: Array, + listener: DialogInterface.OnClickListener) { + QMUIDialog.MenuDialogBuilder(context) + .setSkinManager(QMUISkinManager.defaultInstance(context)) + .addItems(items, listener).create(CURRENT_DIALOG_STYLE).show() + } + + /** + * 有标题的输入弹窗 + */ + fun showEditTextDialog(context: Context, + title: String, + hint: String, + content: String, + getResultCallback: GetResultCallback, + inputType: Int = InputType.TYPE_CLASS_TEXT, + maxLength: Int = 0): QMUIDialog.EditTextDialogBuilder { + val editTextDialogBuilder = QMUIDialog.EditTextDialogBuilder(context) + + editTextDialogBuilder.setTitle(title).setDefaultText(content) + .setSkinManager(QMUISkinManager.defaultInstance(context)).setPlaceholder(hint) + .setInputType(inputType).addAction(context.getString( + R.string.txt_cancel)) { dialog, _ -> dialog.dismiss() } + .addAction(context.getString(R.string.txt_sure)) { dialog, index -> + dialog.dismiss() + getResultCallback.onResult(editTextDialogBuilder.editText.text.toString()) + }.create(CURRENT_DIALOG_STYLE).show() + + editTextDialogBuilder.editText.apply { + setSelection(content.length) + if (maxLength > 0) { + //手动设置maxLength为maxLength + val filters = arrayOf(LengthFilter(maxLength)) + setFilters(filters) + } + } + return editTextDialogBuilder + } + + /** + * 没有标题的输入弹窗 + */ + fun showEditTextDialog(context: Context, + hint: String, + content: String, + getResultCallback: GetResultCallback, + inputType: Int = InputType.TYPE_CLASS_TEXT, + maxLength: Int = 0): QMUIDialog.EditTextDialogBuilder { + return showEditTextDialog(context, "", hint, content, getResultCallback, + inputType = inputType, maxLength = maxLength) + } + + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/MyAlertDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/MyAlertDialog.kt new file mode 100644 index 0000000..6fee08a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/MyAlertDialog.kt @@ -0,0 +1,88 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.text.TextUtils +import android.view.View +import androidx.annotation.StyleRes +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogMyLayoutBinding +import com.abbidot.tracker.widget.TypefaceRoundButton +import com.abbidot.tracker.widget.TypefaceTextView + +/** + *Created by .yzq on 2021/12/24/024. + * @link + * @description: 自定义对话框,背景阴影 + */ +class MyAlertDialog( + context: Context, @StyleRes themeResId: Int = R.style.my_Dialog_style +) : BaseDialog( + DialogMyLayoutBinding::inflate, context, dialogHeightDp = 260, themeResId = themeResId +) { + + private lateinit var mTitleTextView: TypefaceTextView + private lateinit var mContentTextView: TypefaceTextView + private lateinit var mCancelRoundButton: TypefaceRoundButton + private lateinit var mOkRoundButton: TypefaceRoundButton + + override fun initView() { + setCancelable(false) + + mTitleTextView = mViewBinding.myDialogTitle + mContentTextView = mViewBinding.myDialogContent + mCancelRoundButton = mViewBinding.myDialogCancel + mOkRoundButton = mViewBinding.myDialogOk + + //设置按下点击透明效果 + mCancelRoundButton.setChangeAlphaWhenPress(true) + mOkRoundButton.setChangeAlphaWhenPress(true) + } + + fun setOnDialogCancelListener(cancelListener: OnDialogCancelListener) { + mCancelRoundButton.setOnClickListener { + cancelListener.onCancelClick(this) + } + } + + fun setOnDialogOkListener(okListener: OnDialogOkListener) { + mOkRoundButton.setOnClickListener { + okListener.onOkClick(this) + } + } + + fun setTipTitle(title: String) { + if (!TextUtils.isEmpty(title)) { + mTitleTextView.visibility = View.VISIBLE + mTitleTextView.text = title + } + } + + fun setTipTitle(resId: Int) { + setTipTitle(context.getString(resId)) + } + + fun setContent(title: String) { + mContentTextView.text = title + } + + fun setContent(resId: Int) { + setContent(context.getString(resId)) + } + + fun setOkText(text: String) { + mOkRoundButton.text = text + } + + fun setOkText(resId: Int) { + setOkText(context.getString(resId)) + } + + fun setCancelText(text: String) { + mCancelRoundButton.text = text + } + + fun setCancelText(resId: Int) { + setCancelText(context.getString(resId)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/PackageUpgradeDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/PackageUpgradeDialog.kt new file mode 100644 index 0000000..fe49c33 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/PackageUpgradeDialog.kt @@ -0,0 +1,85 @@ +package com.abbidot.tracker.dialog + +import android.content.Intent +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.DialogPackageUpgradeBinding +import com.abbidot.tracker.ui.activity.subscribe.ChoosePlanActivity +import com.abbidot.tracker.vm.SubscriptionManageViewModel + +/** + *Created by .yzq on 2024/9/27/027. + * @link + * @description:升级套餐弹窗 + */ +class PackageUpgradeDialog(activity: BaseActivity<*>, deviceId: String) : + BaseDialog(DialogPackageUpgradeBinding::inflate, activity) { + + private val mBaseActivity = activity + private val mDeviceId = deviceId + + override fun initView() { + mViewBinding.apply { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) { + tvDialogPackageUpgradeLaterBtn.visibility = View.VISIBLE + btnDialogPackageUpgradeNow.setText(R.string.txt_upgrade_now) + } else { + tvDialogPackageUpgradeLaterBtn.visibility = View.GONE + btnDialogPackageUpgradeNow.setText(R.string.txt_ok) + } + + setOnClickListenerViews(btnDialogPackageUpgradeNow, tvDialogPackageUpgradeLaterBtn) + } + } + + private fun goUpgrade() { + ViewModelProvider(mBaseActivity)[SubscriptionManageViewModel::class.java].apply { + mSubscriptionsOrderLiveData.observe(this@PackageUpgradeDialog) { + mBaseActivity.dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { data -> + for (d in data) { + if (d.deviceId == mDeviceId) { + val intent = + Intent(mBaseActivity, ChoosePlanActivity::class.java) + intent.putExtra( + ConstantString.Title, "Premium" + ) + intent.putExtra(ConstantString.Upgrade, true) + intent.putExtra(ConstantString.LkSetMeal, d) + mBaseActivity.startActivity(intent) + dismiss() + break + } + } + } + } + }) + } + getSubscriptionsOrder() + } + } + + override fun onClick(v: View) { + mViewBinding.apply { + when (v) { + btnDialogPackageUpgradeNow -> { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) goUpgrade() + else dismiss() + } + + tvDialogPackageUpgradeLaterBtn -> dismiss() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/PrivacyPolicyDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/PrivacyPolicyDialog.kt new file mode 100644 index 0000000..f523921 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/PrivacyPolicyDialog.kt @@ -0,0 +1,58 @@ +package com.abbidot.tracker.dialog + +import android.app.Activity +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogLegalInfoBinding +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2023/3/30/030. + * @link + * @description: + */ +class PrivacyPolicyDialog(activity: Activity) : + BaseDialog(DialogLegalInfoBinding::inflate, activity) { + + private val mActivity = activity + + override fun initView() { + setCancelable(false) + mViewBinding.apply { + tvDialogLegalInfoPrivacy.setOnClickListener(this@PrivacyPolicyDialog) + tvDialogLegalInfoTermsOfService.setOnClickListener(this@PrivacyPolicyDialog) + btnDialogLegalInfoReject.setChangeAlphaWhenPress(true) + btnDialogLegalInfoReject.setOnClickListener(this@PrivacyPolicyDialog) + } + } + + /** + * 点击同意的回调 + */ + fun agreePrivacyPolicyCallback(onListener: View.OnClickListener) { + mViewBinding.btnDialogLegalInfoAccept.apply { + setChangeAlphaWhenPress(true) + setOnClickListener(onListener) + } + } + + /** + * 是否同意协议 + */ + fun isAgree() = mViewBinding.boxReadAndAccept.isChecked + + override fun onClick(v: View) { + when (v.id) { + R.id.tv_dialog_legal_info_privacy -> ViewUtil.instance.goPrivacyPolicy(mActivity) + R.id.tv_dialog_legal_info_terms_of_service -> ViewUtil.instance.goServiceContract( + mActivity + ) + + R.id.btn_dialog_legal_info_reject -> { + dismiss() + mActivity.finish() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/SelectMapListDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/SelectMapListDialog.kt new file mode 100644 index 0000000..5c467c3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/SelectMapListDialog.kt @@ -0,0 +1,33 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogSelectMapLayoutBinding +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:选择多个地图导航列表弹窗 + */ +class SelectMapListDialog( + context: Context, + adapter: BaseRecyclerAdapter<*> +) : BaseDialog(DialogSelectMapLayoutBinding::inflate, context) { + + private var mAdapter = adapter + + + override fun initView() { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout(context, mViewBinding.selectMapRecyclerView, + mAdapter, bottom = 40) + + mViewBinding.btnCancelSelectMap.let { + it.setChangeAlphaWhenPress(true) + it.setOnClickListener { + dismiss() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/SelectMapTypeDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/SelectMapTypeDialog.kt new file mode 100644 index 0000000..6bbd827 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/SelectMapTypeDialog.kt @@ -0,0 +1,61 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.widget.CompoundButton +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.databinding.DialogSelectMapTypeLayoutBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:选择地图类型弹窗 + */ +class SelectMapTypeDialog( + context: Context, + adapter: BaseRecyclerAdapter<*>, + checkedChangeListener: CompoundButton.OnCheckedChangeListener? = null +) : BaseDialog( + DialogSelectMapTypeLayoutBinding::inflate, context +) { + private var mAdapter = adapter + private val mCheckedChangeListener = checkedChangeListener + + override fun initView() { + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewFlexboxLayout( + context, rvShowMapTypeList, mAdapter, right = AppUtils.dpToPx(58) + ) + cbDialogMapFencesSwitch.isChecked = Util.getShowFenceSp() + cbDialogMapFencesSwitch.setOnCheckedChangeListener(mCheckedChangeListener) + } + } + + /** + * 检测地图类型有没有改变 + */ + fun mapTypeSpToUpdate() { + val mapType = Util.getMapTypeSp() + val list = mAdapter.getData() + for (i in 0 until list.size) { + val l = list[i] + if (l is DataBean) { + if (i == mapType && !l.selected) { + l.selected = true + mAdapter.notifyItemChanged(i) + } else if (i != mapType && l.selected) { + l.selected = false + mAdapter.notifyItemChanged(i) + } + } + } + } + + fun setFencesSwitch(checked: Boolean) { + mViewBinding.cbDialogMapFencesSwitch.isChecked = checked + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/SelectPetLabelDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/SelectPetLabelDialog.kt new file mode 100644 index 0000000..3bd8ea9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/SelectPetLabelDialog.kt @@ -0,0 +1,61 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogSelectPetLabelLayoutBinding +import com.abbidot.tracker.util.ViewUtil +import com.google.android.flexbox.FlexboxLayoutManager + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:选择多个地图导航列表弹窗 + */ +class SelectPetLabelDialog( + context: Context, + adapter: BaseRecyclerAdapter<*>, +) : BaseDialog( + DialogSelectPetLabelLayoutBinding::inflate, context +) { + + private val mAdapter = adapter + private var mOkListener: OnDialogOkListener? = null + + override fun initView() { + mViewBinding.apply { + rvDialogPetLabels.let { + val flexboxLayoutManager = FlexboxLayoutManager(mContext).apply { +// justifyContent = JustifyContent.SPACE_AROUND +// alignItems=AlignItems.FLEX_START + } + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, + rvDialogPetLabels, + mAdapter, + right = AppUtils.dpToPx(6), + bottom = AppUtils.dpToPx(8) + ) + } + + setOnClickListenerViews(btnDialogPetLabelsCancel, btnDialogPetLabelsSave) + } + } + + fun setOkListener(okListener: OnDialogOkListener) { + mOkListener = okListener + } + + override fun onClick(v: View) { + mViewBinding.apply { + when (v) { + btnDialogPetLabelsCancel -> dismiss() + btnDialogPetLabelsSave -> { + mOkListener?.onOkClick(this@SelectPetLabelDialog) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/SetPetWeightDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/SetPetWeightDialog.kt new file mode 100644 index 0000000..4bf9bbc --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/SetPetWeightDialog.kt @@ -0,0 +1,163 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.os.Build +import android.text.TextUtils +import android.widget.NumberPicker +import android.widget.NumberPicker.OnValueChangeListener +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.databinding.DialogSetPetWeightLayoutBinding +import com.abbidot.tracker.util.NumberPickerValueFill0Format +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2024/2/20/020. + * @link + * @description: + */ +class SetPetWeightDialog( + context: Context, petBean: PetBean, onSelectListener: OnDialogSelectListener? = null +) : BaseDialog( + DialogSetPetWeightLayoutBinding::inflate, context +), OnValueChangeListener { + + private val mOnSelectListener = onSelectListener + private var mPetBean = petBean + + override fun initView() { + mViewBinding.apply { + + var maxTensValue: Int + var minTensValue: Int + var defaultValue1: Int + var defaultValue2 = 0 + mPetBean.let { + defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + it.petType, ConstantInt.WeightType + ) + minTensValue = Util.getPetHeightAndWeightRangeValue( + it.petType, ConstantInt.WeightType, ConstantInt.MinType + ) + maxTensValue = Util.getPetHeightAndWeightRangeValue( + it.petType, ConstantInt.WeightType, ConstantInt.MaxType + ) + if (it.weight > 0) { + var convertWeight = Util.metricConvertUnits(it.weight, ConstantInt.Type1) + if (convertWeight < minTensValue) { + convertWeight = minTensValue.toFloat() + } + val values = convertWeight.toString().split(".") + defaultValue1 = values[0].toInt() + defaultValue2 = values[1].toInt() + } + } + + tvDialogWeightSetUnit.text = Util.getMetricUnits(context, ConstantInt.Type1) + + npDialogPetWeightTens.let { + ViewUtil.instance.setNumberPickerAttributes( + context, + it, + minTensValue, + maxTensValue, + defaultValue1, + 20, + NumberPickerValueFill0Format(), + this@SetPetWeightDialog + ) + } + + val valueList = mutableListOf() + for (i in 0..9) { + valueList.add("$i") + } + npDialogPetWeightUnit.let { + ViewUtil.instance.setNumberPickerAttributes( + context, + it, + 0, + 9, + defaultValue2, + 20, + valueChangeListener = this@SetPetWeightDialog + ) + it.displayedValues = valueList.toTypedArray() + } + npDialogWeightUnitSwitch.let { + ViewUtil.instance.setNumberPickerAttributes( + context, it, 0, 1, 0, 20 + ) + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + it.selectionDividerHeight = 0 + } + val unitArray = + arrayOf(context.getString(R.string.txt_lb), context.getString(R.string.txt_kg)) + it.displayedValues = unitArray + } + + val value = "${npDialogPetWeightTens.value}.${npDialogPetWeightUnit.value}" + mOnSelectListener?.onSelectClick(this@SetPetWeightDialog, value) + } + } + + override fun onValueChange(picker: NumberPicker?, oldVal: Int, newVal: Int) { + var value = "" + mViewBinding.apply { + when (picker!!) { + npDialogPetWeightTens -> { + value = "${newVal}.${npDialogPetWeightUnit.value}" + } + + npDialogPetWeightUnit -> { + value = "${npDialogPetWeightTens.value}.${newVal}" + } + } + } + if (!TextUtils.isEmpty(value) && value != "0.0") { + mOnSelectListener?.onSelectClick(this@SetPetWeightDialog, value) + } + } + + + fun updatePetData(petBean: PetBean) { + mPetBean = petBean + mViewBinding.apply { + petBean.let { + var defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + it.petType, ConstantInt.WeightType + ) + var defaultValue2 = 0 + val minTensValue = Util.getPetHeightAndWeightRangeValue( + it.petType, ConstantInt.WeightType, ConstantInt.MinType + ) + val maxTensValue = Util.getPetHeightAndWeightRangeValue( + it.petType, ConstantInt.WeightType, ConstantInt.MaxType + ) + if (it.weight > 0) { + var convertWeight = Util.metricConvertUnits(it.weight, ConstantInt.Type1) + if (convertWeight < minTensValue) { + convertWeight = minTensValue.toFloat() + } + val values = convertWeight.toString().split(".") + defaultValue1 = values[0].toInt() + defaultValue2 = values[1].toInt() + } + + npDialogPetWeightTens.let { np -> + ViewUtil.instance.setNumberPickerAttributes( + context, np, minTensValue, maxTensValue, defaultValue1 + ) + } + npDialogPetWeightUnit.let { np -> + ViewUtil.instance.setNumberPickerAttributes( + context, np, 0, 9, defaultValue2 + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/ShowCalenderAndTimeDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/ShowCalenderAndTimeDialog.kt new file mode 100644 index 0000000..7ff2fde --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/ShowCalenderAndTimeDialog.kt @@ -0,0 +1,205 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.view.View +import android.widget.NumberPicker +import android.widget.NumberPicker.OnValueChangeListener +import android.widget.TextView +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogCalenderAndTimeLayoutBinding +import com.abbidot.tracker.util.NumberPickerValueFill0Format +import com.abbidot.tracker.util.ViewUtil +import java.util.Calendar + + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:显示日历弹窗 + * @param canSelectFutureDate 平时正常日期,true不能选择未来时间 + */ +class ShowCalenderAndTimeDialog( + context: Context, + showCalenderTextView: TextView, + okListener: OnDialogOkListener? = null, + format: String = Utils.DATE_FORMAT_PATTERN_EN1, + canSelectFutureDate: Boolean = false, + calenderShowTimestamp: Long = 0, + minYear: Int = 2023 +) : BaseDialog( + DialogCalenderAndTimeLayoutBinding::inflate, context +), OnValueChangeListener { + + private val mShowCalenderTextView = showCalenderTextView + private var mShowYearMonthDialog: ShowYearMonthDialog? = null + private lateinit var mMonths: Array + private var mShowYear = 0 + private var mShowMonth = 0 + private val mCanSelectFutureDate = canSelectFutureDate + private val mDateFormat = format + private val mOkListener = okListener + + private val mMinYear = minYear + private val mCalenderShowTimestamp = calenderShowTimestamp + + override fun initView() { + mMonths = context.resources.getStringArray(R.array.array_month) + + mViewBinding.apply { + llDialogCalenderLayout.cvShowSelectCalender.let { + mShowYear = it.curYear + mShowMonth = it.curMonth + if (!mCanSelectFutureDate) { + val calendar = Calendar.getInstance() + val cYear = calendar.get(Calendar.YEAR) + val month = calendar.get(Calendar.MONTH) + 1 + val day = calendar.get(Calendar.DAY_OF_MONTH) + it.setRange(mMinYear, 1, 1, cYear, month, day) + } + updateMonthYear() + + it.setOnMonthChangeListener { year, month -> + mShowMonth = month + mShowYear = year + updateMonthYear() + } + } + + npDialogCalenderHour.let { + ViewUtil.instance.setNumberPickerAttributes( + context, + it, + 0, + 23, + 5, + 24, + NumberPickerValueFill0Format(), + this@ShowCalenderAndTimeDialog + ) + } + npDialogCalenderMin.let { + ViewUtil.instance.setNumberPickerAttributes( + context, + it, + 0, + 59, + 5, + 24, + NumberPickerValueFill0Format(), + this@ShowCalenderAndTimeDialog + ) + } + + //根据传入的时间戳进行还原显示 + setSelectDate(mCalenderShowTimestamp) + + setOnClickListenerViews( + llDialogCalenderLayout.ivSelectCalendarMonthLeft, + llDialogCalenderLayout.ivSelectCalendarMonthRight, + btnSelectCalenderHourMinCancel, + btnSelectCalenderHourMinOk, + llDialogCalenderLayout.tvSelectCalendarYearMonth + ) + } + } + + /** + * 根据传入的时间戳进行还原显示 + * @param showTimestamp 13位时间戳 + */ + fun setSelectDate(showTimestamp: Long) { + if (showTimestamp > 0) { + mViewBinding.apply { + val calendar = Calendar.getInstance().apply { + timeInMillis = showTimestamp + } + llDialogCalenderLayout.cvShowSelectCalender.scrollToCalendar( + calendar.get(Calendar.YEAR), + calendar.get(Calendar.MONTH) + 1, + calendar.get(Calendar.DAY_OF_MONTH) + ) + npDialogCalenderHour.value = calendar.get(Calendar.HOUR_OF_DAY) + npDialogCalenderMin.value = calendar.get(Calendar.MINUTE) + } + } + } + + + private fun updateMonthYear() { + val curMonthYear = "${mMonths[mShowMonth - 1]} $mShowYear" + mViewBinding.llDialogCalenderLayout.tvSelectCalendarYearMonth.text = curMonthYear + } + + override fun onClick(v: View) { + mViewBinding.apply { + when (v) { + llDialogCalenderLayout.tvSelectCalendarYearMonth -> { + if (null == mShowYearMonthDialog) { + mShowYearMonthDialog = ShowYearMonthDialog( + context, llDialogCalenderLayout.cvShowSelectCalender + ) + } + mShowYearMonthDialog?.show() + } + + llDialogCalenderLayout.ivSelectCalendarMonthRight -> { + llDialogCalenderLayout.cvShowSelectCalender.scrollToNext(true) + } + + llDialogCalenderLayout.ivSelectCalendarMonthLeft -> { + llDialogCalenderLayout.cvShowSelectCalender.scrollToPre(true) + } + + btnSelectCalenderHourMinCancel -> dismiss() + btnSelectCalenderHourMinOk -> { + val calendar = llDialogCalenderLayout.cvShowSelectCalender.selectedCalendar + val selectMonthYear = "${calendar.year}-${calendar.month}-${calendar.day} ${ + Utils.fill2Digits(npDialogCalenderHour.value) + }:${Utils.fill2Digits(npDialogCalenderMin.value)}:00" + val timesTamp = Utils.stringToTimestamp(selectMonthYear) + val nowTimestamp = System.currentTimeMillis() + if (timesTamp > nowTimestamp) { + mShowCalenderTextView.text = Utils.formatTime(nowTimestamp, mDateFormat) + } else { + mShowCalenderTextView.text = Utils.stringToDate( + selectMonthYear, Utils.DATE_FORMAT_PATTERN_CN2, mDateFormat + ) + } + mOkListener?.onOkClick(this@ShowCalenderAndTimeDialog) + dismiss() + } + } + } + } + + override fun onValueChange(picker: NumberPicker, oldVal: Int, newVal: Int) { + } + + + //利用反射修改线的颜色,目前没用 +// private fun setNumberPickerDividerColor(context: Context, numberPicker: NumberPicker) { +// val pickerFields: Array = NumberPicker::class.java.declaredFields +// for (pf in pickerFields) { +// if (pf.name.equals("mSelectionDivider")) { +// pf.isAccessible = true +// try { +// //设置分割线的颜色值 +// pf.set( +// numberPicker, ColorDrawable( +// ContextCompat.getColor(context, R.color.red) +// ) +// ) +// } catch (e: IllegalArgumentException) { +// e.printStackTrace() +// } catch (e: Resources.NotFoundException) { +// e.printStackTrace() +// } catch (e: IllegalAccessException) { +// e.printStackTrace() +// } +// break +// } +// } +// } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/ShowCalenderDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/ShowCalenderDialog.kt new file mode 100644 index 0000000..c51825c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/ShowCalenderDialog.kt @@ -0,0 +1,143 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogCalenderLayoutBinding +import com.haibin.calendarview.Calendar +import com.haibin.calendarview.CalendarView + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:显示日历弹窗 + * @param canSelectFutureDate 平时正常日期,true不能选择未来时间 + * @param minLastDayRange 最小天数能选最近多少天,0表示没有限制 + */ +class ShowCalenderDialog( + context: Context, + onSelectListener: OnDialogSelectListener? = null, + canSelectFutureDate: Boolean = false, + calenderShowTimestamp: Long = 0, + minYear: Int = 0, minLastDayRange: Int = 0, +) : BaseDialog(DialogCalenderLayoutBinding::inflate, context) { + + private val mCanSelectFutureDate = canSelectFutureDate + private val mOnSelectListener = onSelectListener + private val mCalenderShowTimestamp = calenderShowTimestamp + + private var mShowYearMonthDialog: ShowYearMonthDialog? = null + private lateinit var mMonths: Array + private var mShowYear = 0 + private var mShowMonth = 0 + private var mMinYear = minYear + private val mMinLastDayRange = minLastDayRange + + override fun initView() { + mMonths = context.resources.getStringArray(R.array.array_month) + mViewBinding.apply { + cvShowSelectCalender.let { + mShowYear = it.curYear + if (mMinYear == 0) mMinYear = mShowYear - 1 + mShowMonth = it.curMonth + if (!mCanSelectFutureDate) { + val calendar = java.util.Calendar.getInstance() + val cYear = calendar.get(java.util.Calendar.YEAR) + val month = calendar.get(java.util.Calendar.MONTH) + 1 + val day = calendar.get(java.util.Calendar.DAY_OF_MONTH) + if (mMinLastDayRange > 0) { + val minTimestamp = Utils.getBeforeHowTimestamp( + calendar.timeInMillis, mMinLastDayRange.toLong() + ) + val minDate = + Utils.formatTime(minTimestamp, Utils.DATE_FORMAT_PATTERN_CN).split("-") + it.setRange( + minDate[0].toInt(), + minDate[1].toInt(), + minDate[2].toInt(), + cYear, + month, + day + ) + } else { + it.setRange(mMinYear, 1, 1, cYear, month, day) + } + } + updateMonthYear() + + it.setOnMonthChangeListener { year, month -> + mShowMonth = month + mShowYear = year + updateMonthYear() + } + + it.setOnCalendarSelectListener(object : CalendarView.OnCalendarSelectListener { + override fun onCalendarOutOfRange(calendar: Calendar?) { + + } + + override fun onCalendarSelect(calendar: Calendar, isClick: Boolean) { + if (!isClick) return + mOnSelectListener?.onSelectClick(this@ShowCalenderDialog, calendar) + dismiss() + } + }) + + //根据传入的时间戳进行还原显示 + setSelectDate(mCalenderShowTimestamp) + } + + setOnClickListenerViews( + ivSelectCalendarMonthLeft, ivSelectCalendarMonthRight, tvSelectCalendarYearMonth + ) + } + } + + /** + * 根据传入的时间戳进行还原显示 + * @param showTimestamp 13位时间戳 + */ + fun setSelectDate(showTimestamp: Long) { + if (showTimestamp > 0) { + val calendar = java.util.Calendar.getInstance().apply { + timeInMillis = showTimestamp + } + mViewBinding.cvShowSelectCalender.scrollToCalendar( + calendar.get(java.util.Calendar.YEAR), + calendar.get(java.util.Calendar.MONTH) + 1, + calendar.get(java.util.Calendar.DAY_OF_MONTH) + ) + } + } + + private fun updateMonthYear() { + val curMonthYear = "${mMonths[mShowMonth - 1]} $mShowYear" + mViewBinding.tvSelectCalendarYearMonth.text = curMonthYear + } + + + override fun onClick(v: View) { + mViewBinding.apply { + when (v) { + ivSelectCalendarMonthRight -> { + cvShowSelectCalender.scrollToNext(true) + } + + ivSelectCalendarMonthLeft -> { + cvShowSelectCalender.scrollToPre(true) + } + + tvSelectCalendarYearMonth -> { + if (null == mShowYearMonthDialog) { + mShowYearMonthDialog = ShowYearMonthDialog( + context, cvShowSelectCalender, minYear = mMinYear + ) + } + mShowYearMonthDialog?.show() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/ShowYearMonthDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/ShowYearMonthDialog.kt new file mode 100644 index 0000000..6c03848 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/ShowYearMonthDialog.kt @@ -0,0 +1,114 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.view.View +import android.widget.NumberPicker +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.databinding.DialogYearAndMonthLayoutBinding +import com.abbidot.tracker.util.ViewUtil +import com.haibin.calendarview.CalendarView +import com.hjq.toast.Toaster +import java.util.Calendar + + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:显示日历弹窗 + * @param canSelectFutureDate 平时正常日期,true不能选择未来时间 + */ +class ShowYearMonthDialog( + context: Context, + calendarView: CalendarView, + canSelectFutureDate: Boolean = false, + minYear: Int = 2023 +) : BaseDialog(DialogYearAndMonthLayoutBinding::inflate, context), + NumberPicker.OnValueChangeListener { + + private val mCalendarView = calendarView + private lateinit var mMonths: Array + + private lateinit var mCalendar: Calendar + private val mCanSelectFutureDate = canSelectFutureDate + private val mMinYear = minYear + + override fun initView() { +// mMonths=context.resources.getStringArray(R.array.month_string_array) + mMonths = context.resources.getStringArray(R.array.array_month) + + mCalendar = Calendar.getInstance() + val cYear = mCalendar.get(Calendar.YEAR) + val month = if (mCanSelectFutureDate) { + 12 + } else mCalendar.get(Calendar.MONTH) + 1 + + mViewBinding.apply { + + npDialogYear.let { + ViewUtil.instance.setNumberPickerAttributes( + context, + it, + mMinYear, + cYear, + cYear, + 16, + valueChangeListener = this@ShowYearMonthDialog + ) + } + + npDialogMonth.let { + ViewUtil.instance.setNumberPickerAttributes( + context, it, 1, month, month, 16, valueChangeListener = this@ShowYearMonthDialog + ) + it.displayedValues = mMonths + } + + setOnClickListenerViews(btnSelectYearMonthOk, btnSelectYearMonthCancel) + } + } + + override fun onClick(v: View) { + mViewBinding.apply { + when (v) { + btnSelectYearMonthCancel -> dismiss() + btnSelectYearMonthOk -> { + val year = npDialogYear.value + val month = npDialogMonth.value + mCalendarView.minRangeCalendar.let { + //确定后只能选择最小的年月日 + if (it.year >= year) { + if (it.month == month) { + mCalendarView.scrollToCalendar(it.year, it.month, it.day, true) + } else if (it.month > month) { + Toaster.show(R.string.txt_time_out_of_range) + mCalendarView.scrollToCalendar(it.year, it.month, it.day, true) + } else mCalendarView.scrollToCalendar(year, month, 1, true) + } else { + mCalendarView.scrollToCalendar(year, month, 1, true) + } + } + dismiss() + } + } + } + } + + override fun onValueChange(picker: NumberPicker, oldVal: Int, newVal: Int) { + mViewBinding.apply { + when (picker) { + npDialogYear -> { + if (!mCanSelectFutureDate) { + val month = mCalendar.get(Calendar.MONTH) + 1 // 月份是从0开始的 + val monthMax = if (newVal == mCalendar.get(Calendar.YEAR)) { + month + } else { + 12 + } + npDialogMonth.maxValue = monthMax + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/dialog/TrackerDFUStateDialog.kt b/app/src/main/java/com/abbidot/tracker/dialog/TrackerDFUStateDialog.kt new file mode 100644 index 0000000..c51983f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/dialog/TrackerDFUStateDialog.kt @@ -0,0 +1,87 @@ +package com.abbidot.tracker.dialog + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DFUStateBean +import com.abbidot.tracker.databinding.DialogTrackerDfuListLayoutBinding +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description:TrackerDFU升级弹窗 + */ +class TrackerDFUStateDialog(context: Context, adapter: BaseRecyclerAdapter) : + BaseDialog( + DialogTrackerDfuListLayoutBinding::inflate, context + ) { + + private val mDFUStateList = mutableListOf() + + private val mAdapter = adapter + + override fun initView() { + setCancelable(false) + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + context, trackDfuStateListRecyclerView, mAdapter, bottom = 0 + ) + btnTrackDfuOk.setChangeAlphaWhenPress(true) + btnTrackDfuCancel.setChangeAlphaWhenPress(true) + btnTrackDfuCancel.setOnClickListener { + dismiss() + } + } + } + + fun setContent(string: String) { + mViewBinding.apply { + tvTrackDfuContent.text = string + btnTrackDfuOk.visibility = View.VISIBLE + btnTrackDfuCancel.visibility = View.VISIBLE + svTrackDfuContent.visibility = View.VISIBLE + trackDfuStateListRecyclerView.visibility = View.GONE + mDFUStateList.clear() + } + } + + fun startSureDFU(onClick: () -> Unit) { + mViewBinding.btnTrackDfuOk.setOnClickListener { + mViewBinding.apply { + btnTrackDfuOk.visibility = View.GONE + btnTrackDfuCancel.visibility = View.GONE + svTrackDfuContent.visibility = View.GONE + trackDfuStateListRecyclerView.visibility = View.VISIBLE + } + onClick() + } + } + + fun addData(data: DFUStateBean) { + var isAdd = false + var potion = 0 + for (i in 0 until mDFUStateList.size) { + val dfuStateBean = mDFUStateList[i] + if (dfuStateBean.stateName == data.stateName) { + dfuStateBean.filePath = data.filePath + dfuStateBean.progress = data.progress + isAdd = true + potion = i + break + } + } + if (isAdd) { + mAdapter.notifyItemChanged(potion) + } else { + LogUtil.e("未添加过${mDFUStateList.size}") + mDFUStateList.add(data) + mAdapter.setData(mDFUStateList) + mAdapter.notifyDataSetChanged() + //自动定位滚动最后一个item + mViewBinding.trackDfuStateListRecyclerView.scrollToPosition(mDFUStateList.size - 1) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/diff/FeedbackImageDiffCallBack.kt b/app/src/main/java/com/abbidot/tracker/diff/FeedbackImageDiffCallBack.kt new file mode 100644 index 0000000..2f430df --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/diff/FeedbackImageDiffCallBack.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.diff + +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.bean.MenuTxtBean + +/** + *Created by .yzq on 2022/3/24/024. + * @link + * @description:选意见反馈图片的数据比较差异 + */ +class FeedbackImageDiffCallBack(oldList: MutableList, + newList: MutableList) : + BaseDiffCallBack(oldList, newList) { + + private var mOldList = oldList + private val mNewList = newList + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return mOldList[oldItemPosition].imageUrl == mNewList[newItemPosition].imageUrl + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return mOldList[oldItemPosition].imageUrl == mNewList[newItemPosition].imageUrl + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/diff/NotificationsDiffUtil.kt b/app/src/main/java/com/abbidot/tracker/diff/NotificationsDiffUtil.kt new file mode 100644 index 0000000..2837686 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/diff/NotificationsDiffUtil.kt @@ -0,0 +1,19 @@ +package com.abbidot.tracker.diff + +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.tracker.bean.MessageBean + +/** + *Created by .yzq on 2021/12/31/031. + * @link + * @description:通知消息diffutil,多布局的没有写继承封装 + */ +class NotificationsDiffUtil : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: MessageBean, newItem: MessageBean): Boolean { + return oldItem.messageId == newItem.messageId + } + + override fun areContentsTheSame(oldItem: MessageBean, newItem: MessageBean): Boolean { + return oldItem.title == newItem.title && oldItem.message == newItem.message && oldItem.isContainImg == newItem.isContainImg && oldItem.isSelect == newItem.isSelect + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/diff/PaymentMethodDiffCallBack.kt b/app/src/main/java/com/abbidot/tracker/diff/PaymentMethodDiffCallBack.kt new file mode 100644 index 0000000..2e13e88 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/diff/PaymentMethodDiffCallBack.kt @@ -0,0 +1,33 @@ +package com.abbidot.tracker.diff + +import android.text.TextUtils +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.bean.MenuTxtBean + + +/** + *Created by .yzq on 2022/3/29/029. + * @link + * @description: + */ +class PaymentMethodDiffCallBack(oldList: MutableList, + newList: MutableList) : + BaseDiffCallBack(oldList, newList) { + + private var mOldList = oldList + private val mNewList = newList + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val old = mOldList[oldItemPosition] + val new = mNewList[newItemPosition] + if (TextUtils.isEmpty(old.menuName) && TextUtils.isEmpty(new.menuName)) { + return old.imageId == new.imageId + } + return old.menuName == new.menuName + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return mOldList[oldItemPosition].isSwitch == mNewList[newItemPosition].isSwitch && + mOldList[oldItemPosition].menuValue == mNewList[newItemPosition].menuValue + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/diff/PetPropertyDiffCallBack.kt b/app/src/main/java/com/abbidot/tracker/diff/PetPropertyDiffCallBack.kt new file mode 100644 index 0000000..07e75ae --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/diff/PetPropertyDiffCallBack.kt @@ -0,0 +1,24 @@ +package com.abbidot.tracker.diff + +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.bean.MenuTxtBean + +/** + *Created by .yzq on 2022/3/11/011. + * @link + * @description:宠物属性数据比较 + */ +class PetPropertyDiffCallBack(oldList: MutableList, + newList: MutableList) : + BaseDiffCallBack(oldList, newList) { + private var mOldList = oldList + private val mNewList = newList + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return mOldList[oldItemPosition].imageId == mNewList[newItemPosition].imageId + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + return mOldList[oldItemPosition].menuName == mNewList[newItemPosition].menuName&& + mOldList[oldItemPosition].menuValue==mNewList[newItemPosition].menuValue + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/receiver/BluetoothMonitorReceiver.kt b/app/src/main/java/com/abbidot/tracker/receiver/BluetoothMonitorReceiver.kt new file mode 100644 index 0000000..3fb781d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/receiver/BluetoothMonitorReceiver.kt @@ -0,0 +1,49 @@ +package com.abbidot.tracker.receiver + +import android.bluetooth.BluetoothAdapter +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager + +/** + *Created by .yzq on 2022/5/23/023. + * @link + * @description:监听蓝牙开关操作广播 + */ +class BluetoothMonitorReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context?, intent: Intent?) { + + intent?.apply { + when (intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0)) { + BluetoothAdapter.STATE_TURNING_ON -> { + LogUtil.e("蓝牙正在打开") + } + + BluetoothAdapter.STATE_ON -> { + LogUtil.e("蓝牙已经打开") + XEventBus.post(EventName.BluetoothSwitch, ConState.BLUETOOTH_ON) + } + + BluetoothAdapter.STATE_TURNING_OFF -> { + LogUtil.e("蓝牙正在关闭") + SRBleUtil.instance.cancelBleScan("关闭蓝牙,取消扫描") + BleManager.getInstance().disconnectAllDevice() + } + + BluetoothAdapter.STATE_OFF -> { + LogUtil.e("蓝牙已经关闭") + XEventBus.post(EventName.BluetoothSwitch, ConState.BLUETOOTH_OFF) + } + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/receiver/GPSMonitorReceiver.kt b/app/src/main/java/com/abbidot/tracker/receiver/GPSMonitorReceiver.kt new file mode 100644 index 0000000..68c29c4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/receiver/GPSMonitorReceiver.kt @@ -0,0 +1,38 @@ +package com.abbidot.tracker.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.location.LocationManager +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.constant.ConstantInt + + +/** + *Created by .yzq on 2022/5/23/023. + * @link + * @description:GPS开关操作广播 + */ +class GPSMonitorReceiver(locationManager:LocationManager) : BroadcastReceiver() { + + private val mLocationManager=locationManager + + override fun onReceive(context: Context, intent: Intent?) { + intent?.action?.apply { + if (matches(Regex(LocationManager.PROVIDERS_CHANGED_ACTION))) { + val gpsEnabled = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + if (gpsEnabled) { + LogUtil.e("gps开启") + XEventBus.post(EventName.GPSSwitchState, ConstantInt.Open) + } else { + LogUtil.e("gps关闭") + XEventBus.post(EventName.GPSSwitchState, ConstantInt.Close) + } + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/receiver/MyJPushMessageReceiver.kt b/app/src/main/java/com/abbidot/tracker/receiver/MyJPushMessageReceiver.kt new file mode 100644 index 0000000..3b41a2d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/receiver/MyJPushMessageReceiver.kt @@ -0,0 +1,10 @@ +package com.abbidot.tracker.receiver + +import cn.jpush.android.service.JPushMessageReceiver + +/** + *Created by .yzq on 2022/3/14/014. + * @link + * @description:从JPush3.0.7开始,需要配置继承JPushMessageReceiver的广播,原来如果配了MyReceiver现在可以弃用 + */ +class MyJPushMessageReceiver: JPushMessageReceiver() {} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt b/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt new file mode 100644 index 0000000..49f11c6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/retrofit2/INetworkService.kt @@ -0,0 +1,1382 @@ +package com.abbidot.tracker.retrofit2 + +import com.abbidot.baselibrary.network.base.BaseResponse +import com.abbidot.tracker.bean.ActiveTimeBean +import com.abbidot.tracker.bean.AlipayOrderBean +import com.abbidot.tracker.bean.CountryCodeBean +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.bean.DeviceDetailBean +import com.abbidot.tracker.bean.FamilyPetBean +import com.abbidot.tracker.bean.FirmwareBean +import com.abbidot.tracker.bean.GetFencesBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.HomeDataBean +import com.abbidot.tracker.bean.InviteInfoBean +import com.abbidot.tracker.bean.LKSimListBean +import com.abbidot.tracker.bean.LiveDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.MessageBean +import com.abbidot.tracker.bean.NotificationBean +import com.abbidot.tracker.bean.OrderBean +import com.abbidot.tracker.bean.PackageBean +import com.abbidot.tracker.bean.PackageTypeBean +import com.abbidot.tracker.bean.PayResultBean +import com.abbidot.tracker.bean.PetAllInfoBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.RechargeDeviceBean +import com.abbidot.tracker.bean.RechargeHistoryBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.bean.SleepTimeBean +import com.abbidot.tracker.bean.StripeSubscriptionPriceBean +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.bean.UserInfoBean +import com.abbidot.tracker.bean.WXPayOrderBean +import com.abbidot.tracker.bean.WiFiZoneListBean +import okhttp3.MultipartBody +import retrofit2.http.Field +import retrofit2.http.FormUrlEncoded +import retrofit2.http.GET +import retrofit2.http.Multipart +import retrofit2.http.POST +import retrofit2.http.Part +import retrofit2.http.Query + +interface INetworkService { + + companion object { + //宠物勋章图片url + const val BASE_PET_BADGE_URL = "http://162.62.53.38:9998/static/TrackerBadgeImages/" + private const val IP_SERVER = "https://aws.abbidot.com" +// private const val IP_SERVER = "https://192.168.0.22" +// const val BASE_URL = "http://ec2-50-18-65-40.us-west-1.compute.amazonaws.com:8090/abbidot/" +// const val BASE_URL = "http://ec2-3-101-8-36.us-west-1.compute.amazonaws.com:8090/abbidot/" +// const val BASE_URL = "http://www.xiaopandev.com:8090/abbidot/" + + //亚马逊服务器(国外ip) + const val BASE_URL_ABROAD = "https://aws.abbidot.com/abbidot/" +// const val BASE_URL_ABROAD = "http://192.168.0.229:8080/abbidot/" +// const val BASE_URL_ABROAD = "$IP_SERVER:8443/abbidotServer/" + + //亚马逊服务器(国内ip) +// const val BASE_URL_CN = "$IP_SERVER:8443/abbidotServer/" + const val BASE_URL_CN = BASE_URL_ABROAD + + var BASE_URL = BASE_URL_ABROAD + + const val VERSION_NUMBER = "/v2" + const val STRIPE_VERSION_NUMBER = "/v2" + const val SUBSCRIPTIONS_V3 = "/v3" + + //服务器第二路径地址,国内访问(前面加'/'自动覆盖域名后面路径,具体参照retrofit2库用法) + /*Base URL: http://example.com/ + * Endpoint: https://github.com/square/retrofit/ + * Result: https://github.com/square/retrofit/ + */ +// const val BASE_URL_CN1 = "$SERVER_IP_CN/abbidotServer/" + } + + /** + * 获取国家代码号 + */ + @GET("user$VERSION_NUMBER/getCountryCodes") + suspend fun getCountryCodes(): BaseResponse> + + /** + * 获取邮箱验证码 + * type验证码类型 1是注册 2是忘记密码 + */ + @FormUrlEncoded + @POST("user/sendEmail") + suspend fun getVerificationCode( + @Field("email") email: String, @Field("type") type: Int + ): BaseResponse + + /** + * 验证码验证 + */ + @FormUrlEncoded + @POST("user/validateCode") + suspend fun validateCode( + @Field("email") email: String, + @Field("emailCode") emailCode: String, + @Field("type") type: Int + ): BaseResponse + + /** + * 用户登陆 + */ + @FormUrlEncoded + @POST("user/login") + suspend fun login( + @Field("email") email: String, @Field("password") password: String + ): BaseResponse + + /** + * 注册/创建用户 + */ + @FormUrlEncoded + @POST("user$VERSION_NUMBER/register") + suspend fun registerV2( + @Field("email") email: String, + @Field("password") password: String, + @Field("userName") userName: String, + @Field("countryCode") countryCode: String + ): BaseResponse + + /** + * 注册 + */ + @FormUrlEncoded + @POST("user/register") + suspend fun register( + @Field("email") email: String, + @Field("password") password: String, + @Field("userName") userName: String + ): BaseResponse + + /** + * 获取用户信息 + */ + @GET("user/getUserInfo") + suspend fun getUserInfo(@Query("userId") userId: String): BaseResponse + + /** + * 用户注销 + */ + @FormUrlEncoded + @POST("user/logoff") + suspend fun logoff(@Field("userId") userId: String): BaseResponse + + /** + * 图片上传 + */ + @Multipart + @POST("file/uploadImage") + suspend fun uploadImage(@Part file: MultipartBody.Part): BaseResponse + + /** + * 更新用户信息 + */ + @FormUrlEncoded + @POST("user/updateUser") + suspend fun updateUser( + @Field("birthdayDate") birthdayDate: String, + @Field("email") email: String, + @Field("gender") gender: Int, + @Field("id") userId: String, + @Field("imgurl") imgurl: String, + @Field("location") location: String, +// @Field("phone") phone: String, + @Field("userName") userName: String, + @Field("countryCode") countryCode: String + ): BaseResponse + + /** + * 获取消息列表 + */ + @GET("user/messageList") + suspend fun getMessageList( + @Query("userId") userId: String + ): BaseResponse> + + /** + *删除消息 + */ + @FormUrlEncoded + @POST("user/deleteMessage") + suspend fun deleteMessage(@Field("mesageIds") messageIds: String): BaseResponse + + /** + *用户意见反馈 + * type=反馈问题类型 type 1为APP 2为设备 3其它 + */ + @FormUrlEncoded + @POST("user/feedback") + suspend fun feedback( + @Field("type") type: Int, + @Field("userId") userId: String, + @Field("content") content: String, + @Field("imgurls") imgurls: String, + @Field("email") email: String + ): BaseResponse + + /** + *重置密码 + */ + @FormUrlEncoded + @POST("user/resetPassword") + suspend fun resetPassword( + @Field("email") email: String, @Field("password") password: String + ): BaseResponse + + /** + *修改单位 + */ + @FormUrlEncoded + @POST("user/updateMeasurementUnit") + suspend fun updateMeasureUnit( + @Field("userId") userId: String, @Field("measurementUnitType") measurementUnit: Int + ): BaseResponse + + /** + * 获取蓝牙设备Mac + */ + @GET("device/getMacId") + suspend fun getMacId( + @Query("userId") userId: String, @Query("deviceOutId") deviceOutId: String + ): BaseResponse + + /** + * 获取设备iccid + */ + @GET("device/iccid") + suspend fun getIccId( + @Query("deviceOutId") deviceOutId: String + ): BaseResponse + + /** + * 获取设备订单状态 + */ + @GET("device/getDeviceOrderStatus") + suspend fun getDeviceOrderStatus( + @Query("deviceOutId") deviceOutId: String + ): BaseResponse + + /** + * 上电激活设备,更新状态 + */ + @FormUrlEncoded + @POST("device/updateStatus") + suspend fun updateStatus( + @Field("deviceId") deviceId: String, @Field("addStatus") addStatus: Int//设备添加状态,1 为上电激活成功 + ): BaseResponse + + /** + * 支付成功后,更新绑定状态 + */ + @FormUrlEncoded + @POST("device/update/payDeviceSataus") + suspend fun updatePayDeviceState( + @Field("deviceId") deviceId: String, @Field("userId") userId: String + ): BaseResponse + + /** + * 设备绑定 + */ + @FormUrlEncoded + @POST("device/bindDevice") + suspend fun bindDevice( + @Field("userId") userId: String, + @Field("deviceOutId") deviceOutId: String, + @Field("addStatus") addStatus: Int + ): BaseResponse + + /** + * 解绑删除设备 + */ + @FormUrlEncoded + @POST("device/unbindDevice") + suspend fun unbindDevice( + @Field("userId") userId: String, @Field("deviceId") deviceId: String + ): BaseResponse + + /** + * 把Tracker给分配宠物 + */ + @FormUrlEncoded + @POST("device/assignOtherPetTracker") + suspend fun assignOtherPetTracker( + @Field("userId") userId: String, + @Field("petId") petId: String, + @Field("originDeviceId") originDeviceId: String, + @Field("assignDeviceId") assignDeviceId: String + ): BaseResponse + + /** + * 给已绑定的设备分配宠物 + */ + @FormUrlEncoded + @POST("device/assignOtherPet") + suspend fun assignOtherPet( + @Field("userId") userId: String, + @Field("deviceId") deviceId: String, + @Field("originPetId") originPetId: String, + @Field("assignPetId") assignPetId: String + ): BaseResponse + + /** + * 获取设备列表 + * type=1 是自己的 0是显示授权 + */ + @GET("device/list") + suspend fun getDeviceList( + @Query("userId") userId: String, @Query("type") type: Int + ): BaseResponse> + + /** + * 获取未绑定宠物的设备 + */ + @GET("device/getUnboundDevices") + suspend fun getUnboundDevices( + @Query("userId") userId: String + ): BaseResponse> + + /** + * 获取设备相关详情 + */ + @GET("device/getPetTrackerInfo") + suspend fun getPetTrackerInfo(@Query("deviceId") deviceId: String): BaseResponse + + /** + * 获取设备详情 + */ + @GET("device/detail") + suspend fun getDeviceDetail(@Query("deviceId") deviceId: String): BaseResponse + + /** + * 添加圆形围栏 + */ + @FormUrlEncoded + @POST("device$VERSION_NUMBER/addFence") + suspend fun addCircleFence( + @Field("deviceId") deviceId: String, + @Field("deviceServiceId") deviceServiceId: String, + @Field("name") name: String, + @Field("address") address: String, + @Field("imgurl") imgurl: String, + @Field("isOn") isOn: Int, + @Field("radius") radius: Int, +// @Field("longitude") longitude: Double, +// @Field("latitude") latitude: Double, + @Field("fenceType") fenceType: Int, + @Field("fenceShapeType") fenceShapeType: Int, + @Field("fenceNameType") fenceNameType: Int, + @Field("latitudeCenter") latitudeCenter: Double, + @Field("longitudeCenter") longitudeCenter: Double, + @Field("ancillaryLatitude") ancillaryLatitude: Double, + @Field("ancillaryLongitude") ancillaryLongitude: Double, + @Field("ancillaryOtherLatitude") ancillaryOtherLatitude: Double, + @Field("ancillaryOtherLongitude") ancillaryOtherLongitude: Double +// @Field("fenceId") fenceId: String + ): BaseResponse + + /** + * 添加矩形围栏 + */ + @FormUrlEncoded + @POST("device$VERSION_NUMBER/addFence") + suspend fun addRectFence( + @Field("deviceId") deviceId: String, + @Field("deviceServiceId") deviceServiceId: String, + @Field("name") name: String, + @Field("address") address: String, + @Field("imgurl") imgurl: String, + @Field("isOn") isOn: Int, +// @Field("radius") radius: Double, +// @Field("longitude") longitude: Double, +// @Field("latitude") latitude: Double, + @Field("fenceType") fenceType: Int, + @Field("fenceShapeType") fenceShapeType: Int, + @Field("fenceNameType") fenceNameType: Int, +// @Field("fenceId") fenceId: String, + @Field("longitudeA") longitudeA: Double, + @Field("latitudeA") latitudeA: Double, + @Field("longitudeB") longitudeB: Double, + @Field("latitudeB") latitudeB: Double, + @Field("longitudeC") longitudeC: Double, + @Field("latitudeC") latitudeC: Double, + @Field("longitudeD") longitudeD: Double, + @Field("latitudeD") latitudeD: Double, + @Field("longitudeCenter") longitudeCenter: Double, + @Field("latitudeCenter") latitudeCenter: Double, + @Field("longDistance") longDistance: Int, + @Field("shortDistance") shortDistance: Int + ): BaseResponse + + /** + * 添加矩形围栏 + */ + @FormUrlEncoded + @POST("device$VERSION_NUMBER/addFence") + suspend fun addPolygonFence( + @Field("deviceId") deviceId: String, + @Field("deviceServiceId") deviceServiceId: String, + @Field("name") name: String, + @Field("address") address: String, + @Field("imgurl") imgurl: String, + @Field("isOn") isOn: Int, + @Field("fenceType") fenceType: Int, + @Field("fenceShapeType") fenceShapeType: Int, + @Field("fenceNameType") fenceNameType: Int, + @Field("longitudeA") longitudeA: Double, + @Field("latitudeA") latitudeA: Double, + @Field("longitudeB") longitudeB: Double, + @Field("latitudeB") latitudeB: Double, + @Field("longitudeC") longitudeC: Double, + @Field("latitudeC") latitudeC: Double, + @Field("longitudeD") longitudeD: Double, + @Field("latitudeD") latitudeD: Double, + @Field("longitudeE") longitudeE: Double, + @Field("latitudeE") latitudeE: Double, + @Field("longitudeF") longitudeF: Double, + @Field("latitudeF") latitudeF: Double, + @Field("longitudeCenter") longitudeCenter: Double, + @Field("latitudeCenter") latitudeCenter: Double, +// @Field("longDistance") longDistance: Double, +// @Field("shortDistance") shortDistance: Double + ): BaseResponse + + /** + * 更新圆形围栏 + */ + @FormUrlEncoded + @POST("device$VERSION_NUMBER/updateFence") + suspend fun updateCircleFence( + @Field("deviceId") deviceId: String, + @Field("deviceServiceId") deviceServiceId: String, + @Field("name") name: String, + @Field("address") address: String, + @Field("imgurl") imgurl: String, + @Field("isOn") isOn: Int, + @Field("radius") radius: Int, + @Field("longitudeCenter") longitudeCenter: Double, + @Field("latitudeCenter") latitudeCenter: Double, + @Field("fenceShapeType") fenceShapeType: Int, + @Field("fenceType") fenceType: Int, + @Field("fenceNameType") fenceNameType: Int, + @Field("fenceId") fenceId: String, + @Field("ancillaryLatitude") ancillaryLatitude: Double, + @Field("ancillaryLongitude") ancillaryLongitude: Double, + @Field("ancillaryOtherLatitude") ancillaryOtherLatitude: Double, + @Field("ancillaryOtherLongitude") ancillaryOtherLongitude: Double + ): BaseResponse + + /** + * 更新矩形围栏 + */ + @FormUrlEncoded + @POST("device$VERSION_NUMBER/updateFence") + suspend fun updateRectFence( + @Field("deviceId") deviceId: String, + @Field("deviceServiceId") deviceServiceId: String, + @Field("name") name: String, + @Field("address") address: String, + @Field("imgurl") imgurl: String, + @Field("isOn") isOn: Int, +// @Field("radius") radius: Double, +// @Field("longitude") longitude: Double, +// @Field("latitude") latitude: Double, + @Field("fenceShapeType") fenceShapeType: Int, + @Field("fenceType") fenceType: Int, + @Field("fenceNameType") fenceNameType: Int, + @Field("fenceId") fenceId: String, + @Field("longitudeA") longitudeA: Double, + @Field("latitudeA") latitudeA: Double, + @Field("longitudeB") longitudeB: Double, + @Field("latitudeB") latitudeB: Double, + @Field("longitudeC") longitudeC: Double, + @Field("latitudeC") latitudeC: Double, + @Field("longitudeD") longitudeD: Double, + @Field("latitudeD") latitudeD: Double, + @Field("longitudeCenter") longitudeCenter: Double, + @Field("latitudeCenter") latitudeCenter: Double, + @Field("longDistance") longDistance: Int, + @Field("shortDistance") shortDistance: Int + ): BaseResponse + + /** + * 更新多边形围栏 + */ + @FormUrlEncoded + @POST("device$VERSION_NUMBER/updateFence") + suspend fun updatePolygonFence( + @Field("deviceId") deviceId: String, + @Field("deviceServiceId") deviceServiceId: String, + @Field("fenceId") fenceId: String, + @Field("name") name: String, + @Field("address") address: String, + @Field("imgurl") imgurl: String, + @Field("isOn") isOn: Int, + @Field("fenceType") fenceType: Int, + @Field("fenceShapeType") fenceShapeType: Int, + @Field("fenceNameType") fenceNameType: Int, + @Field("longitudeA") longitudeA: Double, + @Field("latitudeA") latitudeA: Double, + @Field("longitudeB") longitudeB: Double, + @Field("latitudeB") latitudeB: Double, + @Field("longitudeC") longitudeC: Double, + @Field("latitudeC") latitudeC: Double, + @Field("longitudeD") longitudeD: Double, + @Field("latitudeD") latitudeD: Double, + @Field("longitudeE") longitudeE: Double, + @Field("latitudeE") latitudeE: Double, + @Field("longitudeF") longitudeF: Double, + @Field("latitudeF") latitudeF: Double, + @Field("longitudeCenter") longitudeCenter: Double, + @Field("latitudeCenter") latitudeCenter: Double, + ): BaseResponse + + /** + * 获取围栏列表 + */ + @GET("device$VERSION_NUMBER/getFenceList") + suspend fun getFenceList( + @Query( + "deviceId" + ) deviceId: String + ): BaseResponse + + /** + * 移除围栏 + */ + @FormUrlEncoded + @POST("device/removeFence") + suspend fun removeFence( + @Field("fenceId") fenceId: String, @Field("deviceServiceId") deviceServiceId: String + ): BaseResponse + + /** + * 获取wifi区域列表 + */ + @GET("wifiZone/getWifiZonelist") + suspend fun getWifiZoneList( + @Query("deviceId") deviceId: String + ): BaseResponse + + /** + * 添加wifi区域 + */ + @FormUrlEncoded + @POST("wifiZone/addWifiZone") + suspend fun addWifiZone( + @Field("deviceId") deviceId: String, + @Field("wifiZoneName") wifiZoneName: String, + @Field("address") address: String, + @Field("wifiMac") wifiMac: String, + @Field("wifiName") wifiName: String, + @Field("longitude") longitude: Double, + @Field("latitude") latitude: Double, + @Field("type") type: Int + ): BaseResponse + + /** + * 更新wifi区域 + */ + @FormUrlEncoded + @POST("wifiZone/updateWifiZone") + suspend fun updateWifiZone( + @Field("wifiZoneId") wifiZoneId: String, + @Field("deviceId") deviceId: String, + @Field("wifiZoneName") wifiZoneName: String, + @Field("address") address: String, + @Field("wifiMac") wifiMac: String, + @Field("wifiName") wifiName: String, + @Field("longitude") longitude: Double, + @Field("latitude") latitude: Double, + @Field("type") type: Int + ): BaseResponse + + /** + * 删除wifi区域 + */ + @FormUrlEncoded + @POST("wifiZone/removeWifiZone") + suspend fun removeWifiZone( + @Field("wifiZoneId") wifiZoneId: String, + ): BaseResponse + + /** + * 获取所有的宠物列表 + */ + @GET("pet/petList") + suspend fun getPetList( + @Query("userId") userId: String, @Query("type") type: Int + ): BaseResponse> + + /** + * 获取未绑定的宠物列表 + */ + @GET("pet/unbindPetList") + suspend fun getUnbindPetList( + @Query( + "userId" + ) userId: String + ): BaseResponse> + + /** + * 获取首页宠物列表 + */ + @GET("pet/bindPetList") + suspend fun getHomeBindPetList( + @Query( + "userId" + ) userId: String + ): BaseResponse> + + /** + * 获取宠物标签 + */ + @GET("pet/labels") + suspend fun getPetLabels(): BaseResponse> + + /** + * 添加宠物 + */ + @FormUrlEncoded + @POST("pet/addPet") + suspend fun addPet( + @Field("userId") userId: String, + @Field("petName") petName: String, + @Field("breed") breed: String, + @Field("gender") gender: Int, + @Field("petType") petType: Int, + @Field("birthdayDate") birthdayDate: String, + @Field("imgurl") imgurl: String, + @Field("weight") weight: Float, + @Field("height") height: Float, + @Field("labels") labels: String + ): BaseResponse + + /** + * 更新宠物信息 + */ + @FormUrlEncoded + @POST("pet/updatePetInfo") + suspend fun updatePetInfo( + @Field("userId") userId: String, + @Field("petName") petName: String, + @Field("breed") breed: String, + @Field("gender") gender: Int, + @Field("petType") petType: Int, + @Field("birthdayDate") birthdayDate: String, + @Field("petId") petId: String, + @Field("imgurl") imgurl: String, + @Field("weight") weight: Float, + @Field("height") height: Float, + @Field("labels") labels: String + ): BaseResponse + + /** + * 解绑宠物 + */ + @FormUrlEncoded + @POST("pet/unbindPet") + suspend fun unbindPet( + @Field("userId") userId: String, + @Field("deviceId") deviceId: String, + @Field("petId") petId: String + ): BaseResponse + + /** + * 绑定关联宠物 + */ + @FormUrlEncoded + @POST("pet/bindPet") + suspend fun bindPet( + @Field("userId") userId: String, + @Field("deviceId") deviceId: String, + @Field("petId") petId: String + ): BaseResponse + + /** + * 获取宠物所有信息 + */ + @GET("pet/getPetAllInfo") + suspend fun getPetAllInfo(@Query("petId") petId: String): BaseResponse + + /** + * 获取宠物信息 + */ + @GET("pet/getPetInfo") + suspend fun getPetInfo(@Query("petId") petId: String): BaseResponse + + /** + *获取宠物活动统计 + */ + @GET("pet/getPetActivityDetailInfo") + suspend fun getPetActivityDetailInfo( + @Query("petId") petId: String, @Query("deviceId") deviceId: String + ): BaseResponse + + /** + * 删除宠物 + */ + @FormUrlEncoded + @POST("pet/deletePet") + suspend fun deletePet( + @Field("userId") userId: String, @Field("petId") petId: String + ): BaseResponse + + /** + * 获取设备家庭列表 + */ + @GET("device/getFamilies") + suspend fun getFamilies(@Query("deviceId") deviceId: String): BaseResponse + + /** + * 获取设备邀请信息 + */ + @GET("device/getDeviceInviteInfo") + suspend fun getDeviceInviteInfo(@Query("email") email: String): BaseResponse + + /** + * 设备邀请 + */ + @FormUrlEncoded + @POST("device/invite") + suspend fun invite( + @Field("userId") userId: String, + @Field("userName") userName: String, + @Field("sendEmail") sendEmail: String, + @Field("deviceId") deviceId: String, + @Field("acceptEmail") acceptEmail: String, + @Field("relation") relation: Int,//关系 1 Family 2 friend 3 love 4 colleague + @Field("avatar") avatar: Int,//头像 1 男生 2 女生 3 未知 + @Field("petId") petId: String, + @Field("petName") petName: String + ): BaseResponse + + /** + * 国内设备邀请 + */ + @FormUrlEncoded + @POST("device/invite") + suspend fun inviteCN( + @Field("userId") userId: String, + @Field("userName") userName: String, + @Field("deviceId") deviceId: String, + @Field("acceptEmail") phone: String + ): BaseResponse + + /** + * 删除家庭账户 + */ + @GET("device/removeFamily") + suspend fun removeFamily(@Query("familyId") familyId: String): BaseResponse + + /** + * 获取首页Data模块的设备数据 + */ + @GET("data/getDeviceData") + suspend fun getDeviceData( + @Query("deviceId") deviceId: String, + @Query("dayTime") dayTime: String, + @Query("petId") petId: String + ): BaseResponse + + /** + * 获取ActiveTime数据 + */ + @GET("data/getActiveTimeByDay") + suspend fun getActiveTimeByDay( + @Query("deviceId") deviceId: String, + @Query("dayTime") dayTime: String, + @Query("petId") petId: String + ): BaseResponse + + /** + * 获取SleepTime数据 + */ + @GET("data/getSleepTimeByDay") + suspend fun getSleepTimeByDay( + @Query("deviceId") deviceId: String, + @Query("dayTime") dayTime: String, + @Query("petId") petId: String + ): BaseResponse + + /** + * 获取HistoryByDay数据 + */ + @GET("data/getHistoryByDay") + suspend fun getHistoryByDay( + @Query("deviceId") deviceId: String, + @Query("fromTime") fromTime: Long, + @Query("toTime") toTime: Long + ): BaseResponse> + + /** + * 设置运动目标 + */ + @FormUrlEncoded + @POST("data/goal") + suspend fun setGoal( + @Field("deviceId") deviceId: String, @Field("goalTime") goalTime: Int + ): BaseResponse + + /** + * 获取地图模块的设备数据 + */ + @GET("map/deviceStatus") + suspend fun getMapDeviceStatus(@Query("deviceId") deviceId: String): BaseResponse + + /** + * 打开关闭直播 + */ + @GET("map/setupDeviceLiveStatus") + suspend fun setupDeviceLiveStatus( + @Query("deviceId") deviceId: String, + @Query("liveState") liveState: Int, + @Query("sendState") sendState: Int + ): BaseResponse + + /** + * 直播宠物轨迹 + */ + @GET("map/getPetLivePoint") + suspend fun getPetLivePoint( + @Query("deviceId") deviceId: String, @Query("timeStamp") timeStamp: Long + ): BaseResponse + + /** + * 获取所有设备的e-sim卡 + */ + @GET("order/getCards") + suspend fun getCards( + @Query( + "userId" + ) userId: String + ): BaseResponse> + + /** + * 获取设备的充值记录 + */ + @GET("order/getRechargeHistory") + suspend fun getRechargeHistory( + @Query( + "deviceId" + ) deviceId: String + ): BaseResponse> + + /** + * 取消自动续费/自动订阅 + */ + @FormUrlEncoded + @POST("order/cancelSubscription") + suspend fun cancelSubscriptions( + @Field("orderId") orderId: String, + @Field("payType") payType: Int, + @Field("subscriptionId") subscriptionId: String + ): BaseResponse + + /** + * 自动续费,自动订阅 + */ + @FormUrlEncoded + @POST("order/subscription") + suspend fun subscription( + @Field("feePackage") feePackage: String, + @Field("deviceId") deviceId: String, + @Field("accountFee") accountFee: String, + @Field("subscriptionFee") subscriptionFee: String, + @Field("period") period: String, + @Field("userId") userId: String, + @Field("username") username: String + ): BaseResponse + + /** + * 获取套餐列表 + */ + @GET("order/setmeal") + suspend fun getSetMealList(): BaseResponse> + + /** + * 获取订单号 + */ + @GET("order/getPaypalOrderNo") + suspend fun getPaypalOrderNo( + @Query("mealName") mealName: String, + @Query("description") description: String, + @Query("totalCost") totalCost: String, + @Query("deviceId") deviceId: String + ): BaseResponse + + /** + * 信用卡发起支付 + */ + @FormUrlEncoded + @POST("order/payment") + suspend fun cardPayment( + @Field("mealName") mealName: String, + @Field("mealDays") mealDays: Int, + @Field("totalCost") totalCost: String, + @Field("paymentMethodNonce") paymentMethodNonce: String, + @Field("deviceId") deviceId: String + ): BaseResponse + + /** + * 信用卡自动订阅 + */ + @FormUrlEncoded + @POST("creditCard/creditCardSubscription") + suspend fun cardSubscription( + @Field("userId") userId: String, + @Field("paymentMethodNonce") paymentMethodNonce: String, + @Field("firstName") firstName: String, + @Field("lastName") lastName: String, + @Field("feePackage") feePackage: String, + @Field("deviceId") deviceId: String, + @Field("accountFee") accountFee: Double, + @Field("period") period: Float, + @Field("username") username: String, + @Field("planId") planId: String + ): BaseResponse + + /** + * 更新订单状态 + * @param orderStatus:0待支付 1是支付成功 2是取消支付/失败 + */ + @FormUrlEncoded + @POST("order/updateOrderStatus") + suspend fun updateOrderStatus( + @Field("orderId") orderId: String, + @Field("orderStatus") orderStatus: Int, + @Field("deviceId") deviceId: String, + @Field("mealDays") mealDays: Int + ): BaseResponse + + /** + * 获取braintree客户端令牌 + */ + @GET("order/getClientToken") + suspend fun getBraintreeClientToken(): BaseResponse + + /** + * 关闭设备 + */ + @FormUrlEncoded + @POST("device/turnoff") + suspend fun turnoff( + @Field("deviceInfoId") deviceInfoId: String, @Field("turnOff") turnOff: Int + ): BaseResponse + + /** + * 设置设备上报下发时间 + */ + @FormUrlEncoded + @POST("device/gnssInterval") + suspend fun reportInterval( + @Field("deviceInfoId") deviceInfoId: String, @Field("gnssInterval") gnssInterval: Int + ): BaseResponse + + /** + * 设置设备的直播时间 + */ + @FormUrlEncoded + @POST("device/setLiveTime") + suspend fun setLiveTime( + @Field("deviceId") deviceId: String, @Field("liveTime") liveTime: Int + ): BaseResponse + + /** + * 设置ledSwitch开关 + */ + @FormUrlEncoded + @POST("device/ledSwitch") + suspend fun setLedSwitch( + @Field("deviceInfoId") deviceInfoId: String, @Field("ledSwitch") ledSwitch: Int + ): BaseResponse + + /** + * 设置light灯光 + */ + @FormUrlEncoded + @POST("device/ledLignt") + suspend fun setLedLight( + @Field("deviceInfoId") deviceInfoId: String, + @Field("red") red: Int, + @Field("green") green: Int, + @Field("blue") blue: Int, + @Field("lightMode") lightMode: Int, + @Field("ledSwitch") ledSwitch: Int + ): BaseResponse + + /** + * 获取固件信息 + */ + @GET("device/getFirmware") + suspend fun getFirmware(@Query("deviceName") deviceName: String): BaseResponse + + + /** + * 获取sim卡餐列表 + */ + @GET("pay$STRIPE_VERSION_NUMBER/meals") + suspend fun getSimSetMealList( + @Query("deviceCode") deviceCode: String, @Query("partnerCode") partnerCode: String + ): BaseResponse + + /** + * 获取Stripe支付凭证 + */ + @GET("pay$STRIPE_VERSION_NUMBER/payments") + suspend fun getStripePayOrder( + @Query("amount") amount: String, + @Query("userId") userId: String, + @Query("feePackage") feePackage: String, + @Query("deviceId") deviceId: String, + @Query("username") username: String, + @Query("cost") cost: String, + @Query("priceId") priceId: String, + @Query("partnerCode") partnerCode: String, + @Query("packageCode") packageCode: String, + @Query("servicePlanCode") servicePlanCode: String, + @Query("orderPeriod") orderPeriod: String, + @Query("servicePlanShowName") servicePlanShowName: String, + @Query("deviceCode") deviceCode: String, + @Query("autoRenewal") autoRenewal: String + ): BaseResponse + + /** + * Stripe支付创建消费用户 + */ + @FormUrlEncoded + @POST("pay$STRIPE_VERSION_NUMBER/createCustomer") + suspend fun createCustomer( + @Field("parms") param: String, + @Field("userId") userId: String, + @Field("userName") userName: String, + @Field("email") email: String + ): BaseResponse + + /** + * Stripe支付创建订阅 + */ + @FormUrlEncoded + @POST("pay$STRIPE_VERSION_NUMBER/createSubscription") + suspend fun createSubscription( + @Field("userId") userId: String, + @Field("feePackage") feePackage: String, + @Field("deviceId") deviceId: String, + @Field("username") username: String, + @Field("cost") cost: String, + @Field("priceId") priceId: String, + @Field("customerId") customerId: String, + @Field("amount") amount: String, + @Field("partnerCode") partnerCode: String, + @Field("packageCode") packageCode: String, + @Field("servicePlanCode") servicePlanCode: String, + @Field("orderPeriod") orderPeriod: String, + @Field("servicePlanShowName") servicePlanShowName: String, + @Field("deviceCode") deviceCode: String, + @Field("autoRenewal") autoRenewal: String + ): BaseResponse + + /** + * Stripe支付取消订阅 + */ + @FormUrlEncoded + @POST("pay$STRIPE_VERSION_NUMBER/cancelSubscription") + suspend fun cancelStripeSubscription( + @Field("orderId") orderId: String, + @Field("subscriptionId") subscriptionId: String, + @Field("iccid") iccid: String + ): BaseResponse + + /** + * Stripe支付 Rsa算法解密测试 + */ + @FormUrlEncoded + @POST("pay$STRIPE_VERSION_NUMBER/rsaDigisteTest") + suspend fun rsaDecodeTest( + @Field("encryptedData") encryptedData: String + ): BaseResponse + /*******************************************以下是国内版本接口***********************************/ + /** + * 获取微信登录的OpedId + */ +// @Headers("${CommonRequestInterceptor.HEADERS_NAME}:${CommonRequestInterceptor.HEADERS_value}") +// @GET("oauth2/access_token") +// suspend fun getWXOpedId(@Query("appid") appid: String, +// @Query("secret") secret: String, +// @Query("code") code: String, +// @Query("grant_type") grant_type: String): BaseResponse + + /** + * 微信登录 + */ + @GET("user/wxLogin") + suspend fun wxLogin(@Query("code") code: String): BaseResponse + + /** + * 微信绑定 + */ + @FormUrlEncoded + @POST("user/weixinBind") + suspend fun wxBind( + @Field("phone") phone: String, + @Field("verificationCode") verificationCode: String, + @Field("wxId") wxId: String + ): BaseResponse + + /** + * 发送验证码 + */ + @GET("user/sendCode") + suspend fun sendCode( + @Query("phone") phone: String, + //发送类型 1是验证码登录 2微信绑定 3注册 4忘记密码 + @Query("type") type: Int + ): BaseResponse + + /** + * 验证码登录 + */ + @FormUrlEncoded + @POST("user/phoneLogin") + suspend fun phoneLogin( + @Field("phone") phone: String, @Field("verificationCode") verificationCode: String + ): BaseResponse + + /** + * 手机号验证码验证 + */ + @FormUrlEncoded + @POST("user/validateCode") + suspend fun validatePhoneCode( + @Field("phone") phone: String, @Field("code") code: String, + //验证码类型 1是注册 2是忘记密码 + @Field("type") type: Int + ): BaseResponse + + /** + * 账号密码登录 + */ + @FormUrlEncoded + @POST("user/accountPasswordLogin") + suspend fun accountPasswordLogin( + @Field("phone") phone: String, @Field("password") password: String + ): BaseResponse + + /** + * 用户注册 + */ + @FormUrlEncoded + @POST("user/register") + suspend fun register( + @Field("phone") phone: String, @Field("password") password: String + ): BaseResponse + + /** + * 重置密码 + */ + @FormUrlEncoded + @POST("user/resetPassword") + suspend fun resetNewPassword( + @Field("phone") phone: String, @Field("password") password: String + ): BaseResponse + + /** + * 获取微信支付Prepay_id + */ + @FormUrlEncoded + @POST("order/getWxPayOrder") + suspend fun getWxPayOrder( + @Field("feePackage") feePackage: String, + @Field("deviceId") deviceId: String, + @Field("payFee") payFee: Double, + @Field("period") period: Float, + @Field("userId") userId: String, + @Field("username") username: String + ): BaseResponse + + /** + * 获取支付宝支付订单 + */ + @FormUrlEncoded + @POST("order/getAlipayOrder") + suspend fun getAlipayOrder( + @Field("feePackage") feePackage: String, + @Field("deviceId") deviceId: String, + @Field("payFee") payFee: Double, + @Field("period") period: Float, + @Field("userId") userId: String, + @Field("username") username: String + ): BaseResponse + + + /** + * 获取订阅订单列表 + */ + @GET("order$SUBSCRIPTIONS_V3/subscriptions/order/list") + suspend fun getSubscriptionsOrder( + @Query("userId") userId: String + ): BaseResponse> + + /** + * 获取套餐计划类型 + */ + @GET("order$SUBSCRIPTIONS_V3/subscriptions/plan/types") + suspend fun getPackageType(): BaseResponse> + + /** + * 获取套餐列表 + */ + @GET("order$SUBSCRIPTIONS_V3/plan/list") + suspend fun getPackageList( + @Query("planTypeName") planTypeName: String + ): BaseResponse> + + /** + * 获取设备充值得的历史记录 + */ + @GET("order$SUBSCRIPTIONS_V3/subscriptions/history") + suspend fun getSubscriptionsHistory( + @Query("userId") userId: String, @Query("deviceId") deviceId: String + ): BaseResponse> + + /** + * 提交退款申请 + */ + @FormUrlEncoded + @POST("order$SUBSCRIPTIONS_V3/refund/commit") + suspend fun commitRefund( + @Field("currency_code") currency_code: String, + @Field("orderNum") orderNum: String, + @Field("chargeId") chargeId: String, + @Field("amount") amount: Int, + @Field("subscriptionId") subscriptionId: String, + @Field("planName") planName: String, + @Field("refundDescription") refundDescription: String, + @Field("deviceId") deviceId: String, + @Field("deviceOutId") deviceOutId: String, + @Field("payType") payType: Int + ): BaseResponse + + /** + * 退款 + */ + @FormUrlEncoded + @POST("order$SUBSCRIPTIONS_V3/refund") + suspend fun refund( + @Field("orderNum") orderNum: String + ): BaseResponse + + /** + * 更新订阅状态 + */ + @FormUrlEncoded + @POST("order$SUBSCRIPTIONS_V3/updateScriptionStatus") + suspend fun updateSubscriptionStatus( + @Field("subscriptionId") subscriptionId: String, @Field("status") status: Int + ): BaseResponse + + /** + * 获取SIM卡重新激活费用 + */ + @GET("order$SUBSCRIPTIONS_V3/reactivation/fee") + suspend fun getReactivationFee( + @Query("orderId") orderId: String + ): BaseResponse + + /** + * 创建Stripe订单发起支付 + */ + @FormUrlEncoded + @POST("order$SUBSCRIPTIONS_V3/stripe/createOrderInfo") + suspend fun createStripeOrder( + @Field("userId") userId: String, + @Field("deviceId") deviceId: String, + @Field("payMethod") payMethod: Int, + @Field("iccid") iccid: String, + @Field("deviceOutId") deviceOutId: String, + @Field("username") username: String, + @Field("mealName") mealName: String, + @Field("mealDesc") mealDesc: String, + @Field("mealId") mealId: String, + @Field("mealPeriod") mealPeriod: Int, + @Field("mealPrice") mealPrice: Double, + @Field("mealDiscountPrice") mealDiscountPrice: Double, + @Field("mealUnit") mealUnit: String, + @Field("annualCareType") annualCareType: Int, + @Field("annualCarePrice") annualCarePrice: Double, +// @Field("couponCode") couponCode: String, + @Field("reactivation") reactivation: Int, + @Field("reactivatePrice") reactivatePrice: Double, + @Field("totalAmountWithTax") totalAmountWithTax: Double, + @Field("planCategory") planCategory: String, +// @Field("couponPrice") couponPrice: Double, + @Field("autoRenewPrice") autoRenewPrice: Double, +// @Field("useCoupon") useCoupon: Int, + @Field("tax") tax: Double, + @Field("customerId") customerId: String, + @Field("orderNum") orderNum: String, + @Field("isUpdateOrder") isUpdateOrder: Int, + @Field("subscriptionId") subscriptionId: String + ): BaseResponse + + /** + * 创建Paypal订单发起支付 + */ + @FormUrlEncoded + @POST("order$SUBSCRIPTIONS_V3/paypal/createOrderInfo") + suspend fun createPaypalOrder( + @Field("userId") userId: String, + @Field("deviceId") deviceId: String, + @Field("payMethod") payMethod: Int, + @Field("iccid") iccid: String, + @Field("deviceOutId") deviceOutId: String, + @Field("username") username: String, + @Field("mealName") mealName: String, + @Field("mealDesc") mealDesc: String, + @Field("mealId") mealId: String, + @Field("mealPeriod") mealPeriod: Int, + @Field("mealPrice") mealPrice: Double, + @Field("mealDiscountPrice") mealDiscountPrice: Double, + @Field("mealUnit") mealUnit: String, + @Field("annualCareType") annualCareType: Int, + @Field("annualCarePrice") annualCarePrice: Double, +// @Field("couponCode") couponCode: String, + @Field("reactivation") reactivation: Int, + @Field("reactivatePrice") reactivatePrice: Double, + @Field("totalAmountWithTax") totalAmountWithTax: Double, + @Field("planCategory") planCategory: String, + @Field("autoRenewPrice") autoRenewPrice: Double, +// @Field("couponPrice") couponPrice: Double, +// @Field("useCoupon") useCoupon: Int, + @Field("tax") tax: Double, + @Field("customerId") customerId: String, + @Field("orderNum") orderNum: String, + @Field("isUpdateOrder") isUpdateOrder: Int, + @Field("subscriptionId") subscriptionId: String + ): BaseResponse + + /** + * 获取通知设置 + */ + @GET("user$VERSION_NUMBER/notification/getSettings") + suspend fun getNotificationSet( + @Query("userId") userId: String + ): BaseResponse> + + /** + * 消息通知设置 + */ + @FormUrlEncoded + @POST("user$VERSION_NUMBER/notification/setting") + suspend fun setNotification( + @Field("userId") userId: String, + @Field("notificationType") notificationType: Int, + @Field("status") status: Int + ): BaseResponse + + + /** + * 设备日志上传 + */ + @Multipart + @POST("file/uploadLog") + suspend fun uploadLog(@Part file: MultipartBody.Part): BaseResponse + + /** + * 上传设备日志 + */ + @FormUrlEncoded + @POST("device/uploadDeviceLog") + suspend fun uploadDeviceLog( + @Field("deviceOutId") deviceOutId: String, + @Field("uploadTime") uploadTime: String, + @Field("macID") macID: String, + @Field("logFileUrl") logFileUrl: String + ): BaseResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt b/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt new file mode 100644 index 0000000..9038dfa --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/retrofit2/NetworkApi.kt @@ -0,0 +1,1264 @@ +package com.abbidot.tracker.retrofit2 + +import com.abbidot.baselibrary.network.base.BaseNetworkApi +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.PayResultBean +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import okhttp3.MultipartBody + + +/** + * 网络请求具体实现 + * 需要部署服务端:https://github.com/huannan/XArchServer + */ +object NetworkApi : BaseNetworkApi(INetworkService.BASE_URL) { + + /** + * 获取国家代码号 + */ + suspend fun getCountryCodes() = getResult { + service.getCountryCodes() + } + + /** + * 获取邮箱验证码 + * type验证码类型 1是注册 2是忘记密码 + */ + suspend fun requestVerificationCode(email: String, type: Int) = getResult { + service.getVerificationCode(email, type) + } + + /** + * 验证码验证 + */ + suspend fun validateCode(email: String, emailCode: String, type: Int) = getResult { + service.validateCode(email, emailCode, type) + } + + /** + * 注册/创建用户 + */ + suspend fun registerV2(email: String, password: String, userName: String, countryCode: String) = + getResult { + service.registerV2(email, password, userName, countryCode) + } + + /** + * 注册 + */ + suspend fun register(email: String, password: String, userName: String) = getResult { + service.register(email, password, userName) + } + + /** + * 用户登陆 + */ + suspend fun login(email: String, password: String) = getResult { + service.login(email, password) + } + + /** + * 获取用户信息 + */ + suspend fun getUserInfo(userId: String) = getResult { + service.getUserInfo(userId) + } + + /** + * 用户注销 + */ + suspend fun logoff(userId: String) = getResult { + service.logoff(userId) + } + + /** + *删除消息 + */ + suspend fun deleteMessage(messageIds: String) = getResult { + service.deleteMessage(messageIds) + } + + /** + *用户意见反馈 + * type=反馈问题类型 type 1为APP 2为设备 3其它 + */ + suspend fun feedback( + type: Int, userId: String, content: String, imgurls: String, email: String + ) = getResult { + service.feedback(type, userId, content, imgurls, email) + } + + /** + *重置密码 + */ + suspend fun resetPassword(email: String, password: String) = getResult { + service.resetPassword(email, password) + } + + /** + *修改单位 + */ + suspend fun updateMeasureUnit(userId: String, measurementUnit: Int) = getResult { + service.updateMeasureUnit(userId, measurementUnit) + } + + /** + * 图片上传 + */ + suspend fun uploadImage(file: MultipartBody.Part) = getResult { + service.uploadImage(file) + } + + /** + * 更新用户信息 + */ + suspend fun updateUser( + email: String, userId: String, imgurl: String, countryCode: String, username: String + ) = getResult { + service.updateUser("", email, 0, userId, imgurl, "", username, countryCode) + } + + /** + * 获取消息列表 + */ + suspend fun getMessageList(userId: String) = getResult { + service.getMessageList(userId) + } + + /** + * 获取蓝牙设备Mac + */ + suspend fun getMacId(userId: String, deviceOutId: String) = getResult { + service.getMacId(userId, deviceOutId) + } + + /** + * 获取设备iccid + */ + suspend fun getIccId(deviceOutId: String) = getResult { + service.getIccId(deviceOutId) + } + + /** + * 上电激活设备,更新状态 + */ + suspend fun updateStatus(deviceId: String, addStatus: Int) = getResult { + service.updateStatus(deviceId, addStatus) + } + + /** + * 设备绑定 + */ + suspend fun bindDevice(userId: String, deviceOutId: String) = getResult { + service.bindDevice(userId, deviceOutId, 1) + } + + /** + * 支付成功后,更新绑定状态 + */ + suspend fun updatePayDeviceState(deviceId: String, userId: String) = getResult { + service.updatePayDeviceState(deviceId, userId) + } + + /** + * 获取设备订单状态 + */ + suspend fun getDeviceOrderStatus(deviceOutId: String) = getResult { + service.getDeviceOrderStatus(deviceOutId) + } + + /** + * 解绑删除设备 + */ + suspend fun unbindDevice(userId: String, deviceId: String) = getResult { + service.unbindDevice(userId, deviceId) + } + + /** + * 获取设备列表 + */ + suspend fun getDeviceList(userId: String, type: Int) = getResult { + service.getDeviceList(userId, type) + } + + /** + * 获取未绑定宠物的设备 + */ + suspend fun getUnboundDevices(userId: String) = getResult { + service.getUnboundDevices(userId) + } + + /** + * 把Tracker给分配宠物 + */ + suspend fun assignOtherPetTracker( + userId: String, petId: String, originDeviceId: String, assignDeviceId: String + ) = getResult { + service.assignOtherPetTracker(userId, petId, originDeviceId, assignDeviceId) + } + + /** + * 给已绑定的设备分配宠物 + */ + suspend fun assignOtherPet( + userId: String, deviceId: String, originPetId: String, assignPetId: String + ) = getResult { + service.assignOtherPet(userId, deviceId, originPetId, assignPetId) + } + + /** + * 获取地图模块的设备数据 + */ + suspend fun getPetTrackerInfo(deviceId: String) = getResult { + service.getPetTrackerInfo(deviceId) + } + + /** + * 获取设备详情 + */ + suspend fun getDeviceDetail(deviceId: String) = getResult { + service.getDeviceDetail(deviceId) + } + + /** + * 添加圆形围栏 + */ + suspend fun addCircleFence(fencesBean: FencesBean) = getResult { + service.addCircleFence( + fencesBean.deviceId, fencesBean.deviceServiceId, + fencesBean.name, + fencesBean.address, + "", + 1, + fencesBean.radius, + fencesBean.fenceType, + 1, + fencesBean.fenceNameType, + fencesBean.latitudeCenter, + fencesBean.longitudeCenter, + fencesBean.ancillaryLatitude, + fencesBean.ancillaryLongitude, + fencesBean.ancillaryOtherLatitude, + fencesBean.ancillaryOtherLongitude, + ) + } + + /** + * 添加矩形围栏 + */ + suspend fun addRectFence(fencesBean: FencesBean) = getResult { + service.addRectFence( + fencesBean.deviceId, + fencesBean.deviceServiceId, + fencesBean.name, + fencesBean.address, + "", + 1, + fencesBean.fenceType, + 2, + fencesBean.fenceNameType, + fencesBean.longitudeA, + fencesBean.latitudeA, + fencesBean.longitudeB, + fencesBean.latitudeB, + fencesBean.longitudeC, + fencesBean.latitudeC, + fencesBean.longitudeD, + fencesBean.latitudeD, + fencesBean.longitudeCenter, + fencesBean.latitudeCenter, + fencesBean.longDistance, + fencesBean.shortDistance + ) + } + + /** + * 添加多边形形围栏 + */ + suspend fun addPolygonFence(fencesBean: FencesBean) = getResult { + service.addPolygonFence( + fencesBean.deviceId, fencesBean.deviceServiceId, + fencesBean.name, + fencesBean.address, + "", + 1, + fencesBean.fenceType, + 3, + fencesBean.fenceNameType, + fencesBean.longitudeA, + fencesBean.latitudeA, + fencesBean.longitudeB, + fencesBean.latitudeB, + fencesBean.longitudeC, + fencesBean.latitudeC, + fencesBean.longitudeD, + fencesBean.latitudeD, + fencesBean.longitudeE, + fencesBean.latitudeE, + fencesBean.longitudeF, + fencesBean.latitudeF, + fencesBean.longitudeCenter, + fencesBean.latitudeCenter, + ) + } + + /** + * 更新圆形围栏 + */ + suspend fun updateCircleFence( + deviceId: String, + deviceServiceId: String, + name: String, + address: String, + imgurl: String, + isOn: Int, + radius: Int, + longitudeCenter: Double, + latitudeCenter: Double, + fenceNameType: Int, + fenceType: Int, + fenceId: String, + ancillaryLatitude: Double, + ancillaryLongitude: Double, + ancillaryOtherLatitude: Double, + ancillaryOtherLongitude: Double + ) = getResult { + service.updateCircleFence( + deviceId, + deviceServiceId, + name, + address, + imgurl, + isOn, + radius, + longitudeCenter, + latitudeCenter, + 1, + fenceType, + fenceNameType, + fenceId, + ancillaryLatitude, + ancillaryLongitude, + ancillaryOtherLatitude, + ancillaryOtherLongitude + ) + } + + /** + * 更新矩形围栏 + */ + suspend fun updateRectFence( + deviceId: String, + deviceServiceId: String, + name: String, + address: String, + imgurl: String, + isOn: Int, + fenceNameType: Int, + fenceType: Int, + fenceId: String, + longitudeA: Double, + latitudeA: Double, + longitudeB: Double, + latitudeB: Double, + longitudeC: Double, + latitudeC: Double, + longitudeD: Double, + latitudeD: Double, + longitudeCenter: Double, + latitudeCenter: Double, + longDistance: Int, + shortDistance: Int + ) = getResult { + service.updateRectFence( + deviceId, + deviceServiceId, + name, + address, + imgurl, + isOn, + 2, + fenceType, + fenceNameType, + fenceId, + longitudeA, + latitudeA, + longitudeB, + latitudeB, + longitudeC, + latitudeC, + longitudeD, + latitudeD, + longitudeCenter, + latitudeCenter, + longDistance, + shortDistance + ) + } + + /** + * 更新多边形围栏 + */ + suspend fun updatePolygonFence(fencesBean: FencesBean) = getResult { + service.updatePolygonFence( + fencesBean.deviceId, fencesBean.deviceServiceId, + fencesBean.fenceId, + fencesBean.name, + fencesBean.address, + "", + fencesBean.isOn, + fencesBean.fenceType, + 3, + fencesBean.fenceNameType, + fencesBean.longitudeA, + fencesBean.latitudeA, + fencesBean.longitudeB, + fencesBean.latitudeB, + fencesBean.longitudeC, + fencesBean.latitudeC, + fencesBean.longitudeD, + fencesBean.latitudeD, + fencesBean.longitudeE, + fencesBean.latitudeE, + fencesBean.longitudeF, + fencesBean.latitudeF, + fencesBean.longitudeCenter, + fencesBean.latitudeCenter, + ) + } + + /** + * 获取围栏列表 + */ + suspend fun getFenceList(deviceId: String) = getResult { + service.getFenceList(deviceId) + } + + /** + * 移除围栏 + */ + suspend fun removeFence(fenceId: String, deviceServiceId: String) = getResult { + service.removeFence(fenceId, deviceServiceId) + } + + /** + * 获取wifi区域列表 + */ + suspend fun getWifiZoneList(deviceId: String) = getResult { + service.getWifiZoneList(deviceId) + } + + /** + * 添加wifi区域 + */ + suspend fun addWifiZone( + deviceId: String, + wifiZoneName: String, + address: String, + wifiMac: String, + wifiName: String, + longitude: Double, + latitude: Double, + type: Int + ) = getResult { + service.addWifiZone( + deviceId, wifiZoneName, address, wifiMac, wifiName, longitude, latitude, type + ) + } + + /** + * 更新wifi区域 + */ + suspend fun updateWifiZone( + wifiZoneId: String, + deviceId: String, + wifiZoneName: String, + address: String, + wifiMac: String, + wifiName: String, + longitude: Double, + latitude: Double, + type: Int + ) = getResult { + service.updateWifiZone( + wifiZoneId, + deviceId, + wifiZoneName, + address, + wifiMac, + wifiName, + longitude, + latitude, + type + ) + } + + /** + * 删除wifi区域 + */ + suspend fun removeWifiZone(wifiZoneId: String) = getResult { + service.removeWifiZone(wifiZoneId) + } + + + /** + * 获取所有的宠物列表 + */ + suspend fun getPetList(userId: String, type: Int) = getResult { + service.getPetList(userId, type) + } + + /** + * 获取未绑定的宠物列表 + */ + suspend fun getUnbindPetList(userId: String) = getResult { + service.getUnbindPetList(userId) + } + + /** + * 获取首页宠物列表 + */ + suspend fun getHomeBindPetList(userId: String) = getResult { + service.getHomeBindPetList(userId) + } + + /** + * 获取宠物标签 + */ + suspend fun getPetLabels() = getResult { + service.getPetLabels() + } + + /** + * 添加宠物 + */ + suspend fun addPet( + userId: String, + petName: String, + breed: String, + gender: Int, + petType: Int, + birthdayDate: String, + imgurl: String, + weight: Float, + height: Float, + labels: String + ) = getResult { + service.addPet( + userId, petName, breed, gender, petType, birthdayDate, imgurl, weight, height, labels + ) + } + + /** + * 更新宠物信息 + */ + suspend fun updatePetInfo( + userId: String, + petName: String, + breed: String, + gender: Int, + petType: Int, + birthdayDate: String, + petId: String, + imgurl: String, + weight: Float, + height: Float, + labels: String + ) = getResult { + service.updatePetInfo( + userId, + petName, + breed, + gender, + petType, + birthdayDate, + petId, + imgurl, + weight, + height, + labels + ) + } + + /** + * 解绑宠物 + */ + suspend fun unbindPet(userId: String, deviceId: String, petId: String) = getResult { + service.unbindPet(userId, deviceId, petId) + } + + /** + * 绑定关联宠物 + */ + suspend fun bindPet(userId: String, deviceId: String, petId: String) = getResult { + service.bindPet(userId, deviceId, petId) + } + + /** + * 获取宠物信息 + */ + suspend fun getPetAllInfo(petId: String) = getResult { + service.getPetAllInfo(petId) + } + + /** + * 获取宠物信息 + */ + suspend fun getPetInfo(petId: String) = getResult { + service.getPetInfo(petId) + } + + /** + * 获取宠物活动统计 + */ + suspend fun getPetActivityDetailInfo(petId: String, deviceId: String) = getResult { + service.getPetActivityDetailInfo(petId, deviceId) + } + + /** + * 删除宠物 + */ + suspend fun deletePet(userId: String, petId: String) = getResult { + service.deletePet(userId, petId) + } + + /** + * 获取设备家庭列表 + */ + suspend fun getFamilies(deviceId: String) = getResult { + service.getFamilies(deviceId) + } + + /** + * 获取设备邀请信息 + */ + suspend fun getDeviceInviteInfo(email: String) = getResult { + service.getDeviceInviteInfo(email) + } + + /** + * 设备邀请 + */ + suspend fun invite( + userId: String, + userName: String, + sendEmail: String, + deviceId: String, + acceptEmail: String, + relation: Int,//关系 1 Family 2 friend 3 love 4 colleague + avatar: Int,//头像 1 男生 2 女生 3 未知 + petId: String, + petName: String + ) = getResult { + service.invite( + userId, userName, sendEmail, deviceId, acceptEmail, relation, avatar, petId, petName + ) + } + + /** + * 设备邀请 + */ + suspend fun inviteCN(userId: String, userName: String, deviceId: String, phone: String) = + getResult { + service.inviteCN(userId, userName, deviceId, phone) + } + + /** + * 删除家庭账户 + */ + suspend fun removeFamily(familyId: String) = getResult { + service.removeFamily(familyId) + } + + /** + * 获取首页Data模块的设备数据 + */ + suspend fun getDeviceData(deviceId: String, dayTime: String, petId: String) = getResult { + service.getDeviceData(deviceId, dayTime, petId) + } + + /** + * 获取ActiveTime数据 + */ + suspend fun getActiveTimeByDay(deviceId: String, dayTime: String, petId: String) = getResult { + service.getActiveTimeByDay(deviceId, dayTime, petId) + } + + /** + * 获取SleepTime数据 + */ + suspend fun getSleepTimeByDay(deviceId: String, dayTime: String, petId: String) = getResult { + service.getSleepTimeByDay(deviceId, dayTime, petId) + } + + /** + * 获取HistoryByDay数据 + */ + suspend fun getHistoryByDay(deviceId: String, fromTime: Long, toTime: Long) = getResult { + service.getHistoryByDay(deviceId, fromTime, toTime) + } + + /** + * 设置运动目标 + */ + suspend fun setGoal(deviceId: String, goalTime: Int) = getResult { + service.setGoal(deviceId, goalTime) + } + + /** + * 获取地图模块的设备数据 + */ + suspend fun getMapDeviceStatus(deviceId: String) = getResult { + service.getMapDeviceStatus(deviceId) + } + + /** + * 打开关闭直播 + */ + suspend fun setupDeviceLiveStatus(deviceId: String, liveState: Int) = getResult { + service.setupDeviceLiveStatus(deviceId, liveState, 0) + } + + /** + * 直播宠物轨迹 + */ + suspend fun getPetLivePoint(deviceId: String, timeStamp: Long) = getResult { + service.getPetLivePoint(deviceId, timeStamp) + } + + /** + * 获取所有设备的e-sim卡 + */ + suspend fun getCards(userId: String) = getResult { + service.getCards(userId) + } + + /** + * 获取设备的充值记录 + */ + suspend fun getRechargeHistory(deviceId: String) = getResult { + service.getRechargeHistory(deviceId) + } + + /** + * 取消自动续费/自动订阅 + */ + suspend fun cancelSubscriptions(orderId: String, payType: Int, subscriptionId: String) = + getResult { + service.cancelSubscriptions(orderId, payType, subscriptionId) + } + + /** + * 自动续费,自动订阅 + */ + suspend fun subscription( + feePackage: String, + deviceId: String, + accountFee: String, + subscriptionFee: String, + period: String, + userId: String, + username: String + ) = getResult { + service.subscription( + feePackage, deviceId, accountFee, subscriptionFee, period, userId, username + ) + } + + /** + * 获取套餐列表 + */ + suspend fun getSetMealList() = getResult { + service.getSetMealList() + } + + /** + * 获取订单号 + */ + suspend fun getPaypalOrderNo( + mealName: String, description: String, totalCost: String, deviceId: String + ) = getResult { + service.getPaypalOrderNo(mealName, description, totalCost, deviceId) + } + + /** + * 信用卡发起支付 + */ + suspend fun cardPayment( + mealName: String, + mealDays: Int, + totalCost: String, + paymentMethodNonce: String, + deviceId: String + ) = getResult { + service.cardPayment(mealName, mealDays, totalCost, paymentMethodNonce, deviceId) + } + + /** + * 信用卡自动订阅 + */ + suspend fun cardSubscription( + userId: String, + paymentMethodNonce: String, + firstName: String, + lastName: String, + username: String, + deviceId: String, + setMealBean: SetMealBean + ) = getResult { + service.cardSubscription( + userId, + paymentMethodNonce, + firstName, + lastName, + setMealBean.name, + deviceId, + setMealBean.accountFee, + setMealBean.period, + username, + setMealBean.planId + ) + } + + /** + * 更新订单状态 + * @param orderStatus:0待支付 1是支付成功 2是取消支付/失败 + */ + suspend fun updateOrderStatus( + orderId: String, orderStatus: Int, deviceId: String, mealDays: Int + ) = getResult { + service.updateOrderStatus(orderId, orderStatus, deviceId, mealDays) + } + + /** + * 获取braintree客户端令牌 + */ + suspend fun getBraintreeClientToken() = getResult { + service.getBraintreeClientToken() + } + + /** + * 关闭设备 + */ + suspend fun turnoff(deviceInfoId: String, turnOff: Int) = getResult { + service.turnoff(deviceInfoId, turnOff) + } + + /** + * 设置设备上报下发时间 + */ + suspend fun reportInterval(deviceInfoId: String, gnssInterval: Int) = getResult { + service.reportInterval(deviceInfoId, gnssInterval) + } + + /** + * 设置设备的直播时间 + */ + suspend fun setLiveTime(deviceId: String, liveTime: Int) = getResult { + service.setLiveTime(deviceId, liveTime) + } + + /** + * 设置ledSwitch开关 + */ + suspend fun setLedSwitch(deviceInfoId: String, ledSwitch: Int) = getResult { + service.setLedSwitch(deviceInfoId, ledSwitch) + } + + /** + * 设置light灯光 + */ + suspend fun setLedLight( + deviceServerId: String, red: Int, green: Int, blue: Int, lightMode: Int, ledSwitch: Int + ) = getResult { + service.setLedLight(deviceServerId, red, green, blue, lightMode, ledSwitch) + } + + /** + * 获取固件信息 + */ + suspend fun getFirmware(deviceName: String) = getResult { + service.getFirmware(deviceName) + } + + /** + * 获取sim卡餐列表 + */ + suspend fun getSimSetMealList( + iccId: String, partnerCode: String + ) = getResult { + service.getSimSetMealList(iccId, partnerCode) + } + + /** + * 获取Stripe支付凭证 + */ + suspend fun getStripePayOrder( + amount: String, + userId: String, + feePackage: String, + deviceId: String, + username: String, + cost: String, + priceId: String, + partnerCode: String, + packageCode: String, + servicePlanCode: String, + orderPeriod: String, + servicePlanShowName: String, + deviceCode: String, + autoRenewal: String + ) = getResult { + service.getStripePayOrder( + amount, + userId, + feePackage, + deviceId, + username, + cost, + priceId, + partnerCode, + packageCode, + servicePlanCode, + orderPeriod, + servicePlanShowName, + deviceCode, + autoRenewal + ) + } + + /** + * Stripe支付创建消费用户 + */ + suspend fun createCustomer( + param: String, userId: String, userName: String, email: String + ) = getResult { + service.createCustomer(param, userId, userName, email) + } + + /** + * Stripe支付创建订阅 + */ + suspend fun createSubscription( + userId: String, + feePackage: String, + deviceId: String, + username: String, + cost: String, + priceId: String, + customerId: String, + amount: String, + partnerCode: String, + packageCode: String, + servicePlanCode: String, + orderPeriod: String, + servicePlanShowName: String, + deviceCode: String, + autoRenewal: String + ) = getResult { + service.createSubscription( + userId, + feePackage, + deviceId, + username, + cost, + priceId, + customerId, + amount, + partnerCode, + packageCode, + servicePlanCode, + orderPeriod, + servicePlanShowName, + deviceCode, + autoRenewal + ) + } + + /** + * Stripe支付取消订阅 + */ + suspend fun cancelStripeSubscription( + orderId: String, subscriptionId: String, iccid: String + ) = getResult { + service.cancelStripeSubscription(orderId, subscriptionId, iccid) + } + + /** + * Stripe支付 Rsa算法解密测试 + */ + suspend fun rsaDecodeTest(encryptedData: String) = getResult { + service.rsaDecodeTest(encryptedData) + } + + /*******************************************以下是国内版本接口***********************************/ + /** + * 微信登录 + */ + suspend fun wxLogin(code: String) = getResult { + service.wxLogin(code) + } + + /** + * 微信绑定 + */ + suspend fun wxBind(phone: String, verificationCode: String, wxId: String) = getResult { + service.wxBind(phone, verificationCode, wxId) + } + + /** + * 发送验证码 + */ + suspend fun sendCode(phone: String, type: Int) = getResult { + service.sendCode(phone, type) + } + + /** + * 手机号验证码验证 + */ + suspend fun validatePhoneCode(phone: String, code: String, type: Int) = getResult { + service.validatePhoneCode(phone, code, type) + } + + /** + * 验证码登录 + */ + suspend fun phoneLogin(phone: String, verificationCode: String) = getResult { + service.phoneLogin(phone, verificationCode) + } + + /** + * 账号密码登录 + */ + suspend fun accountPasswordLogin(phone: String, password: String) = getResult { + service.accountPasswordLogin(phone, password) + } + + /** + * 用户注册 + */ + suspend fun register(phone: String, password: String) = getResult { + service.register(phone, password) + } + + /** + * 重置密码 + */ + suspend fun resetNewPassword(phone: String, password: String) = getResult { + service.resetNewPassword(phone, password) + } + + /** + * 获取微信Prepay_id + */ + suspend fun getWxPayOrder( + setMealBean: SetMealBean, deviceId: String, userId: String, username: String + ) = getResult { + service.getWxPayOrder( + setMealBean.name, deviceId, setMealBean.accountFee, setMealBean.period, userId, username + ) + } + + /** + * 获取支付宝支付订单 + */ + suspend fun getAlipayOrder( + setMealBean: SetMealBean, deviceId: String, userId: String, username: String + ) = getResult { + service.getAlipayOrder( + setMealBean.name, deviceId, setMealBean.accountFee, setMealBean.period, userId, username + ) + } + + /** + * 获取订阅订单列表 + */ + suspend fun getSubscriptionsOrder(userId: String) = getResult { + service.getSubscriptionsOrder(userId) + } + + /** + * 获取套餐计划类型 + */ + suspend fun getPackageType() = getResult { + service.getPackageType() + } + + /** + * 获取套餐列表 + */ + suspend fun getPackageList(planTypeName: String) = getResult { + service.getPackageList(planTypeName) + } + + /** + * 获取设备充值得的历史记录 + */ + suspend fun getSubscriptionsHistory(userId: String, deviceId: String) = getResult { + service.getSubscriptionsHistory(userId, deviceId) + } + + /** + * 获取设备充值得的历史记录 + */ + suspend fun commitRefund( + currency_code: String, + orderId: String, + chargeId: String, + amount: Int, + subscriptionId: String, + planName: String, + refundDescription: String, + deviceId: String, + deviceOutId: String, + payType: Int + ) = getResult { + service.commitRefund( + currency_code, + orderId, + chargeId, + amount, + subscriptionId, + planName, + refundDescription, + deviceId, + deviceOutId, + payType + ) + } + + /** + * 退款 + */ + suspend fun refund(orderNum: String) = getResult { + service.refund(orderNum) + } + + /** + * 更新订阅状态 状态 0是取消 1使其变为正常 + */ + suspend fun updateSubscriptionStatus(subscriptionId: String, status: Int) = getResult { + service.updateSubscriptionStatus(subscriptionId, status) + } + + /** + * 获取SIM卡重新激活费用 + */ + suspend fun getReactivationFee(orderId: String) = getResult { + service.getReactivationFee(orderId) + } + + /** + * 创建Stripe订单发起支付 + */ + suspend fun createStripeOrder(subscriptionsOrder: SubscriptionsOrderBean): Result { + subscriptionsOrder.apply { + if (isUpdateOrder == ConstantInt.Type0) { + orderNum = "" + subscriptionId = "" + } + return getResult { + service.createStripeOrder( + userId, + deviceId, + 1, + iccid, + deviceOutId, + username, + mealName, + mealDesc, + mealId, + mealPeriod, + mealPrice, + mealDiscountPrice, + mealUnit, + annualCareType, + annualCarePrice, + reactivation, + reactivatePrice, + totalAmountWithTax, + planCategory, + autoRenewPrice, + tax, + customerId, + orderNum, + isUpdateOrder, + subscriptionId + ) + } + } + } + + /** + * 创建Paypal订单发起支付 + */ + suspend fun createPaypalOrder(subscriptionsOrder: SubscriptionsOrderBean): Result { + subscriptionsOrder.apply { + if (isUpdateOrder == ConstantInt.Type0) { + orderNum = "" + subscriptionId = "" + } + return getResult { + service.createPaypalOrder( + userId, + deviceId, + 2, + iccid, + deviceOutId, + username, + mealName, + mealDesc, + mealId, + mealPeriod, + mealPrice, + mealDiscountPrice, + mealUnit, + annualCareType, + annualCarePrice, + reactivation, + reactivatePrice, + totalAmountWithTax, + planCategory, + autoRenewPrice, + tax, + "", + orderNum, + isUpdateOrder, + subscriptionId + ) + } + } + } + + /** + * 获取通知设置 + */ + suspend fun getNotificationSet(userId: String) = getResult { + service.getNotificationSet(userId) + } + + /** + * 消息通知设置 + */ + suspend fun setNotification(userId: String, notificationType: Int, status: Int) = getResult { + service.setNotification(userId, notificationType, status) + } + + /** + * 设备日志上传 + */ + suspend fun uploadLog(file: MultipartBody.Part) = getResult { + service.uploadLog(file) + } + + /** + * 上传设备日志 + */ + suspend fun uploadDeviceLog( + deviceOutId: String, uploadTime: String, macID: String, logFileUrl: String + ) = getResult { + service.uploadDeviceLog(deviceOutId, uploadTime, macID, logFileUrl) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/service/DfuService.java b/app/src/main/java/com/abbidot/tracker/service/DfuService.java new file mode 100644 index 0000000..72cf737 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/service/DfuService.java @@ -0,0 +1,53 @@ +package com.abbidot.tracker.service; + +import android.app.Activity; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; + +import com.abbidot.tracker.BuildConfig; +import com.abbidot.tracker.ui.activity.NotificationActivity; + +import no.nordicsemi.android.dfu.DfuBaseService; + +public class DfuService extends DfuBaseService { + + @Override + protected Class getNotificationTarget() { + /* + * As a target activity the NotificationActivity is returned, not the MainActivity. This + * is because + * the notification must create a new task: + * + * intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + * + * when you press it. You can use NotificationActivity to check whether the new activity + * is a root activity (that means no other activity was open earlier) or that some + * other activity is already open. In the latter case the NotificationActivity will just be + * closed. The system will restore the previous activity. However, if the application has + * been + * closed during upload and you click the notification, a NotificationActivity will + * be launched as a root activity. It will create and start the main activity and + * terminate itself. + * + * This method may be used to restore the target activity in case the application + * was closed or is open. It may also be used to recreate an activity history using + * startActivities(...). + */ + return NotificationActivity.class; + } + + @Override + protected boolean isDebug() { + // Here return true if you want the service to print more logs in LogCat. + // Library's BuildConfig in current version of Android Studio is always set to + // DEBUG=false, so + // make sure you return true or your.app.BuildConfig.DEBUG here. + return BuildConfig.DEBUG; + } + + @Override + protected void updateForegroundNotification(@NonNull final NotificationCompat.Builder builder) { + // Customize the foreground service notification here. + } +} diff --git a/app/src/main/java/com/abbidot/tracker/service/MyJPushService.kt b/app/src/main/java/com/abbidot/tracker/service/MyJPushService.kt new file mode 100644 index 0000000..b6dff3d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/service/MyJPushService.kt @@ -0,0 +1,13 @@ +package com.abbidot.tracker.service + +import cn.jpush.android.service.JCommonService + +/** + *Created by .yzq on 2022/3/14/014. + * @link + * @description: + + +https://www.jiguang.cn/portal/#/dev/sdkIntegration/c968a96f76fc4f52e0f764ee/android?step=3&id=177378 + */ +class MyJPushService: JCommonService() {} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/DebugActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/DebugActivity.kt new file mode 100644 index 0000000..b054281 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/DebugActivity.kt @@ -0,0 +1,278 @@ +package com.abbidot.tracker.ui + +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.ChangePetListDialogAdapter +import com.abbidot.tracker.adapter.HowWorkAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.ActiveTimeBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.SleepTimeBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityDebugBinding +import com.abbidot.tracker.dialog.CommonListDialog +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.DataViewModel +import com.abbidot.tracker.vm.MapViewModel +import com.abbidot.tracker.vm.TrackerInfoViewModel +import com.qmuiteam.qmui.widget.QMUITopBar +import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView + +class DebugActivity : BaseActivity(ActivityDebugBinding::inflate), + ChangePetListDialogAdapter.OnChangeClickListener { + + private val mMapViewModel: MapViewModel by viewModels() + private val mTrackerManageSetViewModel: TrackerInfoViewModel by viewModels() + private val mDataViewModel: DataViewModel by viewModels() + + // private var mDeviceId = "620e5c999e8f470cb5bd75d3" +// private var mPet: PetBean? = null + + //宠物数据 + private var mPetList: MutableList? = null + + private lateinit var mMutableList: MutableList + private lateinit var mAdapter: HowWorkAdapter + private var mCommonListDialog: CommonListDialog? = null + private lateinit var mChangePetListDialogAdapter: ChangePetListDialogAdapter + + //弹窗选中的宠物下标 + private var mPetSelectPosition = 0 + + override fun getTopBar(): QMUITopBar = mViewBinding.qiNuoTestTopBar.titleTopBar + + override fun initData() { + super.initData() + +// mPet = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + mPetList = intent.getParcelableArrayListExtra(ConstantString.Pet) + mPetSelectPosition = intent.getIntExtra(ConstantString.Index, 0) + + setTopBarTitle("Debug") + mMutableList = mutableListOf() + + mAdapter = HowWorkAdapter(mContext, mMutableList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, mViewBinding.qiNuoTestRecyclerView, mAdapter, bottom = 20 + ) + + initQMUICommonListItemView(mViewBinding.switchBaseUrl) + initQMUICommonListItemView(mViewBinding.switchOnlyGoogleMap) + + LogUtil.e("${mPetList?.size}") + if (null == mPetList) { + return + } +// if (null == mPet) { +// return +// } + + mViewBinding.llDebugPet.setOnClickListener(this) + val rightTextButton = + mTopBar!!.addRightTextButton("刷新", R.id.topbar_right_change_textview) + rightTextButton.setOnClickListener(this) + + mChangePetListDialogAdapter = ChangePetListDialogAdapter(mContext, mPetList) + mChangePetListDialogAdapter.setSelectPetPos(mPetSelectPosition) + mChangePetListDialogAdapter.setOnChangeClickListener(this) + + setPetData() + mMapViewModel.getMapDeviceStatus(mPetList!![mPetSelectPosition].deviceId) + } + + private fun initQMUICommonListItemView(view: QMUICommonListItemView) { + when (view) { + mViewBinding.switchBaseUrl -> { + mViewBinding.switchBaseUrl.let { + it.text = "国内服务器" + it.switch.isChecked = MMKVUtil.getBoolean(MMKVKey.DebugIp, true) + it.switch.setOnCheckedChangeListener { _, isChecked -> + MMKVUtil.putBoolean(MMKVKey.DebugIp, isChecked) + showToast("修改成功,注销登录并重启APP生效") + } + } + } + + mViewBinding.switchOnlyGoogleMap -> { + mViewBinding.switchOnlyGoogleMap.let { + it.text = "只启用谷歌地图" + it.switch.isChecked = MMKVUtil.getBoolean(MMKVKey.OnlyGoogleMap, false) + it.switch.setOnCheckedChangeListener { _, isChecked -> + MMKVUtil.putBoolean(MMKVKey.OnlyGoogleMap, isChecked) + showToast("修改成功,重启APP生效") + } + } + } + } + } + + /** + * 设置宠物头像 名字 + */ + private fun setPetData() { + val pet = mPetList!![mPetSelectPosition] + mViewBinding.debugPetName.text = pet.petName + ViewUtil.instance.setPetTypeHead( + mViewBinding.debugPetHead.appHeadImage, pet.imgurl, pet.petType + ) + + ViewUtil.instance.viewAlphaAndRotationObjectAnimator( + mViewBinding.debugPetHead.appHeadImage + ) + } + + override fun liveDataObserve() { + //获取当前宠物位置和围栏信息 + mMapViewModel.mMapDeviceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mMutableList.clear() + mAdapter.notifyDataSetChanged() + val data = it.getOrNull() + data?.apply { + ViewUtil.instance.addMenuBean(mMutableList, "设备Mac地址", data.deviceMacId) + ViewUtil.instance.addMenuBean( + mMutableList, "deviceServerId", data.deviceServerId + ) + var time = + Utils.formatTime(data.updateTime * 1000, Utils.DATE_FORMAT_PATTERN_CN2) + ViewUtil.instance.addMenuBean(mMutableList, "设备状态上报时间", time) + time = Utils.formatTime( + data.latLonUpdateTime * 1000, Utils.DATE_FORMAT_PATTERN_CN2 + ) + ViewUtil.instance.addMenuBean(mMutableList, "经纬度上报时间", time) + ViewUtil.instance.addMenuBean( + mMutableList, "longitude", data.longitude.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "latitude", data.latitude.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "安全围栏中inSafeZone(0=是)", data.inSafeZone.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "危险区域中inNoGoZone(0=不在)", data.inNoGoZone.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "inWifiZone", data.inWifiZone.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "当前速度", data.speed.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "gps信号gnssSignal", data.gpsSignal.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "设备开关机状态(0关1开2眠3电)", data.powerSwitch.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "电量batteryLevel", data.batteryLevel.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "设备led开关", data.ledSwitch.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "定位周期gnssInterval", "${data.gnssInterval / 60}分钟" + ) + ViewUtil.instance.addMenuBean( + mMutableList, "直播时间liveTime", "${data.liveTime / 60}分钟" + ) + ViewUtil.instance.addMenuBean( + mMutableList, "直播liveFlag", data.liveFlag.toString() + ) + ViewUtil.instance.addMenuBean( + mMutableList, "LTE网络信号", "${data.lteSignal}" + ) + + mTrackerManageSetViewModel.getDeviceDetail( + this@DebugActivity, mPetList!![mPetSelectPosition].deviceId + ) + } + } + }) + } + + + mTrackerManageSetViewModel.mDeviceDetailLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!!.deviceInfo + data?.let { + ViewUtil.instance.addMenuBean(mMutableList, "red", data.red.toString()) + ViewUtil.instance.addMenuBean(mMutableList, "green", data.green.toString()) + ViewUtil.instance.addMenuBean(mMutableList, "blue", data.blue.toString()) +// ViewUtil.instance.addMenuBean( +// mMutableList, "设备开关机", data.turnOff.toString() +// ) + ViewUtil.instance.addMenuBean( + mMutableList, "LED灯光模式lightMode", data.lightMode.toString() + ) + } + val today = Utils.getCurrentTime(Utils.DATE_FORMAT_PATTERN_CN) + //请求该设备宠物的数据 + mDataViewModel.getHomeDeviceData( + mPetList!![mPetSelectPosition].deviceId, + today, + mPetList!![mPetSelectPosition].petId + ) + } + }) + } + + //获取首页Data模块的设备数据 + mDataViewModel.mHomeDataLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + val activeTime = data.activeTime + val sleepTime = data.sleepTime + setHomeData(activeTime!!, sleepTime!!) + } + }) + } + } + + private fun setHomeData(activeTimeBean: ActiveTimeBean, sleepTimeBean: SleepTimeBean) { + ViewUtil.instance.addMenuBean( + mMutableList, "睡眠和运动数据", "日期:${activeTimeBean.dayTime}" + ) + ViewUtil.instance.addMenuBean(mMutableList, "步数", "${activeTimeBean.steps}") + ViewUtil.instance.addMenuBean(mMutableList, "运动总时间", "${activeTimeBean.activeTime}秒") + ViewUtil.instance.addMenuBean(mMutableList, "运动数据", activeTimeBean.activeValue) + ViewUtil.instance.addMenuBean(mMutableList, "睡眠总时间", "${sleepTimeBean.sleepTime}秒") + ViewUtil.instance.addMenuBean(mMutableList, "睡眠数据", sleepTimeBean.sleepValue) + mAdapter.setData(mMutableList, true) + } + + //列表弹窗点击事件回调 + override fun onCurrentClick() { + mCommonListDialog!!.dismiss() + } + + override fun onChangeClick(position: Int) { + mCommonListDialog!!.dismiss() + mPetSelectPosition = position + setPetData() + mMapViewModel.getMapDeviceStatus(mPetList!![mPetSelectPosition].deviceId) + } + + override fun onClick(v: View?) { + when (v?.id) { + R.id.topbar_right_change_textview -> mMapViewModel.getMapDeviceStatus(mPetList!![mPetSelectPosition].deviceId) + R.id.ll_debug_pet -> { + if (null == mCommonListDialog) { + mCommonListDialog = + CommonListDialog(mContext, mChangePetListDialogAdapter, 18, 0, 18) + } + mCommonListDialog!!.show() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt new file mode 100644 index 0000000..3b2751c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/HomeV2Activity.kt @@ -0,0 +1,580 @@ +package com.abbidot.tracker.ui.activity + +import android.bluetooth.BluetoothAdapter +import android.content.Intent +import android.content.IntentFilter +import android.text.TextUtils +import android.view.MenuItem +import android.view.View +import androidx.activity.viewModels +import androidx.core.view.get +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import androidx.viewpager2.widget.ViewPager2 +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.ChangePetListDialogAdapter +import com.abbidot.tracker.adapter.ViewPagerAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityHomeV2Binding +import com.abbidot.tracker.dialog.CommonDialog2 +import com.abbidot.tracker.dialog.CommonListDialog +import com.abbidot.tracker.receiver.BluetoothMonitorReceiver +import com.abbidot.tracker.ui.DebugActivity +import com.abbidot.tracker.ui.activity.device.AddNewTracker1Activity +import com.abbidot.tracker.ui.activity.subscribe.SubscriptionPlanActivity +import com.abbidot.tracker.ui.fragment.account.AccountV2Fragment +import com.abbidot.tracker.ui.fragment.data.ActivityV2Fragment +import com.abbidot.tracker.ui.fragment.data.RouteV2Fragment +import com.abbidot.tracker.ui.fragment.map.MapV2Fragment +import com.abbidot.tracker.ui.fragment.pet.PetV2Fragment +import com.abbidot.tracker.util.BleReportManage +import com.abbidot.tracker.util.SocketUtilManage +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.DataViewModel +import com.abbidot.tracker.vm.LogBleReportViewModel +import com.abbidot.tracker.vm.SocketSendMessageViewModel +import com.abbidot.tracker.vm.SubscriptionManageViewModel +import com.clj.fastble.BleManager +import com.google.android.material.navigation.NavigationBarView +import com.hjq.permissions.XXPermissions +import com.hjq.permissions.permission.PermissionLists +import com.huantansheng.easyphotos.EasyPhotos +import com.qmuiteam.qmui.widget.QMUITopBar +import com.tencent.bugly.crashreport.CrashReport +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * v2版主页 + */ +@AndroidEntryPoint +class HomeV2Activity : BaseActivity(ActivityHomeV2Binding::inflate), + NavigationBarView.OnItemSelectedListener, ChangePetListDialogAdapter.OnChangeClickListener { + + val mDataViewModel: DataViewModel by viewModels() + private val mAutomaticConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + private val mLogBleReportViewModel: LogBleReportViewModel by viewModels() + private val mSocketSendMessageViewModel: SocketSendMessageViewModel by viewModels() + + private var mChangePetDialog: CommonListDialog? = null + + private lateinit var mChangePetListDialogAdapter: ChangePetListDialogAdapter + + //弹窗选中的宠物下标 + var mSelectPetPosition = 0 + + //宠物数据 + lateinit var mPetList: MutableList + private var isFirst = true + + //是否请求过宠物数据 + var isRequestPetData = false + + private val mFragments = mutableListOf( + ActivityV2Fragment.newInstance(this), + RouteV2Fragment.newInstance(this), + MapV2Fragment.newInstance(this), + PetV2Fragment.newInstance(this), + AccountV2Fragment.newInstance(this) + ) + private var mBleListenerReceiver: BluetoothMonitorReceiver? = null + + private var mSocketUtilManage: SocketUtilManage? = null + private val mBleReportManage: BleReportManage by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { + mSocketUtilManage = SocketUtilManage() + mSocketUtilManage?.initEasySocket(mContext) + BleReportManage(mSocketSendMessageViewModel, mSocketUtilManage!!) + } + + override fun getTopBar(): QMUITopBar? = null + + override fun initData() { + isEdgeToEdgeAdapterNavigationBars = false + super.initData() + setStatusBarLight() + + mSelectPetPosition = MMKVUtil.getInt(MMKVKey.PetSelectPosition) + + mViewBinding.homeV2ViewPager2.let { + it.adapter = ViewPagerAdapter(mFragments, this).apply { + saveState() + } + ViewUtil.instance.setViewPager2Config(it, object : ViewPager2.OnPageChangeCallback() { + override fun onPageScrolled( + position: Int, positionOffset: Float, positionOffsetPixels: Int + ) { + super.onPageScrolled(position, positionOffset, positionOffsetPixels) + val menuItem = mViewBinding.homeV2BottomTabLayout.menu[position] + menuItem.isChecked = true + } + }, 1) + + edgeToEdgeAdapterBars(it) + } + + mViewBinding.homeV2BottomTabLayout.let { + // 不使用图标默认变色 + it.itemIconTintList = null + it.setOnItemSelectedListener(this) + } + + mPetList = mutableListOf() + mChangePetListDialogAdapter = ChangePetListDialogAdapter(mContext, mPetList).apply { + setOnChangeClickListener(this@HomeV2Activity) + } + + //smoothScroll设为false,不显示动画,关闭预加载? true显示过渡动画 + mViewBinding.homeV2ViewPager2.setCurrentItem(2, false) + setLineShow(2) + + //注册监听蓝牙开关广播 + mBleListenerReceiver = BluetoothMonitorReceiver() + val intentFilter = IntentFilter() + intentFilter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED) + // 注册广播 + registerReceiver(mBleListenerReceiver, intentFilter) + + checkPermissions() + + mDataViewModel.getHomeBindPetList(this) + + //设置bugly的用户id + CrashReport.setUserId(MMKVUtil.getString(MMKVKey.Email)) + + //自动重启APP +// val intent = baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName) +// //与正常页面跳转一样可传递序列化数据,在Launch页面内获得 +// intent!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) +// intent.putExtra("REBOOT", "reboot") +// startActivity(intent) + + //预先加载相册,防止上传头像几千张照片加载慢 + XXPermissions.with(this).permission(PermissionLists.getReadMediaImagesPermission()) + .request { _, allGranted -> + if (allGranted) { + EasyPhotos.preLoad(this) +// { +// runOnUiThread { +// showToast("相册加载完成") +// } +// } + } + } + } + + override fun liveDataObserve() { + + /* + Dispatchers.IO 在Android中主要用于执行输入输出操作,如文件读写和网络请求 + Dispatchers.Main‌:适用于处理与UI相关的操作,如更新UI界面、响应用户输入等。 + Dispatchers.Default‌:适用于执行CPU密集型任务,如算法计算、数据处理等 + Dispatchers.Unconfined‌:不对协程的执行环境做任何限制,协程将在调用它的线程中执行,直到遇到挂起函数为止,之后会在恢复执行时继续在之前的线程中执行‌ + */ + + //退出登录 + XEventBus.observe(this, EventName.LogOut) { + finish() + } + //修改密码成功返回 + XEventBus.observe(this, EventName.ChangePassword) { + finish() + } + //接收开蓝牙操作,然后搜索连接设备 + XEventBus.observe(this, EventName.BluetoothSwitch) { conState: Int -> + if (conState == ConState.BLUETOOTH_ON) { +// mAutomaticConnectionDeviceViewModel.autoConnectBleDevice(mContext) + autoConnectDevice() + } + } + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + getPet(false)?.apply { + //蓝牙连接断开上传日志 + if (ble.mac == macID && ble.conState == ConState.DISCONNECTED) { + mLogBleReportViewModel.uploadLog(mContext, macID) + } + } + } + //更新套餐数据 + XEventBus.observe(this, EventName.RefreshPackage) { + mDataViewModel.getHomeBindPetList(this) + } + //更新宠物信息,添加/编辑宠物 + XEventBus.observe(this, EventName.RefreshPet) { + mDataViewModel.getHomeBindPetList(this) + } + //删除宠物 + XEventBus.observe(this, EventName.DeletePet) { + mSelectPetPosition = 0 + mDataViewModel.getHomeBindPetList(this) + } + //删除设备 + XEventBus.observe(this, EventName.DeleteDevice) { + mSelectPetPosition = 0 + mDataViewModel.getHomeBindPetList(this) + } + //获取蓝牙上报数据 + XEventBus.observe(this, EventName.BleReport) { receiveData: ReceiveDeviceData -> + lifecycleScope.launch(Dispatchers.IO) { + getPet(false)?.apply { + if (macID == receiveData.mac) { + mBleReportManage.dealBleReportData(receiveData.mac, receiveData.data) + } + } + } + } + //获取日志上报数据 + XEventBus.observe(this, EventName.LogReport) { receiveData: ReceiveDeviceData -> + getPet(false)?.apply { + if (macID == receiveData.mac) { + mLogBleReportViewModel.dealLogReportData(mContext, receiveData) + } + } + } + + //蓝牙上报的设备日志 + mLogBleReportViewModel.apply { + mUploadLogLiveData.observe(this@HomeV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { data -> + getPet(false)?.let { pet -> + val time = Utils.formatTime( + System.currentTimeMillis(), Utils.DATE_FORMAT_PATTERN_CN2 + ) + uploadDeviceLog(pet.deviceOutId, time, pet.macID, data) + } + } + } + }) + } + mUploadDeviceLogLiveData.observe(this@HomeV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { +// Toaster.show("日志上传成功") + LogUtil.e("日志上传成功") + } + }) + } + } + //获取所有宠物数据 + mDataViewModel.mHomePetLiveData.observe(this) { + dealRequestResult( + it, + object : GetResultCallback { + override fun onResult(any: Any) { + LogUtil.e("HomeV2Activity------>>获取所有绑定宠物数据") + isRequestPetData = true + mPetList.clear() + mChangePetListDialogAdapter.setData(null) + it.getOrNull()?.apply { + setPetData(this) + } + } + + override fun onRequestError(exceptionCode: String?) { + LogUtil.e("HomeV2Activity------->请求宠物数据错误onRequestError,再次请求") + if (exceptionCode == mNetworkRequestsFailLimit) { + } else { + mDataViewModel.getHomeBindPetList(this@HomeV2Activity) + } + } + }, + isShowNoDataTip = false, + isShowRequestErrorTip = mNetworkRequestsFailRetryCount == mNetworkRequestsFailMaxCount + ) + } + } + + /** + * 套餐过期查询订单回调,防止和升级套餐查询套餐回调冲突 + */ + private fun packageExpiresObserve() { + mSubscriptionViewModel.mSubscriptionsOrderLiveData.observe(this@HomeV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + val pet = mPetList[mSelectPetPosition] + for (l in list) { + if (l.deviceId == pet.deviceId) { + Intent( + mContext, SubscriptionPlanActivity::class.java + ).let { i -> + i.putExtra(ConstantString.LkSetMeal, l) + startActivity(i) + } + break + } + } + } + } + }) + mSubscriptionViewModel.mSubscriptionsOrderLiveData.removeObservers(this@HomeV2Activity) + } + } + + private fun setPetData(petList: MutableList) { + mPetList.addAll(petList) + if (mSelectPetPosition > mPetList.size - 1) { + mSelectPetPosition = 0 + } + if (petList.isEmpty()) { + noSharePet() + return + } + mChangePetListDialogAdapter.apply { + setData(mPetList, true) + if (isFirst) { + isFirst = false + setSelectPetPos(mSelectPetPosition) + //判断套餐有没有到期 + packageUnavailableState() + getPet(false)?.apply { + mLogBleReportViewModel.uploadLog(mContext, macID) + } + } else { + onChangeClick(mSelectPetPosition) + when (mViewBinding.homeV2ViewPager2.currentItem) { + 0 -> { + (mFragments[1] as RouteV2Fragment).setNeedUpdateTag() + (mFragments[3] as PetV2Fragment).setNeedUpdateTag() + } + + 1 -> { + (mFragments[0] as ActivityV2Fragment).setNeedUpdateTag() + (mFragments[3] as PetV2Fragment).setNeedUpdateTag() + } + + 2, 4 -> { + (mFragments[0] as ActivityV2Fragment).setNeedUpdateTag() + (mFragments[1] as RouteV2Fragment).setNeedUpdateTag() + (mFragments[3] as PetV2Fragment).setNeedUpdateTag() + } + + 3 -> { + (mFragments[0] as ActivityV2Fragment).setNeedUpdateTag() + (mFragments[1] as RouteV2Fragment).setNeedUpdateTag() + } + } + } + (mFragments[2] as MapV2Fragment).showPetNameAndHead(mSelectPetPosition) + } + } + + /** + * 套餐不可用状态 + */ + private fun packageUnavailableState() { + autoConnectDevice() + getPet(false)?.apply { + //判断套餐有没有到期 + if (shared == ConstantInt.NoShare && availableOrder == ConstantInt.Type0) { + CommonDialog2( + mContext, + getString(R.string.txt_subscription_expired), + getString(R.string.txt_card_recharge), + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + packageExpiresObserve() + mSubscriptionViewModel.getSubscriptionsOrder() + } + }).show() + } + } + } + + /** + * 处理多个手机登录(特别是获取设备邀请信息)。没有绑定的宠物情况 + */ + fun noSharePet() { + MMKVUtil.putInt(MMKVKey.isBindDevice, ConstantInt.Type0) + MMKVUtil.putInt(MMKVKey.isBindPet, ConstantInt.Type0) + val intent = Intent(mContext, AddNewTracker1Activity::class.java) + intent.putExtra(ConstantString.isFirstBind, true) + startActivityFinish(intent) + } + + /** + * 检查获取定位蓝牙权限 + */ + private fun checkPermissions() { + Util.checkBluetoothPermissionsEnabled(mContext, { +// mAutomaticConnectionDeviceViewModel.autoConnectBleDevice(mContext) + }) + } + + /** + * 选择宠物弹窗 + */ + fun selectPetDialog() { + if (isRequestPetData && mPetList.size == 0) { + showToast(R.string.no_bind_pet) + return + } + if (null == mChangePetDialog) { + mChangePetDialog = CommonListDialog(mContext, mChangePetListDialogAdapter, 18, 0, 18) + } + mChangePetDialog?.show() + } + + fun getPet(showNoPetTip: Boolean = true): PetBean? { + return if (mPetList.size > 0) mPetList[mSelectPetPosition] + else { + if (isRequestPetData && showNoPetTip) showToast(R.string.no_bind_pet) + null + } + } + + /** + * 跳转debug调试页面 + */ + fun goToDebugActivity() { + val intent = Intent(mContext, DebugActivity::class.java) + if (mPetList.size > mSelectPetPosition) { + intent.putParcelableArrayListExtra( + ConstantString.Pet, mPetList as ArrayList + ) + intent.putExtra(ConstantString.Index, mSelectPetPosition) + } + startActivity(intent) + } + + + override fun onCurrentClick() { + mChangePetDialog?.dismiss() + } + + override fun onChangeClick(position: Int) { + if (mPetList.size == 0) return + mChangePetDialog?.dismiss() + mSelectPetPosition = position + mChangePetListDialogAdapter.setSelectPetPos(mSelectPetPosition) + //判断套餐有没有到期 + packageUnavailableState() + when (mViewBinding.homeV2ViewPager2.currentItem) { + 0 -> (mFragments[0] as ActivityV2Fragment).showPetNameAndHead(position) + 1 -> (mFragments[1] as RouteV2Fragment).showPetNameAndHead(position) + 2 -> (mFragments[2] as MapV2Fragment).showPetNameAndHead(position) + 3 -> (mFragments[3] as PetV2Fragment).showPetNameAndHead(position) + } + } + + override fun onNavigationItemSelected(item: MenuItem): Boolean { + if (!AppUtils.isDarkThemeEnabled(mContext)) setStatusBarLight() + else setStatusBarDark() + return when (item.itemId) { + R.id.tab_menu_activity_v2 -> { + mViewBinding.homeV2ViewPager2.currentItem = 0 + setLineShow(0) + true + } + + R.id.tab_home_route_v2 -> { + mViewBinding.homeV2ViewPager2.currentItem = 1 + setLineShow(1) + true + } + + R.id.tab_home_map_v2 -> { + setStatusBarLight() + mViewBinding.homeV2ViewPager2.currentItem = 2 + setLineShow(2) + true + } + + R.id.tab_home_pet_v2 -> { + mViewBinding.homeV2ViewPager2.currentItem = 3 + setLineShow(3) + true + } + + R.id.tab_home_account_v2 -> { + mViewBinding.homeV2ViewPager2.currentItem = 4 + setLineShow(4) + true + } + + else -> false + } + } + + private fun setLineShow(which: Int) { + mViewBinding.homeActivityV2Line.visibility = View.INVISIBLE + mViewBinding.homeRouteV2Line.visibility = View.INVISIBLE + mViewBinding.homeMapV2Line.visibility = View.INVISIBLE + mViewBinding.homePetV2Line.visibility = View.INVISIBLE + mViewBinding.homeAccountV2Line.visibility = View.INVISIBLE + when (which) { + 0 -> mViewBinding.homeActivityV2Line.visibility = View.VISIBLE + 1 -> mViewBinding.homeRouteV2Line.visibility = View.VISIBLE + 2 -> mViewBinding.homeMapV2Line.visibility = View.VISIBLE + 3 -> mViewBinding.homePetV2Line.visibility = View.VISIBLE + 4 -> mViewBinding.homeAccountV2Line.visibility = View.VISIBLE + } + } + + /** + * 自动连接设备 + */ + private fun autoConnectDevice() { + getPet(false)?.apply { + if (TextUtils.isEmpty(macID)) return@apply + if (SRBleUtil.instance.isBlueEnable(mContext)) { + if (BleManager.getInstance().isConnected(macID)) { + SRBleUtil.instance.cancelBleScan("已连接设备,取消其他连接扫描") + val bleTrack = SRBleUtil.instance.getConnectMacDevice(macID) + XEventBus.post(EventName.ConnectDeviceState, bleTrack) + } else { + mAutomaticConnectionDeviceViewModel.connectDeviceToMac( + this@HomeV2Activity, macID, false + ) + } + } + } + } + + private var mTempTime = 0L + override fun listenBackPressed() { + // 2s内再次选择back键有效 + if (System.currentTimeMillis() - mTempTime > 2000) { + showToast(R.string.txt_exit_return) + mTempTime = System.currentTimeMillis() + } else { + finish() + System.gc() + } + } + + override fun onDestroy() { + super.onDestroy() + LogUtil.e("HomeV2Activity---------------onDestroy") + mSocketUtilManage?.destroy() + mBleListenerReceiver?.let { + unregisterReceiver(it) + } + BleManager.getInstance().apply { + disconnectAllDevice() + destroy() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/MyCropImageActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/MyCropImageActivity.kt new file mode 100644 index 0000000..cac41eb --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/MyCropImageActivity.kt @@ -0,0 +1,135 @@ +package com.abbidot.tracker.ui.activity + +import android.content.Intent +import android.net.Uri +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityMyCropImageBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.canhub.cropper.CropImage +import com.canhub.cropper.CropImageView +import com.huantansheng.easyphotos.models.album.entity.Photo + +/** + * 裁剪图片 + */ +class MyCropImageActivity : + BaseActivity(ActivityMyCropImageBinding::inflate), + CropImageView.OnCropImageCompleteListener, CropImageView.OnSetImageUriCompleteListener { + + private val mTag = "裁剪图片" + + override fun getTopBar() = mViewBinding.photoCropImageBar.titleTopBar + + override fun initData() { + super.initData() + initStatus() + + var aspectRatioX = 1 + var aspectRatioY = 1 + var photo: Photo? = null + intent.extras?.apply { + aspectRatioX = getInt(ConstantString.Type, 1) + aspectRatioY = getInt(ConstantString.Index, 1) + photo = Util.getParcelableAdaptive(intent, ConstantString.Photo, Photo::class.java) + } + + if (null == photo) { + LogUtil.e(mTag, "null == photo") + } else { + mViewBinding.photoCropImageView.apply { + setImageUriAsync(photo!!.uri) + //最小裁剪框大小 +// setMinCropResultSize(10000, 10000) + //裁剪的图片宽高比 + setAspectRatio(aspectRatioX, aspectRatioY) + setOnCropImageCompleteListener(this@MyCropImageActivity) + setOnSetImageUriCompleteListener(this@MyCropImageActivity) + } + } + } + + private fun initStatus() { + setTopBarTitle(R.string.txt_crop) + mViewBinding.root.setBackgroundResource(R.color.crop_photos_status_bar) + mTopBar?.titleView?.setTextColor(ContextCompat.getColor(mContext, R.color.white)) + setStatusBarDark() + //设置沉浸状态栏颜色 +// mViewBinding.photoCropImageBar.titleTopBarLayout.setBackgroundResource( +// R.color.crop_photos_status_bar +// ) + setLeftBackImage(R.drawable.ico_cancel_detail) + + val rightTextButton = + mTopBar!!.addRightTextButton(R.string.txt_sure, R.id.topbar_right_change_textview) + rightTextButton.apply { + setTextColor(ContextCompat.getColor(mContext, R.color.crop_images_fg_accent)) + background = + ContextCompat.getDrawable(mContext, R.drawable.selector_transparent_pressed) + val tf = ViewUtil.instance.setTypeface(mContext, getString(R.string.roboto_bold_font)) + typeface = tf + setOnClickListener { + mViewBinding.photoCropImageView.croppedImageAsync() + } + } + } + + override fun onCropImageComplete(view: CropImageView, result: CropImageView.CropResult) { + if (result.isSuccessful) { + val uriContent = result.uriContent + //uniqueName=true每个被裁剪的图像有一个不同的文件名,false始终只有一个文件名,节省内存 + val uriFilePath = result.getUriFilePath(mContext, false) + LogUtil.d(mTag, "成功----------->$uriContent\n$uriFilePath") + } else { + val exception = result.error + LogUtil.e(mTag, "error:${exception?.message}") + showToast(exception?.message.toString()) + } + + setResult(result.uriContent, result.error, result.sampleSize) + } + + + private fun setResult(uri: Uri?, error: Exception?, sampleSize: Int) { + setResult(error?.let { CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE } ?: RESULT_OK, + getResultIntent(uri, error, sampleSize)) + finish() + } + + /** + * Get intent instance to be used for the result of this activity. + */ + private fun getResultIntent(uri: Uri?, error: Exception?, sampleSize: Int): Intent { + val cropImageView = mViewBinding.photoCropImageView + val result = CropImage.ActivityResult( + cropImageView.imageUri, + uri, + error, + cropImageView.cropPoints, + cropImageView.cropRect, + cropImageView.rotatedDegrees ?: 0, + cropImageView.wholeImageRect, + sampleSize + ) + val intent = Intent() + intent.putExtras(getIntent()) + intent.putExtra(CropImage.CROP_IMAGE_EXTRA_RESULT, result) + return intent + } + + override fun onSetImageUriComplete(view: CropImageView, uri: Uri, error: Exception?) { + if (null != error) { + LogUtil.e(mTag, "加载原图uri错误,Failed to load image by URI, ------>${error.message}") + } + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.photoCropImageView.setOnCropImageCompleteListener(null) + mViewBinding.photoCropImageView.setOnSetImageUriCompleteListener(null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/NotificationActivity.java b/app/src/main/java/com/abbidot/tracker/ui/activity/NotificationActivity.java new file mode 100644 index 0000000..1f4c9cf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/NotificationActivity.java @@ -0,0 +1,33 @@ +package com.abbidot.tracker.ui.activity; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + +import androidx.annotation.Nullable; + +import com.abbidot.tracker.ui.activity.device.MyTrackerV2Activity; + + +/** + * Dfu通知Activity + */ +public class NotificationActivity extends Activity { + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // If this activity is the root activity of the task, the app is not running + if (isTaskRoot()) { + // Start the app before finishing + final Intent intent = new Intent(this, MyTrackerV2Activity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtras(getIntent().getExtras()); // copy all extras + startActivity(intent); + } + + // Now finish, which will drop you to the activity at which you were at the top of the task stack + finish(); + } + +} diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/SayHelloActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/SayHelloActivity.kt new file mode 100644 index 0000000..4aa2f7d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/SayHelloActivity.kt @@ -0,0 +1,52 @@ +package com.abbidot.tracker.ui.activity + +import android.content.Intent +import android.view.View +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivitySayHelloBinding +import com.abbidot.tracker.ui.activity.account.CreateAccountV2Activity +import com.abbidot.tracker.ui.activity.account.LoginV2Activity + +class SayHelloActivity : BaseActivity(ActivitySayHelloBinding::inflate) { + + override fun getTopBar() = null + + override fun initData() { + super.initData() + + mViewBinding.apply { + root.setBackgroundResource(R.drawable.icon_say_hi_bg) + setOnClickListenerViews(btnSayHelloLogin, btnSayHelloSignup) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.FinishActivity) { + finish() + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSayHelloLogin -> { + val intent = Intent(mContext, LoginV2Activity::class.java) + val email = MMKVUtil.getString(MMKVKey.Email) + intent.putExtra(MMKVKey.Email, email) + startActivity(intent) + } + + btnSayHelloSignup -> startActivity( + Intent( + mContext, CreateAccountV2Activity::class.java + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt new file mode 100644 index 0000000..2e52c01 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/SplashActivity.kt @@ -0,0 +1,264 @@ +package com.abbidot.tracker.ui.activity + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.annotation.SuppressLint +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Bundle +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat +import cn.jpush.android.api.JPushInterface +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.KeyNames +import com.abbidot.tracker.databinding.ActivitySplashBinding +import com.abbidot.tracker.dialog.PrivacyPolicyDialog +import com.abbidot.tracker.retrofit2.INetworkService +import com.abbidot.tracker.util.ThirdPartyUtil +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.CountDownTimerViewModel +import com.baidu.mapapi.CoordType +import com.baidu.mapapi.SDKInitializer +import com.hjq.toast.Toaster +import com.qmuiteam.qmui.arch.QMUIActivity +import com.stripe.android.PaymentConfiguration +import com.tencent.bugly.crashreport.CrashReport +import com.tencent.mm.opensdk.constants.ConstantsAPI +import com.tencent.mm.opensdk.openapi.IWXAPI +import java.util.* + + +/** + * 入口,启动页 + * + *适配不同Android版本的启动动画,Android 12强制有启动图片 https://www.yii666.com/blog/282005.html + * 新的ActivityResultContract替换onActivityResult https://www.jianshu.com/p/da4c0e87d8b4 + * + */ +class SplashActivity : QMUIActivity() { + + private lateinit var mContext: Context + + private lateinit var mViewBinding: ActivitySplashBinding + + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + + // IWXAPI 是第三方 app 和微信通信的 openApi 接口 + private lateinit var mApi: IWXAPI + + //微信启动广播 + private var mWXAppBroadcastReceiver: BroadcastReceiver? = null + + @SuppressLint("WrongThread") + override fun onCreate(savedInstanceState: Bundle?) { + installSplashScreen() + super.onCreate(savedInstanceState) + //禁用屏幕旋转 代码设置无效 +// requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + mViewBinding = ActivitySplashBinding.inflate(layoutInflater) + setContentView(mViewBinding.root) + + mContext = this + + //清除角标数字 + JPushInterface.setBadgeNumber(mContext, 0) + val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + // 清除所有通知,角标数字 + notificationManager.cancelAll() + + Utils.SECRET_KEY = Util.getMetadata(applicationContext, KeyNames.HTTP_REQUEST_KEY) + INetworkService.apply { + //国内外服务器切换域名 + BASE_URL = if (AppUtils.isChina()) { + BASE_URL_CN + } else { + BASE_URL_ABROAD + } + } + + SRBleUtil.instance.init(application) + + val cd = Calendar.getInstance() + val year = cd[Calendar.YEAR] + mViewBinding.ivSplashName.apply { + text = getString(R.string.system_txt_copyright, year) + ViewCompat.setOnApplyWindowInsetsListener(this) { v, insets -> + val bars = insets.getInsets(WindowInsetsCompat.Type.navigationBars()) + v.setPadding(bars.left, bars.top, bars.right, bars.bottom) + insets + } + } + + if (AppUtils.isDebug()) { + mViewBinding.ivSplashDebug.visibility = View.VISIBLE + } + + mCountDownTimerViewModel.mCountDownEndLiveData.observe(this) { + mViewBinding.splashLottieAnimationView.cancelAnimation() + val userId = MMKVUtil.getString(MMKVKey.UserId) + if (TextUtils.isEmpty(userId)) { + startActivity(Intent(mContext, SayHelloActivity::class.java)) + } else { + startActivity(Util.checkBindDeviceAndPetGoActivity(mContext)) + } + overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left) + finish() + } + + + mViewBinding.splashLottieAnimationView.apply { + setAnimation("lottie/logo_loader.json") + addAnimatorListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator) { + mViewBinding.splashLogoIv.visibility = View.VISIBLE + mCountDownTimerViewModel.startCountDown(2) + } + }) + } + + val firstOpen = MMKVUtil.getBoolean(MMKVKey.FirstOpen, true) + if (firstOpen) { + //隐私协议弹窗 + showPrivacyPolicyDialog() + } else { + acceptPrivacyPolicy() + } + } + + // 字体大小不跟随系统 + override fun attachBaseContext(newBase: Context) { + super.attachBaseContext(getConfigurationContext(newBase)) + } + + // 字体大小不跟随系统 + private fun getConfigurationContext(context: Context): Context { + val configuration = context.resources.configuration + configuration.fontScale = 1F + return context.createConfigurationContext(configuration) + } + + /** + * 显示隐私协议弹窗 + */ + private fun showPrivacyPolicyDialog() { + val privacyPolicyDialog = PrivacyPolicyDialog(this@SplashActivity) + privacyPolicyDialog.apply { + show() + agreePrivacyPolicyCallback { + if (!isAgree()) { + Toaster.show(R.string.register_txt_agree) + return@agreePrivacyPolicyCallback + } + dismiss() + MMKVUtil.putBoolean(MMKVKey.FirstOpen, false) + acceptPrivacyPolicy() + } + } + } + + /** + * 同意隐私协议操作 + */ + private fun acceptPrivacyPolicy() { +// mViewBinding.splashLottieAnimationView.apply { +// postDelayed({ +// playAnimation() +// }, 600) +// } + mViewBinding.splashLogoIv.visibility = View.VISIBLE + mCountDownTimerViewModel.startCountDown(2) + + init() + } + + private fun init() { + LogUtil.e("isDebug=${AppUtils.isDebug()}") + JPushInterface.setDebugMode(AppUtils.isDebug()) + JPushInterface.init(this) + + //同意协议 + SDKInitializer.setAgreePrivacy(application, true) +// LocationClient.setAgreePrivacy(true); + //在使用SDK各组件之前初始化context信息,传入ApplicationContext + SDKInitializer.initialize(application) + //自4.3.0起,百度地图SDK所有接口均支持百度坐标和国测局坐标,用此方法设置您使用的坐标类型. + //包括BD09LL和GCJ02两种坐标,默认是BD09LL坐标。 + SDKInitializer.setCoordType(CoordType.GCJ02) + + if (AppUtils.isChina()) { + regToWx() + } else { + initMapbox() + try { + //stripe支付 + val stripeKey = Util.getMetadata(applicationContext, KeyNames.STRIPE_TEST_KEY) + LogUtil.e("stripeKey=$stripeKey") + PaymentConfiguration.init( + applicationContext, stripeKey + ) + } catch (e: Exception) { + LogUtil.e("stripeSdk initialize初始化异常,${e.message}") + } + } + + if (AppUtils.isDebug()) { + //方便调试测试只使用谷歌地图情况 + initMapbox() + } + + val buglyKey = Util.getMetadata(applicationContext, KeyNames.BUG_LY_KEY_NAME) + //腾讯bugly初始化 + CrashReport.initCrashReport(applicationContext, buglyKey, AppUtils.isDebug()) + } + + private fun initMapbox() { +// try { +// val mapboxAccessToken = +// Util.getMetadata(applicationContext, KeyNames.MAPBOX_ACCESS_TOKEN) +// MapboxSearchSdk.initialize( +// application, mapboxAccessToken, LocationEngineProvider.getBestLocationEngine(this) +// ) +// } catch (e: Exception) { +// LogUtil.e("MapboxSearchSdk initialize多次初始化异常") +// } + } + + /** + * 注册到微信 + */ + private fun regToWx() { + mApi = ThirdPartyUtil.mApi + // 将应用的 appId 注册到微信 +// mApi.registerApp(ThirdPartyUtil.wXAppID) + + mWXAppBroadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + // 将该 app 注册到微信 + mApi.registerApp(ThirdPartyUtil.WXAppID) + } + } + //建议动态监听微信启动广播进行注册到微信 + registerReceiver(mWXAppBroadcastReceiver, IntentFilter(ConstantsAPI.ACTION_REFRESH_WXAPP)) + } + + override fun onDestroy() { + super.onDestroy() + mWXAppBroadcastReceiver?.let { + unregisterReceiver(it) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/WebViewActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/WebViewActivity.kt new file mode 100644 index 0000000..4789d49 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/WebViewActivity.kt @@ -0,0 +1,140 @@ +package com.abbidot.tracker.ui.activity + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.webkit.WebView +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PayResultBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityWebViewBinding +import com.abbidot.tracker.deprecated.ui.activity.profile.recharge.PaymentResultActivity +import com.abbidot.tracker.ui.activity.subscribe.PaymentSuccessActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.widget.BrowserView + +class WebViewActivity : BaseActivity(ActivityWebViewBinding::inflate) { + + //支付成功后的结果 + private var mPayResult: PayResultBean? = null + + override fun getTopBar() = mViewBinding.webViewBar.titleTopBar + + companion object { + fun startWebViewActivity(activity: Activity, title: String, url: String) { + val intent = Intent(activity, WebViewActivity::class.java) + intent.putExtra("url", url) + intent.putExtra("title", title) + activity.startActivity(intent) + } + } + + @SuppressLint("SetJavaScriptEnabled") + override fun initData() { + super.initData() + + intent.extras?.apply { + setTopBarTitle(this.getString(ConstantString.Title).toString()) + val url = this.getString(ConstantString.Url).toString() + LogUtil.e(url) + + mPayResult = Util.getParcelableAdaptive( + intent, ConstantString.Amount, PayResultBean::class.java + ) + + mViewBinding.webViewQmuiWebView.apply { + setBrowserViewClient(MyBrowserViewClient(this@WebViewActivity, mPayResult)) + setBrowserChromeClient(MyBrowserChromeClient(mViewBinding.webViewQmuiWebView)) + setDownloadListener { url1, userAgent, contentDisposition, mimetype, contentLength -> + val uri = Uri.parse(url1) + val intent = Intent(Intent.ACTION_VIEW, uri) + startActivity(intent) + } + loadUrl(url) + } + + } + } + + override fun onResume() { + mViewBinding.webViewQmuiWebView.onResume() + super.onResume() + } + + override fun onPause() { + mViewBinding.webViewQmuiWebView.onPause() + super.onPause() + } + + override fun onDestroy() { + mViewBinding.webViewQmuiWebView.onDestroy() + super.onDestroy() + } + + + private class MyBrowserViewClient(activity: BaseActivity<*>, payResult: PayResultBean? = null) : + BrowserView.BrowserViewClient() { + + private val mActivity = activity + private val mPayResult = payResult + + /** + * 网页加载错误时回调,这个方法会在 onPageFinished 之前调用 + */ + override fun onReceivedError( + view: WebView?, errorCode: Int, description: String?, failingUrl: String? + ) { + // 这里为什么要用延迟呢?因为加载出错之后会先调用 onReceivedError 再调用 onPageFinished + } + + /** + * 开始加载网页 + */ + override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) { + LogUtil.e("开始拦截:$url") + url?.apply { + //支付成功和失败 + if (url.startsWith("https://www.baidu.com")) { + val intent = Intent(mActivity, PaymentSuccessActivity::class.java) + intent.putExtra(ConstantString.Amount, mPayResult) + mActivity.startActivityFinish(intent) + } else if (url.startsWith("https://www.qq.com")) { + val intent = Intent(mActivity, PaymentResultActivity::class.java) + intent.putExtra(ConstantString.Type, ConstantInt.Type2) + mActivity.startActivityFinish(intent) + } + } + } + + /** + * 完成加载网页 + */ + override fun onPageFinished(view: WebView?, url: String?) { + } + } + + private class MyBrowserChromeClient(view: BrowserView) : BrowserView.BrowserChromeClient(view) { + /** + * 收到网页标题 + */ + override fun onReceivedTitle(view: WebView?, title: String?) { + } + + /** + * 收到加载进度变化 + */ + override fun onProgressChanged(view: WebView?, newProgress: Int) { + } + } + + override fun leftBackOnClick() { + if (mViewBinding.webViewQmuiWebView.canGoBack()) { + // 后退网页并且拦截该事件 + mViewBinding.webViewQmuiWebView.goBack() + } else super.leftBackOnClick() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/AddEmailValidActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/AddEmailValidActivity.kt new file mode 100644 index 0000000..3cce1f0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/AddEmailValidActivity.kt @@ -0,0 +1,86 @@ +package com.abbidot.tracker.ui.activity.account + +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import coil.load +import coil.transform.BlurTransformation +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityAddEmailValidBinding +import com.abbidot.tracker.ui.fragment.account.EmailValidFragment + +open class AddEmailValidActivity : + BaseActivity(ActivityAddEmailValidBinding::inflate) { + + lateinit var mFragmentList: MutableList + lateinit var mEmailValidFragment: EmailValidFragment + + var mIndex = 0 + + //用户邮箱 + var mUserEmail = "" + + override fun getTopBar() = mViewBinding.forgotPasswordV2TopBar.titleTopBar + + override fun initData() { + super.initData() + mViewBinding.apply { + setLeftBackImage(R.drawable.icon_black_back_svg) + root.setBackgroundResource(R.color.transparent) + ivForgotPasswordV2BlurBg.load(R.drawable.icon_login_v2_bg) { + transformations(BlurTransformation(mContext, 25f)) + } + setListenKeyboardChange(750) + } + + switchFragment(mIndex) + mEmailValidFragment.setEmail(mUserEmail) + } + + fun setToEmail(email: String) { + mUserEmail = email + mEmailValidFragment.setEmail(mUserEmail) + } + + fun switchFragment(index: Int) { + mIndex = index + + supportFragmentManager.commit { + + for (fragment in mFragmentList) { + hideFragment(this, fragment) + } + + /** + * 为FragmentTransaction添加指定的自定义属性动画 + */ + setCustomAnimations( + R.anim.slide_in_right, + R.anim.slide_out_left, + R.anim.slide_still, + R.anim.slide_out_right + ) + + if (mFragmentList[index].isAdded) { + show(mFragmentList[index]) + } else { + add(R.id.fc_forgot_password_v2_frame, mFragmentList[index]) + } + } + } + + private fun popBack() { + if (mIndex > 0) { + mIndex-- + switchFragment(mIndex) + } else finish() + } + + override fun leftBackOnClick() { + popBack() + } + + override fun listenBackPressed() { + popBack() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeEmailActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeEmailActivity.kt new file mode 100644 index 0000000..38f2a60 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeEmailActivity.kt @@ -0,0 +1,59 @@ +package com.abbidot.tracker.ui.activity.account + +import android.content.Intent +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.ui.fragment.account.EmailValidFragment +import com.abbidot.tracker.ui.fragment.account.ForgotPasswordV2OneFragment +import com.abbidot.tracker.vm.UserProfileViewModel + +class ChangeEmailActivity : AddEmailValidActivity() { + + private val mUserViewModel: UserProfileViewModel by viewModels() + + override fun initData() { + //验证老的邮箱验证码 + val oldEmailValidFragment = EmailValidFragment.newInstance(mContext, 2) { + switchFragment(1) + }.apply { + setEmail(MMKVUtil.getString(MMKVKey.Email)) + } + mEmailValidFragment = + EmailValidFragment.newInstance(mContext, 3, getString(R.string.txt_change_now)) { + updateEmail() + } + mFragmentList = mutableListOf( + oldEmailValidFragment, ForgotPasswordV2OneFragment.newInstance( + mContext, getString(R.string.txt_enter_new_email), 3, 1 + ), mEmailValidFragment + ) + super.initData() + } + + override fun liveDataObserve() { + mUserViewModel.mUpdateUserInfoLiveData.observe(this@ChangeEmailActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + MMKVUtil.putString(MMKVKey.Email, mUserEmail) + XEventBus.post(EventName.UpdateUserInfo) + startActivityFinish(Intent(mContext, ChangeEmailSuccessActivity::class.java)) + } + }) + } + } + + private fun updateEmail() { + val name = MMKVUtil.getString(MMKVKey.UserName) + val countryCode = MMKVUtil.getString(MMKVKey.CountryCode) + val email = mUserEmail + val imageUrl = MMKVUtil.getString(MMKVKey.HeadUrl) + mUserViewModel.updateUserInfo( + this@ChangeEmailActivity, email, imageUrl, countryCode, name + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeEmailSuccessActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeEmailSuccessActivity.kt new file mode 100644 index 0000000..25c9fbf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeEmailSuccessActivity.kt @@ -0,0 +1,26 @@ +package com.abbidot.tracker.ui.activity.account + +import android.view.View +import coil.load +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityChangeEmailSuccessBinding + +class ChangeEmailSuccessActivity : + BaseActivity(ActivityChangeEmailSuccessBinding::inflate) { + override fun getTopBar() = null + + override fun initData() { + super.initData() + + mViewBinding.apply { + ivChangeEmailSuccessfulDone.load(R.drawable.pwd_reset_successful_done) + + setOnClickListenerViews(btnChangeEmailSuccessfulOk) + } + } + + override fun onClick(v: View?) { + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeNameActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeNameActivity.kt new file mode 100644 index 0000000..db16d15 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ChangeNameActivity.kt @@ -0,0 +1,81 @@ +package com.abbidot.tracker.ui.activity.account + +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityChangeNameBinding +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + +class ChangeNameActivity : + BaseActivity(ActivityChangeNameBinding::inflate) { + + private val mUserViewModel: UserProfileViewModel by viewModels() + + override fun getTopBar() = mViewBinding.ilChangeNameTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_change_name) + + val username = MMKVUtil.getString(MMKVKey.UserName) + mViewBinding.apply { + ilChangeNameNameLayout.let { + it.etInputContent.setText(username) + it.tvInputNameTitle.text = getString(R.string.register_txt_name) + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvChangeNameNameErrorTip, it.etInputContent + ) + } + + setOnClickListenerViews(btnChangeNameSave) + } + } + + override fun liveDataObserve() { + mUserViewModel.mUpdateUserInfoLiveData.observe(this@ChangeNameActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val name = mViewBinding.ilChangeNameNameLayout.etInputContent.text.toString() + MMKVUtil.putString(MMKVKey.UserName, name) + XEventBus.post(EventName.UpdateUserInfo) + showToast(R.string.txt_change_successful, true) + } + }) + } + } + + private fun userProfileSave() { + mViewBinding.apply { + val name = ilChangeNameNameLayout.etInputContent.text.toString() + val uploadImageUrl = MMKVUtil.getString(MMKVKey.HeadUrl) + val countryCode = MMKVUtil.getString(MMKVKey.CountryCode) + val email = MMKVUtil.getString(MMKVKey.Email) + if (TextUtils.isEmpty(name)) { + ViewUtil.instance.setInputViewErrorShow( + mContext, ilChangeNameNameLayout.rlEtInputLayout, tvChangeNameNameErrorTip + ) + return + } + + mUserViewModel.updateUserInfo( + this@ChangeNameActivity, "", uploadImageUrl, countryCode, name + ) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnChangeNameSave -> userProfileSave() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/CreateAccountActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/CreateAccountActivity.kt new file mode 100644 index 0000000..d8ea27d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/CreateAccountActivity.kt @@ -0,0 +1,232 @@ +package com.abbidot.tracker.ui.activity.account + +import android.content.Intent +import android.text.InputType +import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.TextUtils +import android.text.method.LinkMovementMethod +import android.text.style.ClickableSpan +import android.view.Gravity +import android.view.View +import android.widget.TextView +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.core.widget.addTextChangedListener +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityCreateAccountBinding +import com.abbidot.tracker.ui.common.CountryCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + + +class CreateAccountActivity : + BaseActivity(ActivityCreateAccountBinding::inflate) { + + private val mUserViewModel: UserProfileViewModel by viewModels() + + private lateinit var mCountryCommon: CountryCommon + + private var isShowPassword = false + + override fun getTopBar() = mViewBinding.createAccountTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + ilCreateAccountFullName.let { + it.tvInputNameTitle.text = getString(R.string.register_txt_name) + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvFullNameErrorTip, it.etInputContent + ) + } + ilCreateAccountCountry.let { + it.tvInputNameTitle.text = getString(R.string.txt_country) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvSelectCountryErrorTip, it.etInputContent + ) + } + ilCreateAccountEmailAddress.let { + it.tvInputNameTitle.text = getString(R.string.txt_email_address) + it.etInputContent.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvEmailErrorTip, it.etInputContent + ) + } + ilCreateAccountPassword.let { + it.tvInputNameTitle.text = getString(R.string.login_txt_password) + ViewUtil.instance.setShowAndHidePassword( + it.etInputContent, it.ivInputRepresentImage + ) + it.etInputContent.addTextChangedListener { str -> + it.rlEtInputLayout.background = ContextCompat.getDrawable( + mContext, R.drawable.shape8_gray_border_white_bg + ) + tvPasswordErrorTip.setTextColor( + ContextCompat.getColor( + mContext, R.color.blue_color1 + ) + ) + str?.apply { + if (length >= 6) { + tvPasswordErrorTip.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.icon_input_check_svg, 0, 0, 0 + ) + } else { + tvPasswordErrorTip.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, 0, 0 + ) + } + } + } + } + +// setSpannableClick( +// ilPolicyTermsCheck.tvLoginAgreePrivacyContent, +// getString(R.string.txt_create_account_terms1), +// getString(R.string.txt_create_account_terms2) +// ) + + mCountryCommon = + CountryCommon(mContext, object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + ilCreateAccountCountry.etInputContent.setText(mCountryCommon.getCountry()) + } + }) + + setOnClickListenerViews( + ilCreateAccountCountry.rlEtInputLayout, + ilCreateAccountCountry.ivInputRepresentImage, + ilCreateAccountPassword.ivInputRepresentImage, + btnCreateAccountSignup + ) + } + + mUserViewModel.getCountryCodes(this) + } + + private fun setSpannableClick(textView: TextView, str1: String, str2: String) { + textView.apply { + val span1 = SpannableString(str1) + span1.setSpan(object : ClickableSpan() { + override fun onClick(v: View) { + ViewUtil.instance.goServiceContract(mActivity) + } + }, 40, 59, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + val span2 = SpannableString(str2) + span2.setSpan(object : ClickableSpan() { + override fun onClick(v: View) { + ViewUtil.instance.goPrivacyPolicy(mActivity) + } + }, 0, 14, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + val spannableStringBuilder = SpannableStringBuilder() + spannableStringBuilder.append(span1) + spannableStringBuilder.append(" ") + spannableStringBuilder.append(span2) + text = spannableStringBuilder + movementMethod = LinkMovementMethod.getInstance() + } + } + + override fun liveDataObserve() { + mUserViewModel.apply { + mCountryCodeLiveData.observe(this@CreateAccountActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mCountryCommon.setCountryCodeData(this) + } + } + }) + } + mRegisterV2LiveData.observe(this@CreateAccountActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + Util.dealLoginSuccessData(mContext, this) + val intent = Intent(mContext, SignupSuccessActivity::class.java) + startActivityFinish(intent) + } + } + }) + } + } + } + + private fun signupClick() { + mViewBinding.apply { + val name = ilCreateAccountFullName.etInputContent.text.toString() + val country = ilCreateAccountCountry.etInputContent.text.toString() + val email = ilCreateAccountEmailAddress.etInputContent.text.toString() + var password = ilCreateAccountPassword.etInputContent.text.toString() + var isSure = true + if (TextUtils.isEmpty(name)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilCreateAccountFullName.rlEtInputLayout, tvFullNameErrorTip + ) + } + if (TextUtils.isEmpty(country)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilCreateAccountCountry.rlEtInputLayout, tvSelectCountryErrorTip + ) + } + if (TextUtils.isEmpty(email) || !Utils.isValidEmail(email)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilCreateAccountEmailAddress.rlEtInputLayout, tvEmailErrorTip + ) + } + if (TextUtils.isEmpty(password) || password.length < 6) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilCreateAccountPassword.rlEtInputLayout, tvPasswordErrorTip + ) + } + if (!isSure) return + + if (!ilPolicyTermsCheck.cbLoginAgreePrivacyCheckbox.isChecked) { + showToast(R.string.register_txt_agree,gravity = Gravity.CENTER) + return + } + + password = Utils.get32Md5Value(password) + mUserViewModel.registerV2( + this@CreateAccountActivity, email, password, name, mCountryCommon.getCountryCode() + ) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilCreateAccountCountry.rlEtInputLayout, ilCreateAccountCountry.ivInputRepresentImage -> mCountryCommon.showCountryCodeDialog() + + ilCreateAccountPassword.ivInputRepresentImage -> { + isShowPassword = !isShowPassword + ViewUtil.instance.showAndHidePasswordClick( + ilCreateAccountPassword.etInputContent, + ilCreateAccountPassword.ivInputRepresentImage, + isShowPassword + ) + } + + btnCreateAccountSignup -> signupClick() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/CreateAccountV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/CreateAccountV2Activity.kt new file mode 100644 index 0000000..7c59aac --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/CreateAccountV2Activity.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.ui.activity.account + +import com.abbidot.tracker.ui.fragment.account.CreateAccountV2OneFragment +import com.abbidot.tracker.ui.fragment.account.CreateAccountV2ThreeFragment +import com.abbidot.tracker.ui.fragment.account.EmailValidFragment + +/** + *Created by .yzq on 2024/8/2/002. + * @link + * @description: + */ +class CreateAccountV2Activity : AddEmailValidActivity() { + + override fun initData() { + mEmailValidFragment = EmailValidFragment.newInstance(mContext, 1) { + switchFragment(2) + } + mFragmentList = mutableListOf( + CreateAccountV2OneFragment.newInstance(mContext), + mEmailValidFragment, + CreateAccountV2ThreeFragment.newInstance(mContext) + ) + super.initData() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/EmailAddressActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/EmailAddressActivity.kt new file mode 100644 index 0000000..c7ac1cd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/EmailAddressActivity.kt @@ -0,0 +1,72 @@ +package com.abbidot.tracker.ui.activity.account + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityEmailAddressBinding +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + +class EmailAddressActivity : + BaseActivity(ActivityEmailAddressBinding::inflate) { + + private val mRegisterViewModel: UserProfileViewModel by viewModels() + + override fun getTopBar() = mViewBinding.ilEmailAddressTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_email_address) + + mViewBinding.apply { + setOnClickListenerViews(btnEmailAddressChange) + } + } + + override fun onResume() { + super.onResume() + val email = MMKVUtil.getString(MMKVKey.Email) + mViewBinding.apply { + tvEmailAddressShow.text = email + } + } + + override fun liveDataObserve() { + mRegisterViewModel.mGetEmailCodeLiveData.observe(this) { + mViewBinding.btnEmailAddressChange.isEnabled = true + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + startActivity(Intent(mContext, ChangeEmailActivity::class.java)) + } + }) + } + } + + private fun startChange() { + val email = MMKVUtil.getString(MMKVKey.Email) + val title = String.format(getString(R.string.txt_change_email_tip), email) + ViewUtil.instance.showDialog( + mContext, title, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mViewBinding.btnEmailAddressChange.isEnabled = false + mRegisterViewModel.getEmailCode(this@EmailAddressActivity, email, 2) + } + }, okTextResId = R.string.txt_sure + ) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnEmailAddressChange -> startChange() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/ForgotPasswordV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ForgotPasswordV2Activity.kt new file mode 100644 index 0000000..716565a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ForgotPasswordV2Activity.kt @@ -0,0 +1,31 @@ +package com.abbidot.tracker.ui.activity.account + +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.tracker.R +import com.abbidot.tracker.ui.fragment.account.EmailValidFragment +import com.abbidot.tracker.ui.fragment.account.ForgotPasswordV2OneFragment +import com.abbidot.tracker.ui.fragment.account.ForgotPasswordV2ThereFragment + +/** + *Created by .yzq on 2024/8/2/002. + * @link + * @description: + */ +class ForgotPasswordV2Activity : AddEmailValidActivity() { + + override fun initData() { + intent.extras?.apply { + mUserEmail = getString(MMKVKey.Email, "") + } + mEmailValidFragment = EmailValidFragment.newInstance(mContext, 2) { + switchFragment(2) + } + mFragmentList = mutableListOf( + ForgotPasswordV2OneFragment.newInstance( + mContext, getString(R.string.txt_change_pwd), 2 + ), mEmailValidFragment, ForgotPasswordV2ThereFragment.newInstance(mContext) + ) + super.initData() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/LoginV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/LoginV2Activity.kt new file mode 100644 index 0000000..43cc90a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/LoginV2Activity.kt @@ -0,0 +1,252 @@ +package com.abbidot.tracker.ui.activity.account + +import android.content.Intent +import android.graphics.Paint +import android.text.InputFilter +import android.text.InputType +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityLoginV2Binding +import com.abbidot.tracker.ui.activity.set.FirstPermissionsActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.LoginViewModel +import com.abbidot.tracker.vm.TrackerInfoViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class LoginV2Activity : BaseActivity(ActivityLoginV2Binding::inflate) { + + private val mLoginViewModel: LoginViewModel by viewModels() + private val mTrackerViewModel: TrackerInfoViewModel by viewModels() + + private var isShowPassword = false + private var isCurrentPostEven = false + + override fun getTopBar() = mViewBinding.loginV2TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + mViewBinding.apply { + root.setBackgroundResource(R.drawable.icon_login_v2_bg) + + var userEmail = "" + intent.extras?.apply { + userEmail = getString(MMKVKey.Email, "") + } + ilLoginV2EmailAddress.let { + it.tvInputNameTitle.setText(R.string.txt_email_address) + it.etInputContent.setHint(R.string.login_txt_email) + it.etInputContent.setText(userEmail) + it.etInputContent.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvEmailErrorTipLogin, it.etInputContent + ) + } + ilLoginV2Password.let { + it.tvInputNameTitle.setText(R.string.login_txt_password) + it.etInputContent.setHint(R.string.login_txt_password) + ViewUtil.instance.setShowAndHidePassword( + it.etInputContent, it.ivInputRepresentImage + ) + val filters = arrayOf(InputFilter.LengthFilter(60)) + it.etInputContent.filters = filters + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvPasswordErrorTipLogin, it.etInputContent + ) + } + //添加下划线 + tvLoginV2ForgotPassword.paintFlags = Paint.UNDERLINE_TEXT_FLAG + tvLoginV2Signup.paintFlags = Paint.UNDERLINE_TEXT_FLAG + + setOnClickListenerViews( + ilLoginV2EmailAddress.etInputContent, + tvLoginV2Signup, + tvLoginV2ForgotPassword, + ilLoginV2Password.ivInputRepresentImage, + btnLoginV2Login + ) + + if (AppUtils.isDebug()) { + //不需要MD5加密登录 + btnLoginV2Login.setOnLongClickListener { + loginClick(false) + true + } + } + + setListenKeyboardChange(750) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.FinishActivity) { + if (!isCurrentPostEven) finish() + } + + mLoginViewModel.mLoginLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + isCurrentPostEven = true + Util.dealLoginSuccessData(mContext, this) + + showToast(R.string.txt_login_successful, gravity = Gravity.CENTER) + loginSuccessGo() + //登录成功,把保存的设备存本地 +// mTrackerViewModel.getDeviceList(this@LoginV2Activity, 1) + } + } + }) + setButtonEnabled( + mViewBinding.btnLoginV2Login, ConstantInt.Type1 + ) + } + + //获取设备列表 + mTrackerViewModel.getDeviceListLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + lifecycleScope.launch { + val isBindDevice = MMKVUtil.getInt(MMKVKey.isBindDevice) + val isBindPet = MMKVUtil.getInt(MMKVKey.isBindPet) + if (isBindDevice == ConstantInt.isBind && isBindPet == ConstantInt.NoBind) { + if (list.size > 0) { + MMKVUtil.putString(MMKVKey.FirstDeviceId, list[0].deviceId) + MMKVUtil.putString( + MMKVKey.FirstDeviceOutId, list[0].deviceOutId + ) + } + } + withContext(Dispatchers.IO) { + //保存本地数据库 + for (device in list) { + if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) { + MyDatabase.deviceDao().insert(device) + } + } + } + + showToast(R.string.txt_login_successful, gravity = Gravity.CENTER) + + loginSuccessGo() + } + } + } + }, isShowNoDataTip = false) + } + } + + private fun loginSuccessGo() { + val hasPermissions = Util.checkNeedPermissionsEnabled(mContext) + mViewBinding.btnLoginV2Login.postDelayed({ + if (hasPermissions) { + startActivityFinish(Util.checkBindDeviceAndPetGoActivity(mContext)) + } else { + startActivityFinish(Intent(mContext, FirstPermissionsActivity::class.java)) + } + }, 1000) + } + + private fun loginClick(isMd5: Boolean = true) { + mViewBinding.apply { + var email = ilLoginV2EmailAddress.etInputContent.text.toString() + var password = ilLoginV2Password.etInputContent.text.toString() + var isSure = true + + when (email) { + "98" -> { + email = "983577727@qq.com" + password = "123456" + } + + "2" -> { + email = "abbidot02@gmail.com" + password = "123456" + } + + "123" -> { + email = "123456@qq.com" + password = "123456" + } + } + + if (TextUtils.isEmpty(email)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilLoginV2EmailAddress.rlEtInputLayout, tvEmailErrorTipLogin + ) + } + if (TextUtils.isEmpty(password)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilLoginV2Password.rlEtInputLayout, tvPasswordErrorTipLogin + ) + } + if (!isSure) return + + if (isMd5) { + setButtonEnabled(btnLoginV2Login, ConstantInt.Type0) + password = Utils.get32Md5Value(password) + } + + mLoginViewModel.login(this@LoginV2Activity, email, password) + } + } + + private fun goForgotPassword() { + Intent(mContext, ForgotPasswordV2Activity::class.java).apply { + val email = mViewBinding.ilLoginV2EmailAddress.etInputContent.text.toString() + putExtra(MMKVKey.Email, email) + startActivity(this) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + tvLoginV2Signup -> startActivity( + Intent( + mContext, CreateAccountV2Activity::class.java + ) + ) + + tvLoginV2ForgotPassword -> goForgotPassword() + + ilLoginV2Password.ivInputRepresentImage -> { + isShowPassword = !isShowPassword + ViewUtil.instance.showAndHidePasswordClick( + ilLoginV2Password.etInputContent, + ilLoginV2Password.ivInputRepresentImage, + isShowPassword + ) + } + + btnLoginV2Login -> loginClick() + ilLoginV2EmailAddress.etInputContent -> { + LogUtil.e("ilLoginV2EmailAddress") + v.requestFocus() + showInputMethod(v) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/NotificationV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/NotificationV2Activity.kt new file mode 100644 index 0000000..d5c3267 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/NotificationV2Activity.kt @@ -0,0 +1,129 @@ +package com.abbidot.tracker.ui.activity.account + +import android.view.View +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.NotificationV2Adapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityNotificationV2Binding +import com.abbidot.tracker.deprecated.ui.activity.vm.NotificationsViewModel +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + * 消息列表 + */ +class NotificationV2Activity : + BaseActivity(ActivityNotificationV2Binding::inflate) { + + private val mNotificationsViewModel: NotificationsViewModel by viewModels() + + private lateinit var mMessageAdapter: NotificationV2Adapter + + override fun getTopBar() = mViewBinding.notificationV2DetailBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_notifications) + + mMessageAdapter = NotificationV2Adapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val message = getItem(pos) + message.isSelect = !message.isSelect + if (!message.isRead) { + message.isRead = true + lifecycleScope.launch(Dispatchers.IO) { + MyDatabase.messageDao().insert(message) + XEventBus.post(EventName.RefreshMessage) + } + } + notifyItemChanged(pos) + } + }) + } + ViewUtil.instance.setRecyclerViewDecorationLinearLayoutManager( + mContext, mViewBinding.rvNotificationV2Detail, mMessageAdapter + ) + setOnClickListenerViews(mViewBinding.btnNotificationV2ReadAll) + + mNotificationsViewModel.getMessageList(this) + } + + override fun liveDataObserve() { + mNotificationsViewModel.mNotificationsLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + lifecycleScope.launch(Dispatchers.IO) { + Util.dealMessage(this@apply) + setMessageData() + } + } + } + + override fun onRequestError(exceptionCode: String?) { + setMessageData() + } + }, isShowRequestErrorTip = false) + } + } + + private fun setMessageData() { + lifecycleScope.launch(Dispatchers.IO) { + MyDatabase.messageDao().findAll(MyDatabase.MessageTableName)?.apply { + LogUtil.e("消息列表个数:${size}") + //按最新时间排序 + val sortedByList = this.sortedByDescending { it.sendTime } + runOnUiThread { + mMessageAdapter.setData(sortedByList.toMutableList(), true) + } + for (i in sortedByList){ + LogUtil.e(i.messageId+":"+i.message) + } + } + } + } + + private fun readAllMessage() { + lifecycleScope.launch(Dispatchers.IO) { + MyDatabase.messageDao().findAll(MyDatabase.MessageTableName)?.apply { + var isUpdate = false + for (message in this) { + if (!message.isRead) { + message.isRead = true + isUpdate = true + MyDatabase.messageDao().insert(message) + } + } + runOnUiThread { + val list = mMessageAdapter.getData() + for (m in list) { + if (!m.isRead) { + m.isRead = true + } + } + mMessageAdapter.notifyItemRangeChanged(0, list.size) + } + if (isUpdate) XEventBus.post(EventName.RefreshMessage) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnNotificationV2ReadAll -> readAllMessage() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/ResetPasswordSuccessActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ResetPasswordSuccessActivity.kt new file mode 100644 index 0000000..754fc7f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/ResetPasswordSuccessActivity.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.ui.activity.account + +import android.view.View +import coil.load +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityResetPasswordSuccessBinding + +class ResetPasswordSuccessActivity : + BaseActivity(ActivityResetPasswordSuccessBinding::inflate) { + + override fun getTopBar() = null + + override fun initData() { + super.initData() + mViewBinding.apply { + ivPwdResetSuccessfulDone.load(R.drawable.pwd_reset_successful_done) + setOnClickListenerViews(btnPwdResetSuccessfulOk) + } + } + + override fun onClick(v: View?) { + finish() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/SignupSuccessActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/SignupSuccessActivity.kt new file mode 100644 index 0000000..0fb791a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/SignupSuccessActivity.kt @@ -0,0 +1,55 @@ +package com.abbidot.tracker.ui.activity.account + +import android.content.Intent +import android.view.View +import coil.load +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivitySignupSuccessBinding +import com.abbidot.tracker.ui.activity.set.FirstPermissionsActivity +import com.abbidot.tracker.util.Util + +class SignupSuccessActivity : + BaseActivity(ActivitySignupSuccessBinding::inflate) { + + override fun getTopBar() = null + + override fun initData() { + super.initData() + + mViewBinding.apply { + ivSignupSuccessDone.load(R.drawable.pwd_reset_successful_done) + + setOnClickListenerViews(btnSignupSuccessOk) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSignupSuccessOk -> { + val hasPermissions = Util.checkNeedPermissionsEnabled(mContext) + if (hasPermissions) { +// val isExistNewInvite = MMKVUtil.getInt(MMKVKey.isExistNewInvite) +// if (isExistNewInvite == ConstantInt.NoShare) { +// intent.setClass(mContext, AddNewTracker1Activity::class.java) +// } else { +// intent.setClass(mContext, ShowInviteDeviceActivity::class.java) +// } + startActivityFinish( + Util.checkBindDeviceAndPetGoActivity( + mContext + ) + ) + } else { + val intent = Intent() + intent.setClass( + mContext, FirstPermissionsActivity::class.java + ) + startActivityFinish(intent) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/account/UserProfileActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/account/UserProfileActivity.kt new file mode 100644 index 0000000..649b7d9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/account/UserProfileActivity.kt @@ -0,0 +1,306 @@ +package com.abbidot.tracker.ui.activity.account + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import coil.load +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityUserProfileBinding +import com.abbidot.tracker.ui.common.CountryCommon +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.TakePhotoAndCompressViewModel +import com.abbidot.tracker.vm.UserProfileViewModel +import com.huantansheng.easyphotos.EasyPhotos +import com.huantansheng.easyphotos.models.album.entity.Photo + +class UserProfileActivity : + BaseActivity(ActivityUserProfileBinding::inflate) { + + private val mUserViewModel: UserProfileViewModel by viewModels() + private val mTakePhotoAndCompressViewModel: TakePhotoAndCompressViewModel by viewModels() + + private lateinit var mCountryCommon: CountryCommon + + //上传成功头像 + private var mUploadImageUrl = "" + private var mTempHeadPath = "" + + override fun getTopBar() = mViewBinding.ilUserProfileTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pet_profile) +// mRightImageButton = addRightImageButton(R.drawable.icon_gray_delete_svg) + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + + ilUserProfileNameLayout.let { + it.tvInputNameTitle.text = getString(R.string.register_txt_name) + it.rlEtInputLayout.setBackgroundResource(R.drawable.shape12_gray_border_white_bg) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.ivInputRepresentImage.setImageResource(R.drawable.ico_list_next) +// ViewUtil.instance.setInputViewErrorHideClick( +// mContext, it.rlEtInputLayout, tvUserProfileNameErrorTip, it.etInputContent +// ) + } + ilUserProfileCountry.let { + it.tvInputNameTitle.text = getString(R.string.txt_country) + it.rlEtInputLayout.setBackgroundResource(R.drawable.shape12_gray_border_white_bg) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.ivInputRepresentImage.setImageResource(R.drawable.ico_list_next) +// ViewUtil.instance.setInputViewErrorHideClick( +// mContext, +// it.rlEtInputLayout, +// tvUserProfileSelectCountryErrorTip, +// it.etInputContent +// ) + } + ilUserProfileEmailLayout.let { + it.tvInputNameTitle.text = getString(R.string.txt_email_address) + it.rlEtInputLayout.setBackgroundResource(R.drawable.shape12_gray_border_white_bg) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.ivInputRepresentImage.setImageResource(R.drawable.ico_list_next) +// it.etInputContent.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS +// ViewUtil.instance.setInputViewErrorHideClick( +// mContext, it.rlEtInputLayout, tvUserProfileEmailErrorTip, it.etInputContent +// ) + } + + mCountryCommon = + CountryCommon(mContext, object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + ilUserProfileCountry.etInputContent.setText(mCountryCommon.getCountry()) + userProfileSave() + } + }) + + setOnClickListenerViews( + btnUserProfileSave, + ilUserProfileHeadLayout.ilPetProfileHead.appHeadImage, + ilUserProfileNameLayout.rlEtInputLayout, + ilUserProfileNameLayout.ivInputRepresentImage, + ilUserProfileCountry.rlEtInputLayout, + ilUserProfileCountry.ivInputRepresentImage, + ilUserProfileEmailLayout.rlEtInputLayout, + ilUserProfileEmailLayout.ivInputRepresentImage + ) + + mUploadImageUrl = MMKVUtil.getString(MMKVKey.HeadUrl) + ViewUtil.instance.imageLoadUrl( + ilUserProfileHeadLayout.ilPetProfileHead.appHeadImage, + mUploadImageUrl, + R.drawable.pic_avatar_df + ) + } + + mTakePhotoAndCompressViewModel.registerCropImageActivityResult(this) + + mUserViewModel.getCountryCodes(this) + } + + override fun onResume() { + super.onResume() + mViewBinding.apply { + val username = MMKVUtil.getString(MMKVKey.UserName) + val email = MMKVUtil.getString(MMKVKey.Email) + ilUserProfileNameLayout.etInputContent.setText(username) + ilUserProfileEmailLayout.etInputContent.setText(email) + } + } + + override fun liveDataObserve() { + mUserViewModel.apply { + //获取国家代码 + mCountryCodeLiveData.observe(this@UserProfileActivity) { + getUserInfo(this@UserProfileActivity) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mCountryCommon.setCountryCodeData(this) + val countryCode = MMKVUtil.getString(MMKVKey.CountryCode) + val country = mCountryCommon.codeToCountry(countryCode) + mViewBinding.ilUserProfileCountry.etInputContent.setText(country) + } + } + }) + } + //获取用户信息 + mUserInfoLiveData.observe(this@UserProfileActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { info -> + mViewBinding.ilUserProfileNameLayout.etInputContent.setText(info.userName) + mViewBinding.ilUserProfileEmailLayout.etInputContent.setText(info.email) + mUploadImageUrl = info.imgurl + ViewUtil.instance.imageLoadUrl( + mViewBinding.ilUserProfileHeadLayout.ilPetProfileHead.appHeadImage, + mUploadImageUrl, + R.drawable.pic_avatar_df + ) + } + } + }) + } + + //上传头像 + mUploadImageLiveData.observe(this@UserProfileActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() +// showToast(R.string.txt_upload_successful) + mUploadImageUrl = data!! + mViewBinding.ilUserProfileHeadLayout.ilPetProfileHead.appHeadImage.load(mUploadImageUrl) + userProfileSave() + } + }) + } + + mUpdateUserInfoLiveData.observe(this@UserProfileActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.UpdateUserInfo) + showToast(R.string.txt_change_successful) + } + }) + } + +// mUserLogoffLiveData.observe(this@UserProfileActivity) { +// dealRequestResult(it, object : GetResultCallback { +// override fun onResult(any: Any) { +// Util.logOut(this@UserProfileActivity, lifecycleScope) +// } +// }) +// } + } + mTakePhotoAndCompressViewModel.mPhotoPathLiveData.observe(this) { + if (it.size > 0) { + mTempHeadPath = it[0].path +// mViewBinding.ilUserProfileHeadLayout.ilPetProfileHead.appHeadImage.load(it[0]) + mUserViewModel.uploadImage(this@UserProfileActivity, mTempHeadPath) + } + } +// XEventBus.observe(this, EventName.UpdateUserInfo) { +// val email = MMKVUtil.getString(MMKVKey.Email) +// mViewBinding.ilUserProfileEmailLayout.etInputContent.setText(email) +// } + } + +// /** +// * 注销账户 +// */ +// private fun closeAccount() { +// ViewUtil.instance.showDialog( +// mContext, R.string.txt_close_account_tips, object : BaseDialog.OnOkClickListener { +// override fun onOkClick(dialog: BaseDialog<*>) { +// dialog.dismiss() +// mUserViewModel.logoff(this@UserProfileActivity) +// } +// }, okTextResId = R.string.txt_sure +// ) +// } + + private fun userProfileSave() { + mViewBinding.apply { + val name = ilUserProfileNameLayout.etInputContent.text.toString() + val country = ilUserProfileCountry.etInputContent.text.toString() + val email = ilUserProfileEmailLayout.etInputContent.text.toString() + var isSure = true + if (TextUtils.isEmpty(name)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilUserProfileNameLayout.rlEtInputLayout, tvUserProfileNameErrorTip + ) + } + if (TextUtils.isEmpty(country)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, + ilUserProfileCountry.rlEtInputLayout, + tvUserProfileSelectCountryErrorTip + ) + } + if (TextUtils.isEmpty(email) || !Utils.isValidEmail(email)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext, ilUserProfileEmailLayout.rlEtInputLayout, tvUserProfileEmailErrorTip + ) + } + if (!isSure) return + + mUserViewModel.updateUserInfo( + this@UserProfileActivity, + "", + mUploadImageUrl, + mCountryCommon.getCountryCode(), + name + ) + } + } + + private fun dealPhoto(data: Intent?) { + if (null != data) { + //返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + val resultPhotos: ArrayList? = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS) + //返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取 +// data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false) + mTakePhotoAndCompressViewModel.goCropAndCompressImage(resultPhotos) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (RESULT_OK == resultCode) { + //相机或相册回调 + if (requestCode == ResultCode.ResultCode_1) { + dealPhoto(data) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilUserProfileCountry.rlEtInputLayout, ilUserProfileCountry.ivInputRepresentImage -> mCountryCommon.showCountryCodeDialog() + ilUserProfileEmailLayout.rlEtInputLayout, ilUserProfileEmailLayout.ivInputRepresentImage -> Intent( + mContext, EmailAddressActivity::class.java + ).let { + startActivity(it) + } + + ilUserProfileNameLayout.rlEtInputLayout, ilUserProfileNameLayout.ivInputRepresentImage -> Intent( + mContext, ChangeNameActivity::class.java + ).let { + startActivity(it) + } + + btnUserProfileSave -> { + if (TextUtils.isEmpty(mTempHeadPath)) { + userProfileSave() + } else { + mUserViewModel.uploadImage(this@UserProfileActivity, mTempHeadPath) + } + } + + ilUserProfileHeadLayout.ilPetProfileHead.appHeadImage -> ViewUtil.instance.goSelectPhoto( + this@UserProfileActivity, ResultCode.ResultCode_1 + ) + +// mRightImageButton -> closeAccount() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/data/DataBoardActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/data/DataBoardActivity.kt new file mode 100644 index 0000000..40f025d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/data/DataBoardActivity.kt @@ -0,0 +1,182 @@ +package com.abbidot.tracker.ui.activity.data + +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.ActiveTimeBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityDataBoardBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.DataDetailViewModel + +class DataBoardActivity : + BaseActivity(ActivityDataBoardBinding::inflate) { + + private val mDataDetailViewModel: DataDetailViewModel by viewModels() + + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.dataBoardTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_data_board) + + intent?.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } +// if (null == mPetBean) finish() + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + ilDataBoardTotalDistance.tvDataBoardUnitItem.text = + Util.getMetricUnits(mContext, ConstantInt.Type4) + ilDataBoardAvgActivityTime.let { + it.tvDataBoardTitleItem.text = getString(R.string.txt_avg_activity_time) + it.tvDataBoardUnitItem.text = getString(R.string.txt_min) + } + ilDataBoardAvgCalories.let { + it.tvDataBoardTitleItem.text = getString(R.string.txt_avg_calories) + it.tvDataBoardUnitItem.text = getString(R.string.txt_calorie_unit) + } + ilDataBoardAvgSleepTime.let { + it.tvDataBoardTitleItem.text = getString(R.string.txt_avg_sleep_time) + it.tvDataBoardUnitItem.text = getString(R.string.txt_min) + it.tvDataBoardHrValueItem.visibility = View.VISIBLE + it.tvDataBoardHrUnitItem.visibility = View.VISIBLE + } + setOnClickListenerViews(btnDataBoardSave) +// rsDataBoardSetGoalView.setValue(0) + } + + mPetBean?.apply { + ViewUtil.instance.setPetTypeHead( + mViewBinding.ivDataBoardPetHead.appHeadImage, imgurl, petType + ) + mDataDetailViewModel.getPetActivityDetailInfo(this@DataBoardActivity, petId, deviceId) + } + } + + override fun liveDataObserve() { + mDataDetailViewModel.apply { + mPetActivityInfoLiveData.observe(this@DataBoardActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { activeTimeBean -> + setActivityInfoData(activeTimeBean) + } + } + + override fun onRequestError(exceptionCode: String?) { + super.onRequestError(exceptionCode) + mViewBinding.rsDataBoardSetGoalView.setValue(0) + } + }) + } + + mSetGoalLiveData.observe(this@DataBoardActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { + XEventBus.post(EventName.SetGoal) + MMKVUtil.putInt( + MMKVKey.ActivityGoal, mViewBinding.rsDataBoardSetGoalView.getValue() + ) + showToast(R.string.txt_save_successful, isFinish = true) + } + } + }) + } + } + } + + private fun setActivityInfoData(activeTimeBean: ActiveTimeBean) { + mViewBinding.apply { + activeTimeBean.let { + ViewUtil.instance.viewNumberValueAnimator( + ilDataBoardTotalDistance.tvDataBoardValueItem, + 0, + Util.metricConvertUnits(it.distance.toFloat(), ConstantInt.Type4, 2), + animatorDuration = 1500L + ) + ViewUtil.instance.viewNumberValueAnimator( + ilDataBoardAvgActivityTime.tvDataBoardValueItem, + 0, + it.avgActiveTime / 60, + animatorDuration = 1500L + ) + ViewUtil.instance.viewNumberValueAnimator( + ilDataBoardAvgCalories.tvDataBoardValueItem, + 0, + if (it.avgCalories == 0) 0 else 9 / it.avgCalories, + animatorDuration = 1500L + ) + val avgSleepTimeHr = it.avgSleepTime / 3600 + val avgSleepTimeMin = (it.avgSleepTime % 3600) / 60 + ViewUtil.instance.viewNumberValueAnimator( + ilDataBoardAvgSleepTime.tvDataBoardHrValueItem, + 0, + avgSleepTimeHr, + animatorDuration = 1500L + ) + ViewUtil.instance.viewNumberValueAnimator( + ilDataBoardAvgSleepTime.tvDataBoardValueItem, + 0, + avgSleepTimeMin, + animatorDuration = 1500L + ) + rsDataBoardSetGoalView.setValue(it.goal) + } + } + } + + private fun savePetGoal() { + mPetBean?.apply { + mDataDetailViewModel.setGoal( + this@DataBoardActivity, deviceId, mViewBinding.rsDataBoardSetGoalView.getValue() + ) + } + } + + override fun leftBackOnClick() { + mViewBinding.apply { + val goal = MMKVUtil.getInt(MMKVKey.ActivityGoal, ConstantInt.DefaultSportGoal) + if (rsDataBoardSetGoalView.getValue() == goal) super.leftBackOnClick() + else ViewUtil.instance.showDialog( + mContext, + R.string.txt_weather_save, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + savePetGoal() + } + }, + okTextResId = R.string.txt_sure, + cancelListener = object : BaseDialog.OnDialogCancelListener { + override fun onCancelClick(dialog: BaseDialog<*>) { + dialog.dismiss() + finish() + } + }) + } + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnDataBoardSave -> savePetGoal() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/data/MoreActivityActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/data/MoreActivityActivity.kt new file mode 100644 index 0000000..889f3a5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/data/MoreActivityActivity.kt @@ -0,0 +1,350 @@ +package com.abbidot.tracker.ui.activity.data + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.ActiveTimeBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityMoreActivityBinding +import com.abbidot.tracker.dialog.ShowCalenderDialog +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.DataDetailViewModel +import com.haibin.calendarview.Calendar + +class MoreActivityActivity : + BaseActivity(ActivityMoreActivityBinding::inflate) { + + private val mDataDetailViewModel: DataDetailViewModel by viewModels() + + private var mCalenderDialog: ShowCalenderDialog? = null + private lateinit var mMonthsAbbr: Array + + private var mPetBean: PetBean? = null + + private var mToday = "" + private var mActiveTimeBean: ActiveTimeBean? = null + + override fun getTopBar() = mViewBinding.moreActivityTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_activity) + mRightImageButton = addRightImageButton(R.drawable.selector_share_icon_pressed1) + + mMonthsAbbr = resources.getStringArray(R.array.array_month_abbr) + + intent.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mViewBinding.apply { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) { + btnMoreActivityEditGoal.visibility = View.GONE + } + + mPetBean?.let { + ViewUtil.instance.setPetTypeHead( + ilMoreActivityPetHead.appHeadImage, it.imgurl, it.petType + ) + tvMoreActivityPetName.text = it.petName + } + tvActivitySelectCalendarShow.text = + Utils.formatTime(System.currentTimeMillis(), Utils.DATE_FORMAT_PATTERN_EN4) + + setOnClickListenerViews(tvActivitySelectCalendarShow, btnMoreActivityEditGoal) + + hcvActivityDataHorizontalCalenderView.setOnDaySelectedListener { hasChanged, year, month, day, week, items -> + val curDate = "$year-${Utils.fill2Digits(month)}-${Utils.fill2Digits(day)}" + val curTimestamp = Utils.stringToTimestamp(curDate, Utils.DATE_FORMAT_PATTERN_CN) + val enDate = Utils.stringToDate( + curDate, Utils.DATE_FORMAT_PATTERN_CN, Utils.DATE_FORMAT_PATTERN_EN4 + ) +// mCalenderDialog?.setSelectDate(curTimestamp) + tvActivitySelectCalendarShow.text = enDate + getActiveTimeByDay(curDate) + } + + ilMoreActiveTime.apply { + ivSportDataType.setImageResource(R.drawable.icon_active_time) + tvSportDataTitle.text = getString(R.string.txt_time) + tvSportDataUnit.text = getString(R.string.txt_min) + } + ilMoreActiveCalories.apply { + ivSportDataType.setImageResource(R.drawable.icon_calories) + tvSportDataTitle.text = getString(R.string.data_active_calories) + tvSportDataUnit.text = getString(R.string.txt_calorie_unit) + } + ilMoreActiveCaloriesAvg.apply { + ivSportDataType.setImageResource(R.drawable.icon_calories) + tvSportDataTitle.text = getString(R.string.txt_7days_avg) + tvSportDataUnit.text = getString(R.string.txt_calorie_unit) + } + + ilMoreActiveLast30TimeAvg.apply { + ivSportDataType.setImageResource(R.drawable.icon_active_time) + tvSportDataTitle.text = getString(R.string.txt_time_avg) + tvSportDataUnit.text = getString(R.string.txt_min) + } + ilMoreActiveLast30CaloriesAvg.apply { + ivSportDataType.setImageResource(R.drawable.icon_calories) + tvSportDataTitle.text = getString(R.string.txt_calories_avg) + tvSportDataUnit.text = getString(R.string.txt_calorie_unit) + } + } + + initSportData() + + mToday = Utils.getCurrentTime(Utils.DATE_FORMAT_PATTERN_CN) + getActiveTimeByDay(mToday) + } + + /** + * 默认的数据 + */ + private fun initSportData() { + val goal = MMKVUtil.getInt(MMKVKey.ActivityGoal) + mViewBinding.apply { + moreActivityTimeProgress.progress = 0 + tvMoreActivityGoalPercent.text = + String.format(getString(R.string.txt_goal_percent), "0") + tvMoreActivityGoal.text = + String.format(getString(R.string.txt_min_unit_lower), "0/$goal") + ilMoreActiveTime.tvSportDataContent.text = "0" + ilMoreActiveCalories.tvSportDataContent.text = "0" + ilMoreActiveCaloriesAvg.tvSportDataContent.text = "0" + val distanceUnit = Util.getMetricUnits(mContext, ConstantInt.Type4) + ilMoreActiveDistance.let { + it.tvSportDataContent.text = "0" + it.tvSportDataUnit.text = distanceUnit + } + tvActivityTotalDistanceUnit.text = distanceUnit + ilMoreActiveLast30TimeAvg.tvSportDataContent.text = "0" + ilMoreActiveLast30CaloriesAvg.tvSportDataContent.text = "0" + tvActivityTotalTimeHour.visibility = View.INVISIBLE + tvActivityTotalTimeHourTitle.visibility = View.INVISIBLE + tvActivityTotalTimeMin.text = "0" + tvActivityTotalDistance.text = "0" + + moreActiveTimeView.setData(mutableListOf()) + last7dayActiveChartView.setData(mutableListOf()) + last30dayActiveLineChartView.setData(mutableListOf()) + } + } + + override fun liveDataObserve() { + mDataDetailViewModel.mActiveDetailLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + setActiveData(this) + } + } + }) + } + + XEventBus.observe(this, EventName.SetGoal) { + mActiveTimeBean?.apply { + val goal = MMKVUtil.getInt(MMKVKey.ActivityGoal) + setGoal(goal) + } + } + } + + private fun setGoal(goalTime: Int) { + mViewBinding.apply { + mActiveTimeBean?.let { + val goal = if (goalTime < ConstantInt.MinSportGoal) ConstantInt.DefaultSportGoal + else goalTime + val activeMin = it.activeTime / 60 + tvMoreActivityGoal.text = + String.format(getString(R.string.txt_min_unit_lower), "$activeMin/$goal") + var goalPercent = activeMin / goal.toFloat() + if (goalPercent > 1) { + goalPercent = 1f + } + goalPercent *= 100 + ViewUtil.instance.viewNumberValueAnimator( + tvMoreActivityGoalPercent, + 0, + goalPercent.toInt(), + getString(R.string.txt_goal_percent) + ) + ViewUtil.instance.viewNumberValueAnimator( + moreActivityTimeProgress, 0, goalPercent.toInt() + ) + } + } + } + + private fun setActiveData(activeTimeBean: ActiveTimeBean) { + mActiveTimeBean = activeTimeBean + mViewBinding.apply { + activeTimeBean.let { + setGoal(it.goalTime) + ViewUtil.instance.viewNumberValueAnimator( + ilMoreActiveTime.tvSportDataContent, 0, it.activeTime / 60 + ) + ViewUtil.instance.viewNumberValueAnimator( + ilMoreActiveCalories.tvSportDataContent, 0, it.calories + ) + ViewUtil.instance.viewNumberValueAnimator( + ilMoreActiveDistance.tvSportDataContent, + 0, + Util.metricConvertUnits(it.distance.toFloat(), ConstantInt.Type4, 2) + ) + ViewUtil.instance.viewNumberValueAnimator( + ilMoreActiveCaloriesAvg.tvSportDataContent, 0, it.calories / 7 + ) + ViewUtil.instance.viewNumberValueAnimator( + tvActivityTotalDistance, 0, Util.metricConvertUnits( + it.totalDistance.toFloat(), ConstantInt.Type4, 2 + ) + ) + val h = it.totalTimes / 3600 + if (h == 0) { + tvActivityTotalTimeHour.visibility = View.INVISIBLE + tvActivityTotalTimeHourTitle.visibility = View.INVISIBLE + } else { + tvActivityTotalTimeHour.visibility = View.VISIBLE + tvActivityTotalTimeHourTitle.visibility = View.VISIBLE + ViewUtil.instance.viewNumberValueAnimator(tvActivityTotalTimeHour, 0, h) + } + ViewUtil.instance.viewNumberValueAnimator( + tvActivityTotalTimeMin, 0, (it.totalTimes % 3600) / 60 + ) + + if (!TextUtils.isEmpty(it.activeValue)) { + val activeTimeList = mutableListOf() + val activeList = it.activeValue.split(",") + for (i in activeList.indices step 2) { + val time = activeList[i].toInt() + activeList[i + 1].toInt() + activeTimeList.add(time / 60) + } + moreActiveTimeView.setData(activeTimeList) + } + + it.sevenDatas?.let { s -> + if (s.size > 0) { + val yAxis = mutableListOf() + val data = mutableListOf() + for (a in s) { + val week = Utils.stringToDate( + a.dayTime, Utils.DATE_FORMAT_PATTERN_CN, Utils.WEEK_FORMAT_PATTERN + ) + if (a.dayTime == mToday) yAxis.add(getString(R.string.txt_today)) + else yAxis.add(week) + data.add(a.activeTime / 60) + } + last7dayActiveChartView.setData(data, yAxis) + } + } + it.thirtyDatas?.let { t -> + if (t.size > 0) { + val subList = if (t.size > 30) t.subList(0, 30) else t + val xAxis = mutableListOf() + val data = mutableListOf() + var step = 0 + var stepValue = 0 + var timeSum = 0 + var kcalSum = 0.0 + for (i in 0 until subList.size) { + val a = subList[i] + if (i == stepValue) { + val date = Utils.stringToDate( + a.dayTime, + Utils.DATE_FORMAT_PATTERN_CN, + Utils.DATE_FORMAT_PATTERN_EN5 + ) + if (a.dayTime == mToday) xAxis.add(getString(R.string.txt_today)) + else xAxis.add(date) + step++ + stepValue = 6 * step - 1 + } + val min = a.activeTime / 60 + timeSum += min + kcalSum += a.calories + data.add(min) + + LogUtil.e(a.dayTime + ",${t.size}") + } + +// val min = it.activeTime / 60 +// timeSum += min +// kcalSum += it.calories + ViewUtil.instance.viewNumberValueAnimator( + ilMoreActiveLast30TimeAvg.tvSportDataContent, 0, timeSum / data.size + ) + ViewUtil.instance.viewNumberValueAnimator( + ilMoreActiveLast30CaloriesAvg.tvSportDataContent, 0, kcalSum / data.size + ) + //反向输出 + xAxis.reverse() + data.reverse() + last30dayActiveLineChartView.setData(data, xAxis) + } + } + } + } + } + + private fun getActiveTimeByDay(dayTime: String) { + initSportData() + mPetBean?.apply { + mDataDetailViewModel.getActiveTimeByDay( + this@MoreActivityActivity, deviceId, dayTime, petId + ) + } + } + + private fun showCalenderDialog() { + val cTimestamp = Utils.stringToTimestamp( + mViewBinding.tvActivitySelectCalendarShow.text.toString(), Utils.DATE_FORMAT_PATTERN_EN4 + ) + val calenderDialog = + ShowCalenderDialog(mContext, object : BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is Calendar) { + mViewBinding.hcvActivityDataHorizontalCalenderView.setSelectedDate( + any.year, any.month, any.day + ) + } + } + }, calenderShowTimestamp = cTimestamp, minLastDayRange = 364) + calenderDialog.show() + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + tvActivitySelectCalendarShow -> showCalenderDialog() + btnMoreActivityEditGoal -> mPetBean?.let { pet -> + Intent(mContext, DataBoardActivity::class.java).let { + it.putExtra(ConstantString.Pet, pet) + startActivity(it) + } + } + + mRightImageButton -> Intent(mContext, SharePetActivityActivity::class.java).let { + it.putExtra(ConstantString.Pet, mPetBean) + it.putExtra(ConstantString.HistoryDataBean, mActiveTimeBean) + startActivity(it) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/data/MoreSleepActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/data/MoreSleepActivity.kt new file mode 100644 index 0000000..251ba99 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/data/MoreSleepActivity.kt @@ -0,0 +1,215 @@ +package com.abbidot.tracker.ui.activity.data + +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.SleepTimeBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityMoreSleepBinding +import com.abbidot.tracker.dialog.ShowCalenderDialog +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.DataDetailViewModel +import com.haibin.calendarview.Calendar + +class MoreSleepActivity : + BaseActivity(ActivityMoreSleepBinding::inflate) { + + private val mDataDetailViewModel: DataDetailViewModel by viewModels() + + private var mCalenderDialog: ShowCalenderDialog? = null + + private var mPetBean: PetBean? = null + + private var mToday = "" + + override fun getTopBar() = mViewBinding.moreSleepTopBar.titleTopBar + + override fun initData() { + super.initData() + setStatusBarDark() + setTopBarTitle(R.string.txt_resting) + + intent.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mViewBinding.apply { + root.background = ContextCompat.getDrawable(mContext, R.drawable.icon_more_sleep_bg) + mTopBar!!.titleView?.setTextColor(ContextCompat.getColor(mContext, R.color.white)) + setLeftBackImage(R.drawable.icon_black_back_svg) +// mRightImageButton = addRightImageButton(R.drawable.selector_share_icon_pressed2) + + mPetBean?.let { + ViewUtil.instance.setPetTypeHead( + ilMoreSleepPetHead.appHeadImage, it.imgurl, it.petType + ) + tvMoreSleepPetName.text = it.petName + } + tvSleepSelectCalendarShow.text = + Utils.formatTime(System.currentTimeMillis(), Utils.DATE_FORMAT_PATTERN_EN4) + + setOnClickListenerViews(tvSleepSelectCalendarShow) + + sleepDataHorizontalCalenderView.setOnDaySelectedListener { hasChanged, year, month, day, week, items -> + val curDate = "$year-${Utils.fill2Digits(month)}-${Utils.fill2Digits(day)}" + val curTimestamp = Utils.stringToTimestamp(curDate, Utils.DATE_FORMAT_PATTERN_CN) + val enDate = Utils.stringToDate( + curDate, Utils.DATE_FORMAT_PATTERN_CN, Utils.DATE_FORMAT_PATTERN_EN4 + ) +// mCalenderDialog?.setSelectDate(curTimestamp) + tvSleepSelectCalendarShow.text = enDate + getSleepTimeByDay(curDate) + } + + sleepDataHorizontalCalenderView.setTodaySelectionBgTextColor( + R.drawable.shape36_drak_green_color_bg, R.color.select_color2 + ) + } + + initSleepData() + + mToday = Utils.getCurrentTime(Utils.DATE_FORMAT_PATTERN_CN) + getSleepTimeByDay(mToday) + } + + override fun liveDataObserve() { + mDataDetailViewModel.mSleepDetailLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + setSleepData(this) + } + } + }) + } + } + + /** + * 默认的数据 + */ + private fun initSleepData() { + mViewBinding.apply { + tvMoreSleepTimeSum.text = "0h 0m" + tvTotalSleepTimeHourTitle.visibility = View.INVISIBLE + tvTotalSleepTimeHour.visibility = View.INVISIBLE + tvTotalSleepTimeMin.text = "0" + + moreSleepRingView.setData(0f, 1f) + last7daySleepChartView.setData(mutableListOf()) + last30daySleepLineChartView.setData(mutableListOf()) + } + } + + private fun getSleepTimeByDay(dayTime: String) { + initSleepData() + mPetBean?.apply { + mDataDetailViewModel.getSleepTimeByDay(this@MoreSleepActivity, deviceId, dayTime, petId) + } + } + + private fun setSleepData(sleepTimeBean: SleepTimeBean) { + mViewBinding.apply { + sleepTimeBean.let { + + ViewUtil.instance.setValueAnimator(0, it.sleepTime, 1500) { v -> + val value = v.animatedValue as Int + val h = value / 3600 + val m = (value % 3600) / 60 + val str = "${h}h ${m}m" + tvMoreSleepTimeSum.text = str + } + + val h = it.avgSleepTime / 3600 + val m = (it.avgSleepTime % 3600) / 60 + moreSleepRingView.setData(it.activeTime / 60f, it.sleepTime / 60f) + if (h == 0) { + tvTotalSleepTimeHourTitle.visibility = View.INVISIBLE + tvTotalSleepTimeHour.visibility = View.INVISIBLE + } else { + tvTotalSleepTimeHourTitle.visibility = View.VISIBLE + tvTotalSleepTimeHour.visibility = View.VISIBLE + ViewUtil.instance.viewNumberValueAnimator(tvTotalSleepTimeHour, 0, h) + } + ViewUtil.instance.viewNumberValueAnimator(tvTotalSleepTimeMin, 0, m) + + it.sevenDatas?.let { s -> + if (s.size > 0) { + val yAxis = mutableListOf() + val data = mutableListOf() + for (a in s) { + val week = Utils.stringToDate( + a.dayTime, Utils.DATE_FORMAT_PATTERN_CN, Utils.WEEK_FORMAT_PATTERN + ) + if (a.dayTime == mToday) yAxis.add(getString(R.string.txt_today)) + else yAxis.add(week) + data.add(a.sleepTime / 60) + } + last7daySleepChartView.setData(data, yAxis, true) + } + } + + it.thirtyDatas?.let { t -> + if (t.size > 0) { + val subList = if (t.size > 30) t.subList(0, 30) else t + val xAxis = mutableListOf() + val data = mutableListOf() + var step = 0 + var stepValue = 0 + for (i in 0 until subList.size) { + val a = subList[i] + if (i == stepValue) { + val date = Utils.stringToDate( + a.dayTime, + Utils.DATE_FORMAT_PATTERN_CN, + Utils.DATE_FORMAT_PATTERN_EN5 + ) + if (a.dayTime == mToday) xAxis.add(getString(R.string.txt_today)) + else xAxis.add(date) + step++ + stepValue = 6 * step - 1 + } + val min = a.sleepTime / 60 + data.add(min) + } + //反向输出 + xAxis.reverse() + data.reverse() + last30daySleepLineChartView.setData(data, xAxis) + } + } + } + } + } + + private fun showCalenderDialog() { + val cTimestamp = Utils.stringToTimestamp( + mViewBinding.tvSleepSelectCalendarShow.text.toString(), Utils.DATE_FORMAT_PATTERN_EN4 + ) + val calenderDialog = + ShowCalenderDialog(mContext, object : BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is Calendar) { + mViewBinding.sleepDataHorizontalCalenderView.setSelectedDate( + any.year, any.month, any.day + ) + } + } + }, calenderShowTimestamp = cTimestamp, minLastDayRange = 364) + calenderDialog.show() + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + tvSleepSelectCalendarShow -> showCalenderDialog() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/data/SharePetActivityActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/data/SharePetActivityActivity.kt new file mode 100644 index 0000000..66cb29e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/data/SharePetActivityActivity.kt @@ -0,0 +1,285 @@ +package com.abbidot.tracker.ui.activity.data + +import android.content.Intent +import android.graphics.Bitmap +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import coil.load +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.BuildConfig +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.ActiveTimeBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivitySharePetActivityBinding +import com.abbidot.tracker.util.FileUtil +import com.abbidot.tracker.util.ImageUtil +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.TakePhotoAndCompressViewModel +import com.huantansheng.easyphotos.EasyPhotos +import com.huantansheng.easyphotos.models.album.entity.Photo +import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack +import com.huantansheng.easyphotos.utils.uri.UriUtils +import com.qmuiteam.qmui.widget.dialog.QMUIBottomSheet +import java.io.File +import java.io.IOException + + +class SharePetActivityActivity : + BaseActivity(ActivitySharePetActivityBinding::inflate) { + + private val mTakePhotoAndCompressViewModel: TakePhotoAndCompressViewModel by viewModels() + +// private lateinit var mFacebookShareDialog: ShareDialog +// private lateinit var mFacebookCallbackManager: CallbackManager + + override fun getTopBar() = mViewBinding.ilSharePetActivityTopBar.titleTopBar + + override fun initData() { + super.initData() + setLeftBackImage(R.drawable.icon_white_back_svg) + if (null == mRightTextButton) { + mRightTextButton = addRightTextButton(R.string.txt_share) + } + + mViewBinding.apply { + + intent.extras?.apply { + Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java)?.let { + ViewUtil.instance.setPetTypeHead( + ilSharePetActivityPetHead.appHeadImage, it.imgurl, it.petType + ) + tvSharePetActivityPetName.text = it.petName + } + Util.getParcelableAdaptive( + intent, ConstantString.HistoryDataBean, ActiveTimeBean::class.java + )?.let { + tvSharePetActivityTime.text = Utils.stringToDate( + it.dayTime, Utils.DATE_FORMAT_PATTERN_CN, Utils.DATE_FORMAT_PATTERN_EN7 + ) + ViewUtil.instance.viewNumberValueAnimator( + ilSharePetActivityActivityTime.tvSportDataContent, 0, it.activeTime / 60 + ) + ViewUtil.instance.viewNumberValueAnimator( + ilSharePetActivityActivityCalories.tvSportDataContent, 0, it.calories + ) + } + } + + val colorBg = ContextCompat.getColor(mContext, R.color.transparent) + ilSharePetActivityActivityTime.let { + it.root.setBackgroundColor(colorBg) + it.tvSportDataUnit.setText(R.string.txt_min) + it.tvSportDataTitle.setText(R.string.txt_activity_time) + it.ivSportDataType.setImageResource(R.drawable.icon_active_time) + } + ilSharePetActivityActivityCalories.let { + it.root.setBackgroundColor(colorBg) + it.tvSportDataUnit.setText(R.string.txt_calorie_unit) + it.tvSportDataTitle.setText(R.string.data_active_calories) + it.ivSportDataType.setImageResource(R.drawable.icon_calories) + } + setOnClickListenerViews(mRightTextButton, ivSharePetActivityChangePhotoBtn) + } + +// mFacebookCallbackManager = CallbackManager.Factory.create() +// mFacebookShareDialog = ShareDialog(mActivity) +// mFacebookShareDialog.registerCallback( +// mFacebookCallbackManager, object : FacebookCallback { +// override fun onCancel() { +// LogUtil.e("1111") +// showToast(R.string.txt_cancel) +// } +// +// override fun onError(error: FacebookException) { +// LogUtil.e("2222,${error.message}") +// showToast("onError,${error.message}") +// } +// +// override fun onSuccess(result: Sharer.Result) { +// LogUtil.e("3333,$result") +// showToast(R.string.txt_successful) +// } +// }, ResultCode.ResultCode_2 +// ) + + //先设置文件fileProvider + EasyPhotos.createCamera(this@SharePetActivityActivity, false) + .setFileProviderAuthority("${BuildConfig.APPLICATION_ID}.fileprovider") + + mTakePhotoAndCompressViewModel.registerCropImageActivityResult(this, 3, 4) + } + + override fun liveDataObserve() { + mTakePhotoAndCompressViewModel.mPhotoPathLiveData.observe(this) { + if (it.size > 0) { + mViewBinding.ivSharePetActivityPhoto.load(it[0]) + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (RESULT_OK == resultCode) { + //相机或相册回调 + if (requestCode == ResultCode.ResultCode_1 && null != data) { + //返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + val resultPhotos: ArrayList = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)!! + //返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取 +// data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false) + mTakePhotoAndCompressViewModel.goCropAndCompressImage(resultPhotos) + } else if (requestCode == ResultCode.ResultCode_2) { +// mFacebookCallbackManager.onActivityResult(requestCode, resultCode, data) + } + } + } + + private fun getBitmap(bitmapCallback: (Bitmap) -> Unit) { + mViewBinding.apply { + ivSharePetActivityChangePhotoBtn.visibility = View.GONE + ivSharePetActivityChangePhotoBtn.postDelayed({ + ImageUtil.viewToBitmap(rlSharePetActivityInfoLayout) { bitmap -> + bitmapCallback.invoke(bitmap) + ivSharePetActivityChangePhotoBtn.visibility = View.VISIBLE + } + }, 300) + } + } + + /** + * 保存图片到本地 + */ + private fun saveToPhoto() { + getBitmap { bitmap -> + val dir = FileUtil.getDiskCacheDirectory(mContext, FileUtil.SHARE_DIRECTORY_NAME) + BitmapUtils.saveBitmapToDir(this, + dir.path, + FileUtil.SHARE_DIRECTORY_NAME, + bitmap, + true, + object : SaveBitmapCallBack { + override fun onSuccess(file: File) { + LogUtil.e("onSuccess,${file.path}") + showToast(R.string.txt_save_successful) + EasyPhotos.recycle(bitmap) + } + + override fun onIOFailed(exception: IOException) { + LogUtil.e("onIOFailed,${exception.message}") + showToast(R.string.txt_fail) + EasyPhotos.recycle(bitmap) + } + + override fun onCreateDirFailed() { + LogUtil.e("onCreateDirFailed") + showToast(R.string.txt_fail) + EasyPhotos.recycle(bitmap) + } + }) + } + } + + /** + * Facebook分享 + */ +// private fun shareToFacebook() { +// getBitmap { bitmap -> +// val photo = SharePhoto.Builder().setBitmap(bitmap).build() +// val content = SharePhotoContent.Builder().addPhoto(photo).build() +// mFacebookShareDialog.show(content) +// } +// } + + private fun showShareMenu() { + QMUIBottomSheet.BottomGridSheetBuilder(mContext).apply { + addItem( + R.drawable.ico_save_to_phone, + "保存到本地", + 0, + QMUIBottomSheet.BottomGridSheetBuilder.SECOND_LINE + ).addItem( + R.drawable.ico_save_to_facebook, + "Facebook", + 1, + QMUIBottomSheet.BottomGridSheetBuilder.SECOND_LINE + ).setAddCancelBtn(true).setOnSheetItemClickListener { dialog, itemView -> + dialog.dismiss() + when (itemView.tag as Int) { + 0 -> saveToPhoto() +// 1 -> shareToFacebook() + } + }.build().show() + } + } + + + /** + * 创建分享的隐示意图 + */ + private fun createShareIntent(file: File) { + // Create the new Intent using the 'Send' action. + val shareIntent = Intent(Intent.ACTION_SEND) + // Set the MIME type + shareIntent.type = "image/*" + // Create the URI from the media + val uri = UriUtils.getUri(mContext, file) + // Add the URI to the Intent. + shareIntent.putExtra(Intent.EXTRA_STREAM, uri) + //设置分享牌信为Instagram Story +// share.setPackage("com.instagram.android")//使用 Instagram 的包名 + + // Broadcast the Intent. + startActivity(Intent.createChooser(shareIntent, "Share to")) + } + + + private fun shareImage() { + getBitmap { bitmap -> + val dir = FileUtil.getDiskCacheDirectory(mContext, FileUtil.SHARE_DIRECTORY_NAME) + BitmapUtils.saveBitmapToDir(this, + dir.path, + FileUtil.SHARE_DIRECTORY_NAME, + bitmap, + false, + object : SaveBitmapCallBack { + override fun onSuccess(file: File) { + LogUtil.e("onSuccess,${file.path}") + createShareIntent(file) + EasyPhotos.recycle(bitmap) + } + + override fun onIOFailed(exception: IOException) { + LogUtil.e("onIOFailed,${exception.message}") + showToast(R.string.txt_fail) + EasyPhotos.recycle(bitmap) + } + + override fun onCreateDirFailed() { + LogUtil.e("onCreateDirFailed") + showToast(R.string.txt_fail) + EasyPhotos.recycle(bitmap) + } + }) + } + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ivSharePetActivityChangePhotoBtn -> ViewUtil.instance.goSelectPhoto( + this@SharePetActivityActivity, ResultCode.ResultCode_1 + ) + + mRightTextButton -> shareImage() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker1Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker1Activity.kt new file mode 100644 index 0000000..cd28245 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker1Activity.kt @@ -0,0 +1,115 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import coil.load +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityAddNewTracker1Binding +import com.abbidot.tracker.ui.activity.account.LoginV2Activity +import com.abbidot.tracker.ui.activity.pet.ShowInviteDeviceActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.vm.FamilyViewModel + +class AddNewTracker1Activity : + BaseActivity(ActivityAddNewTracker1Binding::inflate) { + + private val mFamilyViewModel: FamilyViewModel by viewModels() + + private var isFirstBind = false + + override fun getTopBar() = mViewBinding.ilAddNewTracker1TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_add_new_tracker) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + } + + mViewBinding.apply { + ivChargePowerOnGif.load(R.drawable.charge_power_on) + + if (!isFirstBind) { + tvChargePowerOnBackLoginBtn.visibility = View.GONE + + } + setOnClickListenerViews(btnChargePowerOnNext, tvChargePowerOnBackLoginBtn) + } + + //这个页面判断有没有新的邀请,不用管删除的 + mFamilyViewModel.getDeviceInviteInfo(this) + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshDevice) { + finish() + } + + //获取设备邀请信息 + mFamilyViewModel.mInviteInfoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + if (inviteArray.isNotEmpty()) { + Intent(mContext, ShowInviteDeviceActivity::class.java).let { intent -> + intent.putParcelableArrayListExtra( + ConstantString.Type, inviteArray as ArrayList + ) + startActivityFinish(intent) + } + } + } + } + }) + } + } + + private var mTempTime = 0L + override fun leftBackOnClick() { + if (isFirstBind) { + // 2s内再次选择有效 + if (System.currentTimeMillis() - mTempTime > 2000) { + showToast(R.string.txt_exit_return) + mTempTime = System.currentTimeMillis() + } else { + finish() + System.gc() + } + } else { + super.leftBackOnClick() + } + } + + /** + * 检查获取定位蓝牙权限 + */ + private fun checkPermissions() { + Util.checkBluetoothPermissionsEnabled(mContext, { + val intent = Intent(mContext, AddNewTracker2Activity::class.java) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + }) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnChargePowerOnNext -> checkPermissions() + tvChargePowerOnBackLoginBtn -> startActivityFinish( + Intent( + mContext, LoginV2Activity::class.java + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker2Activity.kt new file mode 100644 index 0000000..70ac3fb --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker2Activity.kt @@ -0,0 +1,123 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.view.View +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityAddNewTracker2Binding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager +import com.clj.fastble.callback.BleScanCallback +import com.clj.fastble.data.BleDevice +import com.clj.fastble.scan.BleScanRuleConfig +import com.clj.fastble.utils.HexUtil + +class AddNewTracker2Activity : + BaseActivity(ActivityAddNewTracker2Binding::inflate) { + + private var isFirstBind = false + private lateinit var mScanDeviceList: ArrayList + + override fun getTopBar() = mViewBinding.ilAddNewTracker2TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_add_new_tracker) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + } + + mScanDeviceList = ArrayList() + + mViewBinding.apply { + lavAddNewTracker2Lottie.let { + it.setAnimation("lottie/searching_tracker.json") + it.postDelayed({ + it.playAnimation() + }, 500) + } + } + + Util.checkBluetoothPermissionsEnabled(mContext, { + startScanBle() + }) + } + + private fun startScanBle() { + SRBleUtil.instance.cancelBleScan("AddNewTracker2Activity,已经在扫描蓝牙,取消蓝牙扫描") + + mScanDeviceList.clear() + // 扫描超时时间,可选 + val scanRuleConfig = BleScanRuleConfig.Builder().setScanTimeOut(15000).build() + BleManager.getInstance().initScanRule(scanRuleConfig) + BleManager.getInstance().scan(object : BleScanCallback() { + override fun onScanStarted(success: Boolean) { + LogUtil.e("开始扫描...$success") + } + + override fun onScanning(bleDevice: BleDevice?) { + LogUtil.d("onScanning....$bleDevice") + bleDevice?.apply { + if (null != name && name.contains("Pet_Tracker")) { + val start = 45 + val end = start + 6 + val subArray = scanRecord.sliceArray(start until end) + LogUtil.e("截取后的外显id广播=${HexUtil.formatHexString(subArray, true)}") + val deviceOutId = subArray.decodeToString() + DataBean().let { + it.name = deviceOutId + mScanDeviceList.add(it) + } + } + } + } + + override fun onScanFinished(scanResultList: MutableList?) { + LogUtil.e("automaticConnection--扫描完成...${scanResultList?.size}") + scanFinished() + } + }) + } + + private fun scanFinished() { + if (mScanDeviceList.size > 0) { + val intent = Intent(mContext, AddNewTracker3Activity::class.java) + intent.putParcelableArrayListExtra(ConstantString.DeviceInfo, mScanDeviceList) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivityFinish(intent) + } else { + mViewBinding.lavAddNewTracker2Lottie.visibility = View.GONE + ViewUtil.instance.showDialog(mContext, + R.string.txt_no_search_device, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mViewBinding.lavAddNewTracker2Lottie.visibility = View.VISIBLE + startScanBle() + } + }, + okTextResId = R.string.txt_ok, + cancelTextResId = R.string.txt_cancel, + cancelListener = object : BaseDialog.OnDialogCancelListener { + override fun onCancelClick(dialog: BaseDialog<*>) { + dialog.dismiss() + finish() + } + }) + } + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.lavAddNewTracker2Lottie.cancelAnimation() + SRBleUtil.instance.cancelBleScan() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker3Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker3Activity.kt new file mode 100644 index 0000000..f3e72de --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker3Activity.kt @@ -0,0 +1,166 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.view.View +import android.widget.NumberPicker +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityAddNewTracker3Binding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.TrackerSetViewModel +import com.clj.fastble.BleManager + +class AddNewTracker3Activity : + BaseActivity(ActivityAddNewTracker3Binding::inflate), + NumberPicker.OnValueChangeListener { + + private val mTrackerSetViewModel: TrackerSetViewModel by viewModels() + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private var isFirstBind = false + private var mDeviceOutId = "" + private var mDeviceId = "" + private var mScanDeviceList: ArrayList? = null + + override fun getTopBar() = mViewBinding.ilAddNewTracker3TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_add_new_tracker) + setLeftBackImage(R.drawable.icon_white_back_svg) + mRightImageButton = addRightImageButton(R.drawable.icon_refresh) + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mDeviceOutId = getString(ConstantString.DeviceOutId, "") + mScanDeviceList = Util.getParcelableArrayListAdaptive( + intent, ConstantString.DeviceInfo, DataBean::class.java + ) + } + + if (null == mScanDeviceList) finish() + + mViewBinding.apply { + + mScanDeviceList?.let { + if (it.size == 1) { + mDeviceOutId = it[0].name + tvAddNewTracker3DeviceOutId.visibility = View.VISIBLE + rlAddNewTracker3DeviceLayout.visibility = View.GONE + tvAddNewTracker3DeviceOutId.text = mDeviceOutId + } else if (it.size > 1) { + mDeviceOutId = it[0].name + val deviceOutIds = mutableListOf() + for (d in it) { + deviceOutIds.add(d.name) + } + tvAddNewTracker3DeviceOutId.visibility = View.GONE + rlAddNewTracker3DeviceLayout.visibility = View.VISIBLE + npAddNewTracker3DeviceList.let { np -> + ViewUtil.instance.setNumberPickerAttributes( + mContext, + np, + 0, + it.size - 1, + 0, + 26, + valueChangeListener = this@AddNewTracker3Activity, + removeUpDownLine = true, + loopSelector = false + ) + np.displayedValues = deviceOutIds.toTypedArray() + } + } + } + + setOnClickListenerViews(btnPairTrackerNo, btnPairTrackerYes, mRightImageButton) + } + } + + override fun liveDataObserve() { + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + setButtonEnabled(mViewBinding.btnPairTrackerYes, ConstantInt.Type1) + if (ble.conState == ConState.CONNECTED) { + val intent = Intent(mContext, AddPairedSuccessActivity::class.java) + intent.putExtra(ConstantString.DeviceOutId, mDeviceOutId) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + intent.putExtra(ConstantString.DeviceId, mDeviceId) + intent.putExtra(ConstantString.Mac, ble.mac) + startActivity(intent) + } + } + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + if (conState == ConState.DEVICE_NOT_FOUND) { + setButtonEnabled(mViewBinding.btnPairTrackerYes, ConstantInt.Type1) + showToast(R.string.txt_ble_connect_fail) + } + } + + XEventBus.observe(this, EventName.RefreshDevice) { + finish() + } + + mTrackerSetViewModel.mAddBindDeviceLiveData.observe(this) { + setButtonEnabled(mViewBinding.btnPairTrackerYes, ConstantInt.Type1) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mDeviceId = deviceId + setButtonEnabled(mViewBinding.btnPairTrackerYes, ConstantInt.Type0) + //连接蓝牙,方便发送激活指令 + mConnectionDeviceViewModel.connectDeviceToDeviceOutId( + this@AddNewTracker3Activity, mDeviceOutId + ) + } + } + }) + } + } + + override fun onValueChange(picker: NumberPicker?, oldVal: Int, newVal: Int) { + mScanDeviceList?.let { + mDeviceOutId = it[newVal].name + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPairTrackerNo -> { + val intent = Intent(mContext, AddNewTracker4Activity::class.java) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + + btnPairTrackerYes -> { + BleManager.getInstance().disconnectAllDevice() + setButtonEnabled(mViewBinding.btnPairTrackerYes, ConstantInt.Type0) + mTrackerSetViewModel.bindDevice( + this@AddNewTracker3Activity, mDeviceOutId + ) + } + + mRightImageButton -> { + val intent = Intent(mContext, AddNewTracker2Activity::class.java) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivityFinish(intent) + } + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker4Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker4Activity.kt new file mode 100644 index 0000000..d33e6ad --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddNewTracker4Activity.kt @@ -0,0 +1,94 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityAddNewTracker4Binding +import com.abbidot.tracker.util.ViewUtil +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo + +class AddNewTracker4Activity : + BaseActivity(ActivityAddNewTracker4Binding::inflate) { + + private var isFirstBind = false + + override fun getTopBar() = mViewBinding.ilAddNewTracker4TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_add_new_tracker) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + } + + mViewBinding.apply { + + vetAddNewTracker4EditText.setEditClickListener { + tvAddNewTracker4Error.visibility = View.GONE + vetAddNewTracker4EditText.setBoxBackground( + ContextCompat.getDrawable( + mContext, R.drawable.shape8_gray_border_white_bg + ) + ) + } + setOnClickListenerViews(btnAddNewTrackerAdd) + } + + setListenKeyboardChange() + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshDevice) { + finish() + } + } + + private fun addTrackerError(errorTip: String) { + mViewBinding.apply { + vetAddNewTracker4EditText.let { v -> + YoYo.with(Techniques.Shake).onStart { + v.setBoxBackground( + ContextCompat.getDrawable( + mContext, R.drawable.shape8_red_border_white_bg + ) + ) + if (tvAddNewTracker4Error.visibility == View.GONE) { + ViewUtil.instance.viewShowFadeInAnimation(btnAddNewTrackerAdd, 800) + } + ViewUtil.instance.viewShowFadeInAnimation(tvAddNewTracker4Error, 800) + }.duration(50).playOn(v) + } + } + } + + private fun addNewTracker() { + mViewBinding.apply { + val deviceOutId = vetAddNewTracker4EditText.content + if (TextUtils.isEmpty(deviceOutId) || deviceOutId.length < 6) { + addTrackerError(getString(R.string.txt_add_tracker_tip)) + return + } + val intent = Intent(mContext, AddPairedSuccessActivity::class.java) + intent.putExtra(ConstantString.DeviceOutId, deviceOutId) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnAddNewTrackerAdd -> addNewTracker() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddPairedSuccessActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddPairedSuccessActivity.kt new file mode 100644 index 0000000..ff5a751 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/AddPairedSuccessActivity.kt @@ -0,0 +1,161 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityAddPairedSuccessBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.AddTrackerDeviceViewModel +import com.abbidot.tracker.ui.activity.pet.first.FirstSetPetTypeActivity +import com.abbidot.tracker.ui.activity.subscribe.SubscriptionPlanActivity + +class AddPairedSuccessActivity : + BaseActivity(ActivityAddPairedSuccessBinding::inflate) { + + private val mAddTrackerViewModel: AddTrackerDeviceViewModel by viewModels() + + private lateinit var mSubscriptionsOrderBean: SubscriptionsOrderBean + + private var isFirstBind = false + private var mDeviceOutId = "" + private var mDeviceId = "" + private var mMac = "" + + override fun getTopBar() = mViewBinding.ilAddPairedSuccessTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mDeviceId = getString(ConstantString.DeviceId, "") + mMac = getString(ConstantString.Mac, "") + mDeviceOutId = getString(ConstantString.DeviceOutId, "") + } + + mSubscriptionsOrderBean = SubscriptionsOrderBean() + + mViewBinding.apply { + tvPairSuccessDeviceOutId.text = mDeviceOutId + setOnClickListenerViews(btnPairSuccessActivate) + } + } + + override fun liveDataObserve() { + mAddTrackerViewModel.apply { + //获取设备套餐可用状态 + mDeviceOrderStatusLiveData.observe(this@AddPairedSuccessActivity) { + setButtonEnabled(mViewBinding.btnPairSuccessActivate, ConstantInt.Type1) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + + MMKVUtil.putString(MMKVKey.FirstDeviceId, mDeviceId) + MMKVUtil.putString(MMKVKey.FirstDeviceOutId, mDeviceOutId) + MMKVUtil.putString(MMKVKey.FirstDeviceMac, mMac) + + mSubscriptionsOrderBean.let { o -> + o.deviceId = mDeviceId + o.userId = MMKVUtil.getString(MMKVKey.UserId) + } + + //判断设备是否有可用套餐 + if (availableOrder == ConstantInt.Type1) { + setButtonEnabled( + mViewBinding.btnPairSuccessActivate, ConstantInt.Type0 + ) + //刷新设备绑定 + mAddTrackerViewModel.updatePayDeviceState( + this@AddPairedSuccessActivity, mDeviceId + ) + } else { + mAddTrackerViewModel.getIccId( + this@AddPairedSuccessActivity, mDeviceOutId + ) + } + } + } + }) + } + + //获取设备sim卡IccId + mGetIccIdLiveData.observe(this@AddPairedSuccessActivity) { + setButtonEnabled(mViewBinding.btnPairSuccessActivate, ConstantInt.Type1) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mSubscriptionsOrderBean.deviceOutId = mDeviceOutId + mSubscriptionsOrderBean.iccid = iccid + setButtonEnabled(mViewBinding.btnPairSuccessActivate, ConstantInt.Type0) + + val intent = Intent(mContext, SubscriptionPlanActivity::class.java) + intent.putExtra(ConstantString.LkSetMeal, mSubscriptionsOrderBean) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivityFinish(intent) + } + } + }) + } + + //更新设备绑定状态 + mUpdatePayDeviceStateLiveData.observe(this@AddPairedSuccessActivity) { + setButtonEnabled(mViewBinding.btnPairSuccessActivate, ConstantInt.Type1) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + MMKVUtil.putInt(MMKVKey.isBindDevice, ConstantInt.isBind) + XEventBus.post(EventName.RefreshDevice) + if (isFirstBind) { + val intent = Intent(mContext, FirstSetPetTypeActivity::class.java) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivityFinish(intent) + } + finish() + } + + override fun onRequestError(exceptionCode: String?) { + LogUtil.e("支付成功后,更新绑定状态------->请求宠物数据错误onRequestError,再次请求") + if (exceptionCode == mNetworkRequestsFailLimit) { + } else { + setButtonEnabled(mViewBinding.btnPairSuccessActivate, ConstantInt.Type0) + mAddTrackerViewModel.updatePayDeviceState( + this@AddPairedSuccessActivity, mSubscriptionsOrderBean.deviceId + ) + } + } + }) + } + } + } + + + private fun addBindActivateDevice() { + if (TextUtils.isEmpty(mDeviceOutId)) { + showToast(R.string.txt_no_dfu) + return + } + setButtonEnabled(mViewBinding.btnPairSuccessActivate, ConstantInt.Type0) + mAddTrackerViewModel.getDeviceOrderStatus( + this@AddPairedSuccessActivity, mDeviceOutId + ) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPairSuccessActivate -> addBindActivateDevice() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/DeleteTracker1Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/DeleteTracker1Activity.kt new file mode 100644 index 0000000..b91b845 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/DeleteTracker1Activity.kt @@ -0,0 +1,51 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityDeleteTracker1Binding +import com.abbidot.tracker.util.Util + +class DeleteTracker1Activity : + BaseActivity(ActivityDeleteTracker1Binding::inflate) { + + private var mDeviceBean: DeviceBean? = null + private var isOnlyBindOneDevice = false + + override fun getTopBar() = mViewBinding.ilDeleteTracker1TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + isOnlyBindOneDevice = getBoolean(ConstantString.isCheck, false) + mDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.DeviceInfo, DeviceBean::class.java + ) + } + + mViewBinding.apply { + setOnClickListenerViews(btnCancelDeleteTracker1, btnNextDeleteTracker1) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnCancelDeleteTracker1 -> finish() + btnNextDeleteTracker1 -> { + Intent(mContext, DeleteTracker2Activity::class.java).let { + it.putExtra(ConstantString.DeviceInfo, mDeviceBean) + it.putExtra(ConstantString.isCheck, isOnlyBindOneDevice) + startActivityFinish(it) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/DeleteTracker2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/DeleteTracker2Activity.kt new file mode 100644 index 0000000..b57b2d2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/DeleteTracker2Activity.kt @@ -0,0 +1,263 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.graphics.Typeface +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityDeleteTracker2Binding +import com.abbidot.tracker.dialog.CommonDialog1 +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.TrackerSetViewModel +import com.clj.fastble.BleManager +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class DeleteTracker2Activity : + BaseActivity(ActivityDeleteTracker2Binding::inflate) { + + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + private val mTrackerSetViewModel: TrackerSetViewModel by viewModels() + + private var mDeviceBean: DeviceBean? = null + private var isOnlyBindOneDevice = false + + override fun getTopBar() = mViewBinding.ilDeleteTracker2TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + isOnlyBindOneDevice = getBoolean(ConstantString.isCheck, false) + mDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.DeviceInfo, DeviceBean::class.java + ) + } + + mViewBinding.apply { + setButtonEnabled(btnDeleteDeleteTracker2) + setOnClickListenerViews( + btnCancelDeleteTracker2, + btnDeleteDeleteTracker2, + ilDeleteTracker2BluetoothTips.trbBleConnectState + ) + + mDeviceBean?.let { device -> + val str = String.format(getString(R.string.txt_con_tracker_dec), device.deviceOutId) + tvDeleteTracker2Dec.text = str + + connectBle() + } + } + } + + private fun connectBle() { + mDeviceBean?.let { device -> + //是否连接了蓝牙 + if (BleManager.getInstance().isConnected(device.macId)) { + setButtonEnabled(mViewBinding.btnDeleteDeleteTracker2, ConstantInt.Type1) + mViewBinding.ilDeleteTracker2BluetoothTips.trbBleConnectState.let { + it.setBackgroundColor( + ContextCompat.getColor(mContext, R.color.cyan_color1) + ) + it.text = getString(R.string.tracker_manage_set_ble_connect) + } + } else { + mConnectionDeviceViewModel.connectDeviceToMac( + this@DeleteTracker2Activity, device.macId + ) + } + } + } + + override fun liveDataObserve() { + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { trackDevice: BleTrackDeviceBean -> + showLoading(false) + mDeviceBean?.apply { + if (trackDevice.mac == macId) { + mViewBinding.ilDeleteTracker2BluetoothTips.trbBleConnectState.let { view -> + val bgColor = if (trackDevice.conState == ConState.CONNECTED) { + setButtonEnabled( + mViewBinding.btnDeleteDeleteTracker2, ConstantInt.Type1 + ) + view.text = getString(R.string.tracker_manage_set_ble_connect) + ContextCompat.getColor(mContext, R.color.cyan_color1) + } else { + setButtonEnabled( + mViewBinding.btnDeleteDeleteTracker2, ConstantInt.Type0 + ) + view.text = getString(R.string.tracker_manage_set_ble_unconnect) + ContextCompat.getColor(mContext, R.color.red_color4) + } + view.setBackgroundColor(bgColor) +// ViewUtil.instance.bleConState( +// this@DeleteTracker2Activity, trackDevice.conState, Gravity.CENTER +// ) + } + } + } + } + + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + setButtonEnabled(mViewBinding.btnDeleteDeleteTracker2, ConstantInt.Type0) + if (conState == ConState.DEVICE_NOT_FOUND) { + ViewUtil.instance.showDialog( + mContext, + R.string.txt_no_search_device, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + } + }, + okTextResId = R.string.txt_sure, + cancelTextResId = R.string.txt_cancel + ) + } + mViewBinding.ilDeleteTracker2BluetoothTips.trbBleConnectState.let { view -> + ViewUtil.instance.bleConActionState(this, view, conState) + } + } + + //接收蓝牙返回数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mDeviceBean?.apply { + if (macId == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + + //解绑设备/删除设备 + mTrackerSetViewModel.mUnbindDeviceLiveData.observe(this@DeleteTracker2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { +// showToast(R.string.txt_delete_success) + lifecycleScope.launch { + withContext(Dispatchers.IO) { + mDeviceBean?.let { device -> + SRBleUtil.instance.disconnectToMac(device.macId) + //删除设备成功后,把本地数据库中的设备也删除 + MyDatabase.deviceDao().deleteByParams( + ConstantString.DeviceId, + device.deviceId, + MyDatabase.DeviceTableName + ) + runOnUiThread { + showDeleteDialog() + } + } + } + } + } + + override fun onRequestError(exceptionCode: String?) { + super.onRequestError(exceptionCode) + setButtonEnabled(mViewBinding.btnCancelDeleteTracker2, ConstantInt.Type1) + setButtonEnabled(mViewBinding.btnDeleteDeleteTracker2, ConstantInt.Type1) + } + }) + } + } + + private fun showDeleteDialog() { + CommonDialog1( + mContext, + "", + getString(R.string.txt_delete_succe), + false, + 5f, + Typeface.BOLD, + R.color.data_black_color, + false, + okClickListener = object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + if (isOnlyBindOneDevice) { + XEventBus.post(EventName.LogOut) + MMKVUtil.putInt(MMKVKey.isBindDevice, ConstantInt.Type0) + MMKVUtil.putInt(MMKVKey.isBindPet, ConstantInt.Type0) + val intent = Intent(mContext, AddNewTracker1Activity::class.java) + intent.putExtra(ConstantString.isFirstBind, true) + startActivityFinish(intent) + } else { + XEventBus.post(EventName.DeleteDevice) + finish() + } + } + }).show() + } + + /** + * 解析数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 0x19 && data1 == 1 && data2 == 0) { + mDeviceBean?.apply { + mTrackerSetViewModel.unbindDevice(this@DeleteTracker2Activity, deviceId) + } + } + } + } + + private fun deleteTracker() { + mDeviceBean?.apply { + //是否连接了蓝牙 + if (BleManager.getInstance().isConnected(macId)) { + SRBleUtil.instance.getConnectMacDevice(macId)?.let { + setButtonEnabled(mViewBinding.btnDeleteDeleteTracker2, ConstantInt.Type0) + setButtonEnabled(mViewBinding.btnCancelDeleteTracker2, ConstantInt.Type0) + SRBleUtil.instance.writeData(it.bleDevice, SRBleCmdUtil.instance.factoryReset()) + } + } else { + showToast(R.string.tracker_manage_set_ble_unconnect) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnCancelDeleteTracker2 -> finish() + btnDeleteDeleteTracker2 -> deleteTracker() + ilDeleteTracker2BluetoothTips.trbBleConnectState -> mDeviceBean?.apply { + ViewUtil.instance.connectionBtn( + mContext, + ilDeleteTracker2BluetoothTips.trbBleConnectState.text.toString(), + macId + ) { + connectBle() + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/MyTrackerV2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/MyTrackerV2Activity.kt new file mode 100644 index 0000000..de1ef34 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/MyTrackerV2Activity.kt @@ -0,0 +1,577 @@ +package com.abbidot.tracker.ui.activity.device + +import android.content.Intent +import android.text.TextUtils +import android.view.LayoutInflater +import android.view.View +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.AssignOptionsAdapter +import com.abbidot.tracker.adapter.MyTrackerV2Adapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityMyTrackerV2Binding +import com.abbidot.tracker.ui.activity.pet.PetProfileActivity +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.PetRelationViewModel +import com.abbidot.tracker.vm.TrackerInfoViewModel +import com.abbidot.tracker.vm.TrackerSetViewModel +import com.qmuiteam.qmui.widget.dialog.QMUIBottomSheet +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class MyTrackerV2Activity : + BaseActivity(ActivityMyTrackerV2Binding::inflate) { + + private val mTrackerViewModel: TrackerInfoViewModel by viewModels() + private val mTrackerSetViewModel: TrackerSetViewModel by viewModels() + private val mPetRelationViewModel: PetRelationViewModel by viewModels() + + private lateinit var mMyTrackerAdapter: MyTrackerV2Adapter + private lateinit var mAssignOptionsAdapter: AssignOptionsAdapter + + private var mQMUIBottomSheet: QMUIBottomSheet? = null + + private lateinit var mUnAssignPetList: MutableList + private lateinit var mUnAssignDeviceList: MutableList + + //当前操作的DeviceBean下标 + private var mCurrentBeanOption = 0 + + //是底部弹窗跳转添加的 + private var isShowBottomSheetAdd = false + + //是否只有绑定一个的设备 + private var isOnlyBindOneDevice = false + + override fun getTopBar() = mViewBinding.ilMyTrackerV2TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.tracker_manage_mine) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mUnAssignPetList = mutableListOf() + mUnAssignDeviceList = mutableListOf() + + mViewBinding.apply { + val trackerList = mutableListOf() + trackerList.add(DeviceBean()) + mMyTrackerAdapter = MyTrackerV2Adapter(mContext, trackerList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, + rvMyTrackerV2List, + mMyTrackerAdapter, + top = AppUtils.dpToPx(10), + bottom = AppUtils.dpToPx(10) + ) + + mAssignOptionsAdapter = AssignOptionsAdapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val dataBean = mAssignOptionsAdapter.getData()[pos] + if (pos == 0) { + isShowBottomSheetAdd = true + //处理设备类型 + if (dataBean.menuType == ConstantInt.SpecialType) { + startActivity(Intent(mContext, AddNewTracker1Activity::class.java)) + } else { + startActivity(Intent(mContext, PetProfileActivity::class.java)) + } + } else { + //关联其他宠物/设备 + val device = mMyTrackerAdapter.getData()[mCurrentBeanOption] + if (dataBean.menuType == ConstantInt.SpecialType) { + if (TextUtils.isEmpty(device.deviceOutId)) { + mPetRelationViewModel.bindPet( + this@MyTrackerV2Activity, dataBean.id, device.pet?.petId!! + ) + } else mTrackerSetViewModel.assignOtherPetTracker( + this@MyTrackerV2Activity, + device.pet?.petId!!, + device.deviceId, + dataBean.id + ) + } else { + if (null == device.pet) { + mPetRelationViewModel.bindPet( + this@MyTrackerV2Activity, device.deviceId, dataBean.id + ) + } else mPetRelationViewModel.assignOtherPet( + this@MyTrackerV2Activity, + device.deviceId, + device.pet?.petId!!, + dataBean.id + ) + } + } + } + }) + } + } + + mTrackerViewModel.getDeviceList(this, 1) + } + + override fun liveDataObserve() { + //更新宠物信息,添加/编辑宠物 + XEventBus.observe(this, EventName.RefreshPet) { + if (isShowBottomSheetAdd) mPetRelationViewModel.getUnbindPetList(this@MyTrackerV2Activity) + else mTrackerViewModel.getDeviceList(this, 1) + } + //添加了设备 + XEventBus.observe(this, EventName.RefreshDevice) { + if (isShowBottomSheetAdd) mTrackerViewModel.getUnbindTrackerList(this@MyTrackerV2Activity) + else mTrackerViewModel.getDeviceList(this, 1) + } + //删除设备 + XEventBus.observe(this, EventName.DeleteDevice) { + mTrackerViewModel.getDeviceList(this, 1) + } + //删除设备 + XEventBus.observe(this, EventName.LogOut) { + //删除了最后一个设备 + finish() + } + + mTrackerViewModel.apply { + //获取设备列表 + getDeviceListLiveData.observe(this@MyTrackerV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + dismissBottomSheet() + isOnlyBindOneDevice = false + //绑定的设备数量 + var bindDeviceNumber = 0 + it.getOrNull()?.let { list -> + lifecycleScope.launch { + withContext(Dispatchers.IO) { + for (device in list) { + //保存本地数据库 + if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) { + bindDeviceNumber++ + MyDatabase.deviceDao().insert(device) + } else if (!TextUtils.isEmpty(device.deviceOutId) && null == device.pet) { + //删除本地没绑定的设备 + MyDatabase.deviceDao().deleteByParams( + ConstantString.DeviceId, + device.deviceId, + MyDatabase.DeviceTableName + ) + } + } + if (bindDeviceNumber == 1) isOnlyBindOneDevice = true + } + list.add(DeviceBean()) + mMyTrackerAdapter.setData(list, true) + } + } + } + }) + } + //获取未绑定宠物的设备 + mUnbindTrackerListLiveData.observe(this@MyTrackerV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mUnAssignDeviceList.clear() + it.getOrNull()?.let { deviceBeans -> + mUnAssignDeviceList.addAll(deviceBeans) + } + showAssignOptions(mUnAssignDeviceList, false) + } + }, isShowNoDataTip = false) + } + } + + mTrackerSetViewModel.apply { + //解绑设备/删除设备 +// mUnbindDeviceLiveData.observe(this@MyTrackerV2Activity) { +// dealRequestResult(it, object : GetResultCallback { +// override fun onResult(any: Any) { +// showToast(R.string.txt_delete_success) +// XEventBus.post(EventName.DeleteDevice) +// lifecycleScope.launch { +// withContext(Dispatchers.IO) { +// val device = mMyTrackerAdapter.getData()[mCurrentBeanOption] +// SRBleUtil.instance.disconnectToMac(device.macId) +// //删除设备成功后,把本地数据库中的设备也删除 +// MyDatabase.deviceDao().deleteByParams( +// ConstantString.DeviceId, +// device.deviceId, +// MyDatabase.DeviceTableName +// ) +// } +// mUnAssignDeviceList.clear() +// mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) +// } +// } +// }) +// } + //关联其他设备 + mAssignOtherTrackerLiveData.observe(this@MyTrackerV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + dismissBottomSheet() + XEventBus.post(EventName.RefreshPet) +// mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) + mUnAssignPetList.clear() + } + }) + } + } + + mPetRelationViewModel.apply { + //获取未绑定设备的宠物 + mUnbindPetListLiveData.observe(this@MyTrackerV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mUnAssignPetList.clear() + it.getOrNull()?.let { petBeans -> + mUnAssignPetList.addAll(petBeans) + } + showAssignOptions(mUnAssignPetList, true) + } + }, isShowNoDataTip = false) + } + //给设备绑定其他宠物 + mAssignOtherPetLiveData.observe(this@MyTrackerV2Activity) { + dismissBottomSheet() + XEventBus.post(EventName.RefreshPet) +// mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) + mUnAssignPetList.clear() + } + //绑定设备/宠物 + mBindPetLiveData.observe(this@MyTrackerV2Activity) { + dismissBottomSheet() + XEventBus.post(EventName.RefreshPet) +// mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) + mUnAssignPetList.clear() + mUnAssignDeviceList.clear() + } + //解绑宠物 + mUnbindPetLiveData.observe(this@MyTrackerV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + val device = mMyTrackerAdapter.getData()[mCurrentBeanOption] + //解绑成功后,把本地数据库中的设备也删除 + MyDatabase.deviceDao().deleteByParams( + ConstantString.DeviceId, + device.deviceId, + MyDatabase.DeviceTableName + ) + } + mUnAssignDeviceList.clear() + mUnAssignPetList.clear() + XEventBus.post(EventName.RefreshPet) +// mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) + } + } + }) + } + //删除宠物 + mDeletePetLiveData.observe(this@MyTrackerV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.DeletePet) + mUnAssignPetList.clear() + mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) + } + }) + } + } + } + + /** + * 获取哪个未绑定的数据 + */ + fun getAssignData(position: Int) { + mCurrentBeanOption = position + mMyTrackerAdapter.getData()[position].apply { + if (TextUtils.isEmpty(deviceOutId)) { + getUnAssignDeviceData() + } else if (null == pet) { + getUnAssignPetData() + } + } + } + + private fun getUnAssignDeviceData() { + if (mUnAssignDeviceList.size == 0) mTrackerViewModel.getUnbindTrackerList(this@MyTrackerV2Activity) + else showAssignOptions(mUnAssignDeviceList, false) + } + + private fun getUnAssignPetData() { + if (mUnAssignPetList.size == 0) mPetRelationViewModel.getUnbindPetList(this@MyTrackerV2Activity) + else showAssignOptions(mUnAssignPetList, true) + } + + /** + * 显示可添加的设备或者宠物 + */ + private fun showAssignOptions(any: Any, isPet: Boolean) { + if (any is MutableList<*>) { + val list = mutableListOf() + if (isPet) { + list.add(DataBean("+ " + getString(R.string.tracker_manage_add_pet), "")) + } else { + DataBean("+ " + getString(R.string.txt_new_tracker), "").apply { + menuType = ConstantInt.SpecialType + list.add(this) + } + } + for (i in 0 until any.size) { + val a = any[i] + if (a is PetBean) { + DataBean(a.petName, a.imgurl).apply { + menuType = a.petType + id = a.petId + list.add(this) + } + } else if (a is DeviceBean) { + DataBean(a.deviceOutId, "").apply { + menuType = ConstantInt.SpecialType + id = a.deviceId + list.add(this) + } + } + } + mAssignOptionsAdapter.setData(list, true) + } + + if (null == mQMUIBottomSheet) { + val builder = ViewUtil.instance.showBottomSheetList(mContext, null).apply { + val view = + LayoutInflater.from(mContext).inflate(R.layout.layout_assign_options_list, null) + addContentFooterView(view) + val recyclerView = view.findViewById(R.id.rv_assign_options_list) + ViewUtil.instance.setRecyclerViewDecorationLinearLayoutManager( + mContext, recyclerView, mAssignOptionsAdapter + ) +// setOnBottomDialogDismissListener { +// isShowBottomSheetAdd = false +// } + } + mQMUIBottomSheet = builder.build().apply { + setOnCancelListener { + if (isShowBottomSheetAdd) { + isShowBottomSheetAdd = false + mTrackerViewModel.getDeviceList(this@MyTrackerV2Activity, 1) + } + } + setCanceledOnTouchOutside(false) + } + } + mQMUIBottomSheet?.show() + } + + /** + * 消失可添加的设备或者宠物的底部弹窗 + */ + private fun dismissBottomSheet() { + isShowBottomSheetAdd = false + mQMUIBottomSheet?.dismiss() + } + + /** + *底部弹出设备操作菜单 + */ + fun showDeviceBottomSheetMenu(pos: Int) { + mCurrentBeanOption = pos + val device = mMyTrackerAdapter.getData()[pos] + ViewUtil.instance.showBottomSheetList(mContext, { dialog, itemView, position, tag -> + dialog.dismiss() + when (position) { + 0 -> if (null == device.pet) deleteDevice(device) + else getUnAssignDeviceData() + + 1 -> if (!isOnlyBindOneDevice) unAssignDeviceDialogTips(device) + else deleteDevice(device) + + 2 -> deleteDevice(device) + } + }, title = device.deviceOutId).apply { + if (null == device.pet) { + addItem( + getString(R.string.txt_delete) + " ${device.deviceOutId}" + ) + } else { + addItem(getString(R.string.txt_assign_other_tracker)) + if (!isOnlyBindOneDevice) { + addItem( + String.format(getString(R.string.txt_unassign_other), device.deviceOutId) + ) + } + addItem( + getString(R.string.txt_delete) + " ${device.deviceOutId}" + ) + } + }.build().show() + } + + /** + *底部弹出宠物操作菜单 + */ + fun showPetBottomSheetMenu(pos: Int) { + mCurrentBeanOption = pos + val device = mMyTrackerAdapter.getData()[pos] + var petName = "" + device.pet?.let { + petName = it.petName + } + ViewUtil.instance.showBottomSheetList(mContext, { dialog, itemView, position, tag -> + dialog.dismiss() + when (position) { + 0 -> { + //关联其他宠物 + if (!TextUtils.isEmpty(device.deviceOutId)) { + getUnAssignPetData() + } else { + //编辑 + goEditPet(device) + } + } + + 1 -> { + //有绑定对应的设备 + if (!TextUtils.isEmpty(device.deviceOutId)) { + //只有最后一对设备,只有编辑 + if (isOnlyBindOneDevice) goEditPet(device) + //解绑宠物 + else unAssignDeviceDialogTips(device) + } else { + //删除 + deletePetDialogTips(device) + } + } + //编辑 + 2 -> goEditPet(device) + 3 -> deletePetDialogTips(device) + } + }, title = petName).apply { + //有绑定对应的设备 + if (!TextUtils.isEmpty(device.deviceOutId)) { + addItem(getString(R.string.txt_assign_other_pet)) + if (!isOnlyBindOneDevice) addItem( + String.format(getString(R.string.txt_unassign_other), petName) + ) + addItem(getString(R.string.pet_manage_detail_edit) + " $petName") + if (!isOnlyBindOneDevice) addItem( + getString(R.string.txt_delete) + " $petName" + ) + } else { + addItem(getString(R.string.pet_manage_detail_edit) + " $petName") + addItem(getString(R.string.txt_delete) + " $petName") + } + }.build().show() + } + + /** + * 跳转宠物编辑 + */ + private fun goEditPet(device: DeviceBean) { + device.pet?.apply { + val intent = Intent( + mContext, PetProfileActivity::class.java + ) + intent.putExtra(ConstantString.Edit, true) + intent.putExtra(ConstantString.Pet, this) + startActivity(intent) + } + } + + /** + * 添加红色的底部item + */ +// private fun addBottomSheetRedItem( +// text: String, bottomListSheetBuilder: QMUIBottomSheet.BottomListSheetBuilder +// ) { +// QMUIBottomSheetListItemModel(text, text).let { +// it.skinTextColorAttr(R.attr.qmui_config_color_red) +// bottomListSheetBuilder.addItem(it) +// } +// } + + /** + * 删除设备 + */ + private fun deleteDevice(device: DeviceBean) { + ViewUtil.instance.showDialog( + mContext, getString(R.string.txt_delete_tips), object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + Intent(mContext, DeleteTracker1Activity::class.java).let { + it.putExtra(ConstantString.DeviceInfo, device) + val isUnbindLastOne = if (isOnlyBindOneDevice && null == device.pet) { + false + } else isOnlyBindOneDevice + it.putExtra(ConstantString.isCheck, isUnbindLastOne) + startActivity(it) + } +// mTrackerSetViewModel.unbindDevice(this@MyTrackerV2Activity, device.deviceId) + } + }, okTextResId = R.string.txt_sure + ) + } + + /** + * 解绑宠物/设备 + */ + private fun unAssignDeviceDialogTips(device: DeviceBean) { + device.apply { + pet?.let { + val string = String.format( + getString(R.string.txt_unassign_pet_tracker), it.petName, deviceOutId + ) + ViewUtil.instance.showDialog( + mContext, string, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mPetRelationViewModel.unbindPet( + this@MyTrackerV2Activity, deviceId, it.petId + ) + } + }, okTextResId = R.string.txt_sure + ) + } + } + } + + /** + * 删除宠物 + */ + private fun deletePetDialogTips(device: DeviceBean) { + device.apply { + pet?.let { + val string = String.format( + getString(R.string.txt_delete_pet_data), it.petName + ) + ViewUtil.instance.showDialog( + mContext, string, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mPetRelationViewModel.deletePet( + this@MyTrackerV2Activity, it.petId + ) + } + }, okTextResId = R.string.txt_sure + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddAndEditFencesZoneBaseActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddAndEditFencesZoneBaseActivity.kt new file mode 100644 index 0000000..29dd39e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddAndEditFencesZoneBaseActivity.kt @@ -0,0 +1,791 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.annotation.SuppressLint +import android.text.TextUtils +import android.view.Gravity +import android.view.MotionEvent +import android.view.View +import androidx.activity.viewModels +import androidx.core.view.isVisible +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.SelectFencesTypeCardShadeAdapter +import com.abbidot.tracker.adapter.VirtualFencesZoneShadeAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.GetFencesBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityAddEditFencesZoneBinding +import com.abbidot.tracker.ui.common.map.FencesAddEditMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.FencesManageViewModel +import com.abbidot.tracker.vm.FencesMapViewModel +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.OnCircleViewScaleClickListener +import com.abbidot.tracker.widget.OnPolygonViewPointPullClickListener +import com.abbidot.tracker.widget.OnRectViewRotateScaleClickListener +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo + +/** + * 添加编辑围栏 + */ +open class AddAndEditFencesZoneBaseActivity : + BaseActivity(ActivityAddEditFencesZoneBinding::inflate), + FencesAddEditMapCommon.OnActivityCallBack, OnRectViewRotateScaleClickListener, + OnCircleViewScaleClickListener, OnPolygonViewPointPullClickListener { + + val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + private val mFencesManageViewModel: FencesManageViewModel by viewModels() + private val mFencesMapViewModel: FencesMapViewModel by viewModels() + + private lateinit var mFragment: Fragment + lateinit var mFencesMapCommon: FencesAddEditMapCommon + + lateinit var mFencesZoneShapeList: MutableList + + lateinit var mFencesZoneShapeAdapter: VirtualFencesZoneShadeAdapter + lateinit var mFencesZoneTypeList: MutableList + + lateinit var mSelectTypeShadeAdapter: SelectFencesTypeCardShadeAdapter + + lateinit var mFencesBean: FencesBean + var mPetBean: PetBean? = null + var mFencesList: GetFencesBean? = null + var isEditFences = false + + private var mShowCenterLocationType = ConstantInt.OtherLocationType + private val mDefaultFencesName = + arrayOf("Home", "Office", "Garden", "Park", "Play", "Unsafe", "Vegetable Garden", "Water") + + //是否是卫星地图 + private var isSatelliteMap = false + private lateinit var mHaveFencesList: MutableList + + private lateinit var mDistanceUnit: String + + override fun getTopBar() = mViewBinding.addEditFencesZoneTopBar.titleTopBar + + @SuppressLint("ClickableViewAccessibility") + override fun initData() { + super.initData() +// setLeftBackImage(R.drawable.icon_white_back_svg) + val mapTypeSp = Util.getMapTypeSp() + isSatelliteMap = mapTypeSp == ConstantInt.Type1 + + mDistanceUnit = Util.getMetricUnits(mContext, ConstantInt.Type3) + + mHaveFencesList = mutableListOf() + mPetBean = + Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java)?.apply { + mFencesBean.deviceId = deviceId + mFencesBean.deviceServiceId = deviceServiceId + } + mFencesList = + Util.getParcelableAdaptive(intent, ConstantString.Fence, GetFencesBean::class.java) + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivFencesZoneRefreshBtn, mShowCenterLocationType + ) + ilFencesZoneNameInput.tvInputNameTitle.text = getString(R.string.txt_fence_name) + fencesZoneCircleView.setOnCircleViewScaleClickListener(this@AddAndEditFencesZoneBaseActivity) + fencesZoneRectView.setOnRectViewRotateScaleClickListener(this@AddAndEditFencesZoneBaseActivity) + fencesZonePolygonView.setOnPolygonViewPointPullClickListener(this@AddAndEditFencesZoneBaseActivity) + + mFencesMapCommon = FencesAddEditMapCommon() + mFragment = mFencesMapCommon.getMapFragment( + mContext, + mFencesBean, + fencesZoneCircleView, + fencesZoneRectView, + fencesZonePolygonView, + isEditFences, + this@AddAndEditFencesZoneBaseActivity + ) { +// mFencesList?.apply { +// mPetBean?.let { +// mFencesMapCommon.setPetHeadAndCurrentLocation( +// it.imgurl, latitude, longitude +// ) +// } +// } + mapLoadOk() + } + supportFragmentManager.commit { + add(R.id.fc_fences_map_fragment, mFragment) + } + + addListData() + + mFencesZoneShapeAdapter = + VirtualFencesZoneShadeAdapter(mContext, mFencesZoneShapeList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + if (mFencesZoneShapeList[pos].selected) return + ViewUtil.instance.updateDataBeanOnlySelectedItem( + this@apply, mFencesZoneShapeList, pos + ) + setFenceShape(pos + 1) + mFencesMapCommon.calculateDistanceAndGetPointLatLng( + this@AddAndEditFencesZoneBaseActivity, 600 + ) + } + }) + } + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, rvFencesZoneShapeList, mFencesZoneShapeAdapter + ) + + mSelectTypeShadeAdapter = + SelectFencesTypeCardShadeAdapter(mContext, mFencesZoneTypeList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + ViewUtil.instance.updateDataBeanOnlySelectedItem( + this@apply, mFencesZoneTypeList, pos + ) + if (mFencesBean.fenceType == 2) mFencesBean.fenceNameType = pos + 6 + else mFencesBean.fenceNameType = pos + 1 + setFenceName() + } + }) + } + ViewUtil.instance.setRecyclerViewGridLayout( + mContext, rvFencesZoneImageTypeList, mSelectTypeShadeAdapter, 5 + ) + + setOnClickListenerViews( + ivFencesZoneRefreshBtn, + ivFencesZoneMapSwitchBtn, + tvFencesZoneShapeResetBtn, + btnCancelFencesZone, + btnSaveFencesZone + ) + + var oldY = 0f + val slideDis = 50 + rlAddEditFencesZoneInfoLayout.setOnTouchListener { v, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + oldY = event.y + } + + MotionEvent.ACTION_MOVE -> { + } + + MotionEvent.ACTION_UP -> { + if (event.y - oldY > slideDis) { + downLayout() + } else if (event.y - oldY < -slideDis) { + slideUpLayout() + } + } + } + false + } + //监听布局变化 + rlAddEditFencesZoneInfoLayout.addOnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom -> + bottomMarginOverlap() + } + } + + setListenKeyboardChange() + + setFenceName() + } + + open fun mapLoadOk() { + drawFences() + } + + /** + * 设置围栏名字 + */ + private fun setFenceName() { + mViewBinding.ilFencesZoneNameInput.apply { + val inputContent = etInputContent.text.toString() + if (TextUtils.isEmpty(inputContent)) { + etInputContent.setText(mDefaultFencesName[mFencesBean.fenceNameType - 1]) + } else { + var isDefaultFencesName = false + for (str in mDefaultFencesName) { + if (str == inputContent) { + isDefaultFencesName = true + break + } + } + if (isDefaultFencesName) etInputContent.setText(mDefaultFencesName[mFencesBean.fenceNameType - 1]) + } + } + } + + /** + * 设置显示哪种围栏样式 + */ + fun setFenceShape(shapeType: Int) { + if (mFencesMapCommon.isMapLoadOk()) showLoading(true) + mViewBinding.fencesZoneCircleView.visibility = View.GONE + mViewBinding.fencesZoneRectView.visibility = View.GONE + mViewBinding.fencesZonePolygonView.visibility = View.GONE + when (shapeType) { + ConstantInt.CircleShapeType -> mViewBinding.fencesZoneCircleView.visibility = + View.VISIBLE + + ConstantInt.RectangleShapeType -> mViewBinding.fencesZoneRectView.visibility = + View.VISIBLE + + ConstantInt.PolygonShapeType -> mViewBinding.fencesZonePolygonView.visibility = + View.VISIBLE + } + mFencesBean.fenceShapeType = shapeType + } + + override fun liveDataObserve() { + //反地理编码 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(this) { + if (mFencesBean.startDrag) return@observe + mViewBinding.tvFencesZoneGpsLocation.apply { + mFencesBean.address = it +// visibility = if (TextUtils.isEmpty(it)) { +// View.INVISIBLE +// } else { +// mFencesBean.address = it +// if (mViewBinding.ilFencesZoneNameInput.root.visibility == View.GONE) View.GONE +// else View.VISIBLE +// } +// text = it + } + } + } + + /** + * 地图上画已添加的围栏 + */ + fun drawFences() { + mFencesList?.apply { + nogoFenceList?.let { + mHaveFencesList.addAll(it.toList()) + } + normalFenceList?.let { + mHaveFencesList.addAll(it.toList()) + } + + if (mHaveFencesList.size > 0) mFencesMapViewModel.setImageFencesData( + mContext, mHaveFencesList, mFragment, isSatelliteMap = isSatelliteMap + ) + } + } + + /** + * 添加围栏通知 + */ + fun addLiveDataObserve() { + mFencesManageViewModel.mFencesAddLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + sendNotifyData() + successTips(R.string.txt_save_successful) + } + + override fun onRequestError(exceptionCode: String?) { + super.onRequestError(exceptionCode) + setButtonEnabled(mViewBinding.btnSaveFencesZone, ConstantInt.Type1) + } + }) + } + } + + /** + * 编辑更新围栏通知 + */ + fun editLiveDataObserve() { + mFencesManageViewModel.mDeleteFenceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.DeleteFences) + sendNotifyData() + showToast( + R.string.txt_delete_success, isFinish = true, gravity = Gravity.CENTER + ) + } + }) + } + mFencesManageViewModel.mUpdateFenceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + sendNotifyData() + successTips(R.string.txt_save_successful) + } + }) + } + } + + private fun sendNotifyData() { + mPetBean?.apply { + SRBleUtil.instance.isConnectBleSendNotifyData(macID) + } + } + + /** + * 修改成功提示 + */ + private fun successTips(resStringId: Int) { + XEventBus.post(EventName.RefreshFences) + showToast(resStringId, isFinish = true, gravity = Gravity.CENTER) + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + bottomMarginOverlap() + } + + private fun slideUpLayout() { + mViewBinding.apply { + LogUtil.i("上滑") +// tvFencesZoneGpsLocation.visibility = View.VISIBLE +// tvFencesZoneShapeResetBtn.visibility = View.VISIBLE + ilFencesZoneNameInput.root.visibility = View.VISIBLE + rvFencesZoneImageTypeList.visibility = View.VISIBLE + tvFencesZoneShapeTitle.visibility = View.VISIBLE + rvFencesZoneShapeList.visibility = View.VISIBLE + ivFencesZoneRefreshBtn.visibility = View.GONE + ivFencesZoneMapSwitchBtn.visibility = View.GONE + +// mFencesMapCommon.resetFencesCentre(mFencesBean) + + YoYo.with(Techniques.BounceInUp).duration(800).onEnd { + ViewUtil.instance.viewShowFadeInAnimation(ivFencesZoneRefreshBtn) + ViewUtil.instance.viewShowFadeInAnimation(ivFencesZoneMapSwitchBtn) + }.playOn(rlAddEditFencesZoneInfoLayout) + } + } + + private fun downLayout() { + mViewBinding.apply { + LogUtil.i("下滑") + tvFencesZoneGpsLocation.visibility = View.GONE + tvFencesZoneShapeResetBtn.visibility = View.GONE + ilFencesZoneNameInput.root.visibility = View.GONE + rvFencesZoneImageTypeList.visibility = View.GONE + tvFencesZoneShapeTitle.visibility = View.GONE + rvFencesZoneShapeList.visibility = View.GONE + rlAddEditFencesZoneInfoLayout.visibility = View.GONE + ivFencesZoneRefreshBtn.visibility = View.GONE + ivFencesZoneMapSwitchBtn.visibility = View.GONE + +// mFencesMapCommon.resetFencesCentre(mFencesBean) + + YoYo.with(Techniques.BounceIn).onStart { + rlAddEditFencesZoneInfoLayout.visibility = View.VISIBLE + }.onEnd { + ViewUtil.instance.viewShowFadeInAnimation(ivFencesZoneRefreshBtn) + ViewUtil.instance.viewShowFadeInAnimation(ivFencesZoneMapSwitchBtn) + }.duration(800).playOn(rlAddEditFencesZoneInfoLayout) + } + } + + /** + * 重新设置布局位置 + */ + private fun bottomMarginOverlap() { + mViewBinding.apply { + ViewUtil.instance.setMapViewBottomMarginOverlap( + rlAddEditFencesZoneMapLayout, + rlAddEditFencesZoneInfoLayout.height, + AppUtils.dpToPx(20) + ) + + //重新设置围栏中心 + mFencesMapCommon.resetFencesCentre( + this@AddAndEditFencesZoneBaseActivity, fencesZoneCircleView, mFencesBean + ) + } + } + + private fun addListData() { + mFencesZoneShapeList = mutableListOf() + with(DataBean()) { + imageId = R.drawable.icon_fence_circle_shape_svg + selectImageId = R.drawable.icon_fence_circle_shape_select_svg + selected = true + mFencesZoneShapeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_fence_rect_shape_svg + selectImageId = R.drawable.icon_fence_rect_shape_select_svg + mFencesZoneShapeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_fence_polygon_shape_svg + selectImageId = R.drawable.icon_fence_polygon_shape_select_svg + mFencesZoneShapeList.add(this) + } + + mFencesZoneTypeList = mutableListOf() + } + + /** + * 安全区域围栏数据初始化 + * @param isEditFences 编辑围栏不需要初始化大小 + */ + fun safeZoneInitData(isEditFences: Boolean = false) { + mViewBinding.apply { + safeZoneToMapTypeChangeColor() + + if (!isEditFences) { + fencesZoneCircleView.initDefaultRadius() + fencesZoneRectView.initDefaultWidthHeight() + fencesZonePolygonView.initDefaultWidthHeightSize() + } + + with(DataBean()) { + imageId = R.drawable.icon_save_zone_type1_svg + selected = true + mFencesZoneTypeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_save_zone_type2_svg + mFencesZoneTypeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_save_zone_type3_svg + mFencesZoneTypeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_save_zone_type4_svg + mFencesZoneTypeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_save_zone_type5_svg + mFencesZoneTypeList.add(this) + } + mSelectTypeShadeAdapter.setData(mFencesZoneTypeList, true) + } + } + + /** + * 危险区域围栏数据初始化 + * @param isEditFences 编辑围栏不需要初始化大小 + */ + fun noGoZoneInitData(isEditFences: Boolean = false) { + mViewBinding.apply { + fencesZoneCircleView.setColorAndImage( + mContext, + R.color.red_color2, + R.color.red_color3, + R.drawable.icon_fences_zone_gps_red_svg, + R.drawable.icon_fence_circle_scale_red_svg + ) + fencesZoneRectView.setColorAndImage( + mContext, + R.color.red_color2, + R.color.red_color3, + R.drawable.icon_fences_zone_gps_red_svg, + R.drawable.icon_fence_rect_zoom_red_svg, + R.drawable.icon_fence_dot_red_svg, + R.drawable.icon_fence_rect_red_rotate_svg + ) + fencesZonePolygonView.setColorAndImage( + mContext, + R.color.red_color2, + R.color.red_color3, + R.drawable.icon_fences_zone_gps_red_svg, + R.drawable.icon_fence_polygon_operate_red_svg + ) + + if (!isEditFences) { + fencesZoneCircleView.initDefaultRadius() + fencesZoneRectView.initDefaultWidthHeight() + fencesZonePolygonView.initDefaultWidthHeightSize() + } + + with(DataBean()) { + imageId = R.drawable.icon_no_zone_type1_svg + selected = true + mFencesZoneTypeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_no_zone_type2_svg + mFencesZoneTypeList.add(this) + } + with(DataBean()) { + imageId = R.drawable.icon_no_zone_type3_svg + mFencesZoneTypeList.add(this) + } + mSelectTypeShadeAdapter.setData(mFencesZoneTypeList, true) + } + } + + /** + * 安全围栏根据地图类型改变颜色 + */ + private fun safeZoneToMapTypeChangeColor() { + mViewBinding.apply { + if (isSatelliteMap) { + fencesZoneCircleView.setColorAndImage( + mContext, + R.color.select_color2, + locationDrawableRes = R.drawable.icon_fences_zone_gps_green_svg + ) + fencesZoneRectView.setColorAndImage( + mContext, + R.color.select_color2, + locationDrawableRes = R.drawable.icon_fences_zone_gps_green_svg + ) + fencesZonePolygonView.setColorAndImage( + mContext, + R.color.select_color2, + locationDrawableRes = R.drawable.icon_fences_zone_gps_green_svg + ) + } else { + fencesZoneCircleView.setColorAndImage( + mContext, + R.color.select_color, + locationDrawableRes = R.drawable.icon_fences_zone_gps_svg + ) + fencesZoneRectView.setColorAndImage( + mContext, + R.color.select_color, + locationDrawableRes = R.drawable.icon_fences_zone_gps_svg + ) + fencesZonePolygonView.setColorAndImage( + mContext, + R.color.select_color, + locationDrawableRes = R.drawable.icon_fences_zone_gps_svg + ) + } + } + } + + /** + * 保存围栏` + */ + fun saveFences(fencesBean: FencesBean) { + val fenceName = mViewBinding.ilFencesZoneNameInput.etInputContent.text.toString() + if (TextUtils.isEmpty(fenceName)) { + showToast(R.string.txt_not_null, gravity = Gravity.CENTER) + return + } + fencesBean.apply { + setButtonEnabled(mViewBinding.btnSaveFencesZone, ConstantInt.Type0) + name = fenceName + when (fenceShapeType) { + ConstantInt.CircleShapeType -> { + mFencesManageViewModel.addCircleFence( + this@AddAndEditFencesZoneBaseActivity, fencesBean + ) + } + + ConstantInt.RectangleShapeType -> { + mFencesManageViewModel.addRectFence( + this@AddAndEditFencesZoneBaseActivity, fencesBean + ) + } + + else -> { + mFencesManageViewModel.addPolygonFence( + this@AddAndEditFencesZoneBaseActivity, fencesBean + ) + } + } + } + } + + /** + * 更新围栏 + */ + fun updateFences(fencesBean: FencesBean) { + val fenceName = mViewBinding.ilFencesZoneNameInput.etInputContent.text.toString() + if (TextUtils.isEmpty(fenceName)) { + showToast(R.string.txt_not_null, gravity = Gravity.CENTER) + return + } + fencesBean.apply { + name = fenceName + when (fenceShapeType) { + ConstantInt.CircleShapeType -> { + mFencesManageViewModel.updateCircleFence( + this@AddAndEditFencesZoneBaseActivity, fencesBean + ) + } + + ConstantInt.RectangleShapeType -> { + mFencesManageViewModel.updateRectFence( + this@AddAndEditFencesZoneBaseActivity, fencesBean + ) + } + + else -> { + mFencesManageViewModel.updatePolygonFence( + this@AddAndEditFencesZoneBaseActivity, fencesBean + ) + } + } + } + } + + /** + * 删除围栏 + */ + fun deleteFences(fencesBean: FencesBean) { + fencesBean.apply { + mFencesManageViewModel.deleteFence( + this@AddAndEditFencesZoneBaseActivity, fenceId, deviceServiceId + ) + } + } + + /** + * 恢复默认围栏大小 + */ + private fun recoverDefaultFencesView() { + mViewBinding.apply { + if (fencesZoneCircleView.isVisible) fencesZoneCircleView.initDefaultRadius() + if (fencesZoneRectView.isVisible) fencesZoneRectView.initDefaultWidthHeight() + if (fencesZonePolygonView.isVisible) fencesZonePolygonView.initDefaultWidthHeightSize() + mFencesMapCommon.calculateDistanceAndGetPointLatLng(this@AddAndEditFencesZoneBaseActivity) + } + } + + override fun rectRotateClick() { + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + override fun rectScaleClick() { + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + override fun rectStartDragClick() { + mFencesBean.startDrag = true + } + + override fun rectEndDragClick() { + mFencesBean.startDrag = false + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + override fun circleScaleClick() { + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + override fun circleStartDragClick() { + mFencesBean.startDrag = true + } + + override fun circleEndDragClick() { + mFencesBean.startDrag = false + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + override fun polygonPointPullClick() { + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + override fun polygonStartDragClick() { + mFencesBean.startDrag = true + } + + override fun polygonEndDragClick() { + mFencesBean.startDrag = false + mFencesMapCommon.calculateDistanceAndGetPointLatLng(delayMillis = 0) + } + + /** + * 切换地图类型 + */ + private fun switchMapType() { + mViewBinding.apply { + isSatelliteMap = !isSatelliteMap + mFencesMapCommon.switchSatelliteAndNormalMapType() + if (mHaveFencesList.size > 0) mFencesMapViewModel.setImageFencesData( + mContext, mHaveFencesList, mFragment, isSatelliteMap = isSatelliteMap + ) + if (mFencesBean.fenceType == ConstantInt.SafeZone) { + safeZoneToMapTypeChangeColor() + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ivFencesZoneRefreshBtn -> { + when (mShowCenterLocationType) { +// ConstantInt.PetLocationType -> { +// mShowCenterLocationType = ConstantInt.UserLocationType +// } + + ConstantInt.UserLocationType -> { + mShowCenterLocationType = ConstantInt.OtherLocationType + } + + ConstantInt.OtherLocationType -> { + mShowCenterLocationType = ConstantInt.UserLocationType + } + } + mFencesBean.let { + mFencesMapCommon.switchShowLocation( + mShowCenterLocationType, it.latitudeCenter, it.longitudeCenter + ) + } + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivFencesZoneRefreshBtn, mShowCenterLocationType + ) + } + + tvFencesZoneShapeResetBtn -> recoverDefaultFencesView() + btnCancelFencesZone -> finish() + ivFencesZoneMapSwitchBtn -> switchMapType() + } + } + } + + override fun calculationDistance(distances: Array) { + if (mFencesBean.startDrag) return + LogUtil.e("选点计算距离返回${distances.toList()}") + when (mFencesBean.fenceShapeType) { + ConstantInt.CircleShapeType -> { + mFencesBean.radius = Utils.roundOffToInt(distances[0].toDouble()) +// val radiusStr = Util.metricConvertUnits( +// mContext, distances[0].toFloat(), ConstantInt.Type3, 2 +// ).toString() + mDistanceUnit + val radiusStr = mFencesBean.radius.toString() + mDistanceUnit + mViewBinding.fencesZoneCircleView.setShowDisDistance(radiusStr) + } + + ConstantInt.RectangleShapeType -> { + mFencesBean.longDistance = Utils.roundOffToInt(distances[0].toDouble()) + mFencesBean.shortDistance = Utils.roundOffToInt(distances[1].toDouble()) +// val longStr = Util.metricConvertUnits( +// mContext, distances[0].toFloat(), ConstantInt.Type3, 2 +// ).toString() + mDistanceUnit +// val shortStr = Util.metricConvertUnits( +// mContext, distances[0].toFloat(), ConstantInt.Type3, 2 +// ).toString() + mDistanceUnit + val longStr = mFencesBean.longDistance.toString() + mDistanceUnit + val shortStr = mFencesBean.shortDistance.toString() + mDistanceUnit + mViewBinding.fencesZoneRectView.setShowDisDistance(longStr, shortStr) + } + + else -> { + } + } + //经纬度转成地址显示 + mFencesMapCommon.getLatLonAddress(mGeoCoderViewModel, mFencesBean) + showLoading(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddNoGoZoneActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddNoGoZoneActivity.kt new file mode 100644 index 0000000..1a75277 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddNoGoZoneActivity.kt @@ -0,0 +1,42 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt + +/** + *Created by .yzq on 2024/1/24/024. + * @link + * @description: + */ +class AddNoGoZoneActivity : AddAndEditFencesZoneBaseActivity() { + + override fun initData() { + setTopBarTitle(R.string.txt_add_no_go_zone) + mFencesBean = FencesBean().apply { + fenceType = 2 + fenceNameType = 6 + } + super.initData() + mFencesMapCommon.setMapZoom(18f) + mViewBinding.apply { + noGoZoneInitData() + setFenceShape(ConstantInt.CircleShapeType) + } + } + + override fun liveDataObserve() { + super.liveDataObserve() + addLiveDataObserve() + } + + override fun onClick(v: View?) { + super.onClick(v) + mViewBinding.apply { + when(v!!){ + btnSaveFencesZone -> saveFences(mFencesBean) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddSafeZoneActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddSafeZoneActivity.kt new file mode 100644 index 0000000..e251e2e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/AddSafeZoneActivity.kt @@ -0,0 +1,39 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt + +/** + *Created by .yzq on 2024/1/24/024. + * @link + * @description: + */ +class AddSafeZoneActivity : AddAndEditFencesZoneBaseActivity() { + + override fun initData() { + setTopBarTitle(R.string.txt_add_safe_zone) + mFencesBean = FencesBean() + super.initData() + mFencesMapCommon.setMapZoom(18f) + mViewBinding.apply { + setFenceShape(ConstantInt.CircleShapeType) + safeZoneInitData() + } + } + + override fun liveDataObserve() { + super.liveDataObserve() + addLiveDataObserve() + } + + override fun onClick(v: View?) { + super.onClick(v) + mViewBinding.apply { + when(v!!){ + btnSaveFencesZone -> saveFences(mFencesBean) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/EditNoGoZoneActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/EditNoGoZoneActivity.kt new file mode 100644 index 0000000..e80794d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/EditNoGoZoneActivity.kt @@ -0,0 +1,72 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class EditNoGoZoneActivity : AddAndEditFencesZoneBaseActivity() { + override fun initData() { + setTopBarTitle(R.string.txt_edit_no_go_zone) + + isEditFences = true + val editFences = + Util.getParcelableAdaptive(intent, ConstantString.CurrentFence, FencesBean::class.java) + if (null == editFences) finish() + else mFencesBean = editFences + + super.initData() + + mFencesBean.apply { + mFencesMapCommon.restoreEditFencesSetMapZoom(mContext, this) + //保存反地理信息 ,下次直接使用 + mGeoCoderViewModel.mAddress = address + mGeoCoderViewModel.mLat = latitudeCenter + mGeoCoderViewModel.mLon = longitudeCenter + mViewBinding.ilFencesZoneNameInput.etInputContent.setText(name) + } + + mRightImageButton = addRightImageButton(R.drawable.icon_gray_delete_svg) + noGoZoneInitData(true) + } + + override fun liveDataObserve() { + super.liveDataObserve() + editLiveDataObserve() + } + + override fun mapLoadOk() { + mFencesList?.apply { + nogoFenceList?.let { + for (fences in it) { + if (mFencesBean.fenceId == fences.fenceId) { + it.remove(fences) + break + } + } + } + drawFences() + } + + mFencesBean.apply { + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mFencesZoneShapeAdapter, mFencesZoneShapeList, fenceShapeType - 1 + ) + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mSelectTypeShadeAdapter, mFencesZoneTypeList, fenceNameType - 6 + ) + } + } + + override fun onClick(v: View?) { + super.onClick(v) + mViewBinding.apply { + when (v!!) { + btnSaveFencesZone -> updateFences(mFencesBean) + mRightImageButton -> deleteFences(mFencesBean) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/EditSafeZoneActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/EditSafeZoneActivity.kt new file mode 100644 index 0000000..19e5762 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/EditSafeZoneActivity.kt @@ -0,0 +1,79 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +/** + *Created by .yzq on 2024/1/24/024. + * @link + * @description: + */ +class EditSafeZoneActivity : AddAndEditFencesZoneBaseActivity() { + + override fun initData() { + setTopBarTitle(R.string.txt_edit_safe_zone) + + isEditFences = true + val editFences = + Util.getParcelableAdaptive(intent, ConstantString.CurrentFence, FencesBean::class.java) + if (null == editFences) finish() + else mFencesBean = editFences + + super.initData() + + mFencesBean.apply { + mFencesMapCommon.restoreEditFencesSetMapZoom(mContext,this) + //保存反地理信息 ,下次直接使用 + mGeoCoderViewModel.mAddress = address + mGeoCoderViewModel.mLat = latitudeCenter + mGeoCoderViewModel.mLon = longitudeCenter + mViewBinding.ilFencesZoneNameInput.etInputContent.setText(name) + } + + mRightImageButton = addRightImageButton(R.drawable.icon_gray_delete_svg) + safeZoneInitData(true) + } + + override fun liveDataObserve() { + super.liveDataObserve() + editLiveDataObserve() + } + + override fun mapLoadOk() { + mFencesList?.apply { + normalFenceList?.let { + for (fences in it) { + if (mFencesBean.fenceId == fences.fenceId) { + it.remove(fences) + break + } + } + } + + drawFences() + } + + mFencesBean.apply { + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mFencesZoneShapeAdapter, mFencesZoneShapeList, fenceShapeType - 1 + ) + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mSelectTypeShadeAdapter, mFencesZoneTypeList, fenceNameType - 1 + ) + } + } + + override fun onClick(v: View?) { + super.onClick(v) + mViewBinding.apply { + when (v!!) { + btnSaveFencesZone -> updateFences(mFencesBean) + mRightImageButton -> deleteFences(mFencesBean) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/PreviewFencesZoneActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/PreviewFencesZoneActivity.kt new file mode 100644 index 0000000..552bcd3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/PreviewFencesZoneActivity.kt @@ -0,0 +1,355 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.content.Intent +import android.view.Gravity +import android.view.View +import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.GetFencesBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityPreviewFencesZoneBinding +import com.abbidot.tracker.ui.common.map.PreviewFencesZoneCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FencesManageViewModel +import com.abbidot.tracker.vm.FencesMapViewModel +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo + +/** + * 预览查看围栏 + */ +class PreviewFencesZoneActivity : + BaseActivity(ActivityPreviewFencesZoneBinding::inflate) { + + private val mFencesMapViewModel: FencesMapViewModel by viewModels() + private val mFencesManageViewModel: FencesManageViewModel by viewModels() + + private var mCurrentFences: FencesBean? = null + private var mGetFencesBean: GetFencesBean? = null + private var mPetBean: PetBean? = null + private var mCurrentDeviceId = "" + + private lateinit var mFragment: Fragment + private lateinit var mPreviewFencesMapCommon: PreviewFencesZoneCommon + private var mShowCenterLocationType = ConstantInt.OtherLocationType + + //是否是当前页面删除,放在重复收到本页面删除通知 + private var isCurrentActivityDelete = false + + //是否是卫星地图 + private var isSatelliteMap = false + private lateinit var mFencesList: MutableList + + override fun getTopBar() = mViewBinding.previewFencesZoneTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) { + mRightImageButton = addRightImageButton(R.drawable.icon_gray_delete_svg) + } + + val mapTypeSp = Util.getMapTypeSp() + isSatelliteMap = mapTypeSp == ConstantInt.Type1 + + mFencesList = mutableListOf() + + intent?.apply { + extras?.let { + mCurrentFences = + Util.getParcelableAdaptive(intent, ConstantString.Fence, FencesBean::class.java) + mPetBean = + Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + ?.apply { + mCurrentDeviceId = deviceId + mCurrentFences?.deviceServiceId = deviceServiceId + mCurrentFences?.deviceId = deviceId + } + } + } + + mPreviewFencesMapCommon = PreviewFencesZoneCommon() + mFragment = mPreviewFencesMapCommon.getMapFragment( + mContext, + ) { + mCurrentFences?.apply { + setCurrentFencesInfo(this) + } + mFencesManageViewModel.getFenceList( + this@PreviewFencesZoneActivity, mCurrentDeviceId + ) + } + supportFragmentManager.commit { + add(R.id.fc_preview_fences_map_fragment, mFragment) + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + mCurrentFences?.apply { + deviceId = mCurrentDeviceId + } + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivPreviewFencesRefreshBtn, mShowCenterLocationType + ) + val duration = 2500L + YoYo.with(Techniques.FadeIn).duration(duration).onEnd { + ivPreviewFencesMapSwitchBtn.visibility = View.VISIBLE + ivPreviewFencesRefreshBtn.visibility = View.VISIBLE + }.playOn(rlPreviewFencesInfoLayout) +// ViewUtil.instance.viewShowFadeInAnimation(rlPreviewFencesInfoLayout, duration) + + setOnClickListenerViews( + btnPreviewFencesEdit, + ivPreviewFencesMapSwitchBtn, + ivPreviewFencesRefreshBtn, + mRightImageButton, + cbPreviewFencesSwitch + ) + + if (share != ConstantInt.NoShare) { + btnPreviewFencesEdit.visibility = View.GONE + btnPreviewFencesActive.visibility = View.GONE + cbPreviewFencesSwitch.visibility = View.GONE + tvPreviewFencesActiveTxt.visibility = View.GONE + } + } + } + + override fun liveDataObserve() { + //获取所有围栏返回 + mFencesManageViewModel.mFencesLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mGetFencesBean = it.getOrNull() + mGetFencesBean?.apply { + setFencesData(this) + } + } + }) + } + + mFencesManageViewModel.mUpdateFenceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshFences) + mViewBinding.cbPreviewFencesSwitch.isChecked = + !mViewBinding.cbPreviewFencesSwitch.isChecked + showToast(R.string.txt_change_successful, gravity = Gravity.CENTER) + mCurrentFences?.apply { + isOn = + if (mViewBinding.cbPreviewFencesSwitch.isChecked) ConstantInt.Open else ConstantInt.Close + mFencesManageViewModel.getFenceList( + this@PreviewFencesZoneActivity, mCurrentDeviceId + ) + } + } + }) + } + + mFencesManageViewModel.mDeleteFenceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + isCurrentActivityDelete = true + XEventBus.post(EventName.DeleteFences) + showToast(R.string.txt_delete_success, true, gravity = Gravity.CENTER) + } + }) + } + + //接收更新围栏返回通知 + XEventBus.observe(this, EventName.RefreshFences) { + mFencesManageViewModel.getFenceList( + this@PreviewFencesZoneActivity, mCurrentDeviceId + ) + } + + //接收删除围栏返回通知 + XEventBus.observe(this, EventName.DeleteFences) { + if (!isCurrentActivityDelete) finish() + } + } + + /** + * 设置当前围栏的信息 + */ + private fun setCurrentFencesInfo(fencesBean: FencesBean) { + mViewBinding.apply { + val fenceNameTypeImage = + ViewUtil.instance.getFenceNameImageType(fencesBean.fenceNameType) + ilPreviewFencesTypeImage.ivSelectTypeImage.setImageResource(fenceNameTypeImage) + tvPreviewFencesName.text = fencesBean.name + tvPreviewFencesLocation.text = fencesBean.address + cbPreviewFencesSwitch.isChecked = fencesBean.isOn == ConstantInt.Open + mPreviewFencesMapCommon.restorePreviewFencesSetMapZoom(mContext, fencesBean) + + mShowCenterLocationType = ConstantInt.OtherLocationType + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivPreviewFencesRefreshBtn, mShowCenterLocationType + ) + mPreviewFencesMapCommon.switchShowLocation( + mShowCenterLocationType, fencesBean.latitudeCenter, fencesBean.longitudeCenter + ) + } + } + + private fun setFencesData(getFencesBean: GetFencesBean) { + getFencesBean.apply { + mFencesList.clear() + normalFenceList?.apply { + for (fencesBean in this) { + if (fencesBean.fenceId == mCurrentFences?.fenceId) { + mCurrentFences = fencesBean + break + } + } + mFencesList.addAll(this.toList()) + } + nogoFenceList?.apply { + for (fencesBean in this) { + if (fencesBean.fenceId == mCurrentFences?.fenceId) { + mCurrentFences = fencesBean + break + } + } + mFencesList.addAll(this.toList()) + } + + var currentFencesId = "" + mCurrentFences?.apply { + currentFencesId = fenceId + setCurrentFencesInfo(this) + } + mFencesMapViewModel.setImageFencesData( + mContext, mFencesList, mFragment, currentFencesId, isSatelliteMap + ) + + mPetBean?.let { + mCurrentFences?.deviceServiceId = it.deviceServiceId + mCurrentFences?.deviceId = it.deviceId + mPreviewFencesMapCommon.setPetCurrentLocation( + it.imgurl, it.petType, latitude, longitude + ) + } + } + } + + /** + * 修改开关状态 + */ + private fun changeOnAndOff(fencesBean: FencesBean) { + mViewBinding.cbPreviewFencesSwitch.isChecked = !mViewBinding.cbPreviewFencesSwitch.isChecked + fencesBean.apply { + isOn = if (isOn == ConstantInt.Open) { + ConstantInt.Close + } else { + ConstantInt.Open + } + when (fenceShapeType) { + ConstantInt.CircleShapeType -> { + mFencesManageViewModel.updateCircleFence( + this@PreviewFencesZoneActivity, this + ) + } + + ConstantInt.RectangleShapeType -> { + mFencesManageViewModel.updateRectFence( + this@PreviewFencesZoneActivity, this + ) + } + + else -> { + mFencesManageViewModel.updatePolygonFence( + this@PreviewFencesZoneActivity, this + ) + } + } + } + + } + + + /** + * 删除围栏 + */ + private fun deleteFences() { + mCurrentFences?.apply { + mFencesManageViewModel.deleteFence( + this@PreviewFencesZoneActivity, fenceId, deviceServiceId + ) + } + } + + private fun goEditFences() { + Util.checkLocationPermissionsGpsEnabled(mContext, { + mCurrentFences?.apply { + val intent = if (fenceType == ConstantInt.SafeZone) Intent( + mContext, EditSafeZoneActivity::class.java + ) + else Intent(mContext, EditNoGoZoneActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.Fence, mGetFencesBean) + intent.putExtra(ConstantString.CurrentFence, mCurrentFences) + startActivity(intent) + } + }) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPreviewFencesEdit -> goEditFences() + + mRightImageButton -> deleteFences() + cbPreviewFencesSwitch -> mCurrentFences?.apply { changeOnAndOff(this) } + + ivPreviewFencesMapSwitchBtn -> { + isSatelliteMap = !isSatelliteMap + mPreviewFencesMapCommon.switchSatelliteAndNormalMapType() + //切换卫星地图,围栏换颜色 + mCurrentFences?.apply { + mFencesMapViewModel.setImageFencesData( + mContext, mFencesList, mFragment, fenceId, isSatelliteMap + ) + } + } + + ivPreviewFencesRefreshBtn -> { + when (mShowCenterLocationType) { + ConstantInt.PetLocationType -> { + mShowCenterLocationType = ConstantInt.UserLocationType + } + + ConstantInt.UserLocationType -> { + mShowCenterLocationType = ConstantInt.OtherLocationType + } + + ConstantInt.OtherLocationType -> { + mShowCenterLocationType = ConstantInt.PetLocationType + } + } + mCurrentFences?.apply { + mPreviewFencesMapCommon.switchShowLocation( + mShowCenterLocationType, latitudeCenter, longitudeCenter + ) + } + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivPreviewFencesRefreshBtn, mShowCenterLocationType + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/VirtualFencesActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/VirtualFencesActivity.kt new file mode 100644 index 0000000..c112c29 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/fences/VirtualFencesActivity.kt @@ -0,0 +1,252 @@ +package com.abbidot.tracker.ui.activity.device.fences + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.FencesZoneAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.GetFencesBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityVirtualFencesBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FencesManageViewModel +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + * 虚拟围栏列表 + */ +class VirtualFencesActivity : + BaseActivity(ActivityVirtualFencesBinding::inflate) { + + private val mFencesManageViewModel: FencesManageViewModel by viewModels() + + private lateinit var mFencesAdapter: FencesZoneAdapter + private lateinit var mFencesList: MutableList + + private lateinit var mFencesNoGoAdapter: FencesZoneAdapter + private lateinit var mFencesNoGoList: MutableList + private var mPetBean: PetBean? = null + private var mGetFencesBean: GetFencesBean? = null + + override fun getTopBar() = mViewBinding.virtualFencesTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_my_pet_fence) + setLeftBackImage(R.drawable.icon_white_back_svg) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + + mFencesList = mutableListOf() + mFencesNoGoList = mutableListOf() + + mViewBinding.apply { + + mFencesAdapter = FencesZoneAdapter(mContext, mFencesList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + goPreviewFencesZone(mFencesList[pos]) + } + }) + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + if (mFencesList.size + mFencesNoGoList.size >= 5) { + return + } + goAddSafeZone() + } + }) + } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvFencesSafeZone, mFencesAdapter, top = QMUIDisplayHelper.dpToPx(18) + ) + mFencesNoGoAdapter = FencesZoneAdapter(mContext, mFencesNoGoList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + goPreviewFencesZone(mFencesNoGoList[pos]) + } + }) + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + if (mFencesList.size + mFencesNoGoList.size >= 5) { + return + } + goAddNoGoZone() + } + }) + } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvFencesNoGoZone, mFencesNoGoAdapter, top = QMUIDisplayHelper.dpToPx(18) + ) + + setOnClickListenerViews(btnAddFencesNoGoZone, btnAddFencesSafeZone) + + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) { + btnAddFencesNoGoZone.visibility = View.INVISIBLE + btnAddFencesSafeZone.visibility = View.INVISIBLE + } + + val fencesCount = String.format(getString(R.string.txt_fences_set), "0/5") + tvVirtualFencesCount.text = fencesCount + } + + mPetBean?.apply { + mFencesManageViewModel.getFenceList(this@VirtualFencesActivity, deviceId) + } + } + + override fun liveDataObserve() { + //接收更新围栏返回通知 + XEventBus.observe(this, EventName.RefreshFences) { + mPetBean?.apply { + mFencesManageViewModel.getFenceList(this@VirtualFencesActivity, deviceId) + } + } + //接收删除围栏返回通知 + XEventBus.observe(this, EventName.DeleteFences) { + mPetBean?.apply { + mFencesManageViewModel.getFenceList(this@VirtualFencesActivity, deviceId) + } + } + //获取所有围栏返回 + mFencesManageViewModel.mFencesLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mGetFencesBean = it.getOrNull() + mGetFencesBean?.apply { + setFencesData(this) + } + } + }) + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) { + mFencesAdapter.showNoDataAddButton(false) + mFencesNoGoAdapter.showNoDataAddButton(false) + } + } + } + + private fun setFencesData(getFencesBean: GetFencesBean) { + mFencesList.clear() + mFencesNoGoList.clear() + mFencesAdapter.setData(mFencesList, true) + mFencesNoGoAdapter.setData(mFencesNoGoList, true) +// mSelectLabelsAdapter.notifyItemRangeChanged(0, mSelectLabelList.size) + getFencesBean.normalFenceList?.apply { + mFencesList.addAll(this.toList()) + mFencesAdapter.setData(mFencesList, true) + } + getFencesBean.nogoFenceList?.apply { + mFencesNoGoList.addAll(this.toList()) + mFencesNoGoAdapter.setData(mFencesNoGoList, true) + } + + val fencesCount = mFencesList.size + mFencesNoGoList.size + + mViewBinding.apply { + svVirtualFencesLayout.visibility = View.VISIBLE + + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) { + if (mFencesList.size == 0) btnAddFencesSafeZone.visibility = View.INVISIBLE + else btnAddFencesSafeZone.visibility = View.VISIBLE + + if (mFencesNoGoList.size == 0) btnAddFencesNoGoZone.visibility = View.INVISIBLE + else btnAddFencesNoGoZone.visibility = View.VISIBLE + + //围栏有一种超过5个,另外一种不能创建 + if (mFencesList.size >= 5) { + mFencesNoGoAdapter.showNoDataAddButton(false) + btnAddFencesNoGoZone.visibility = View.INVISIBLE + } else { + mFencesNoGoAdapter.showNoDataAddButton(true) + btnAddFencesNoGoZone.visibility = View.VISIBLE + } + if (mFencesNoGoList.size >= 5) { + mFencesAdapter.showNoDataAddButton(false) + btnAddFencesSafeZone.visibility = View.INVISIBLE + } else { + mFencesAdapter.showNoDataAddButton(true) + btnAddFencesSafeZone.visibility = View.VISIBLE + } + + if (fencesCount >= 5) { + btnAddFencesNoGoZone.visibility = View.INVISIBLE + btnAddFencesSafeZone.visibility = View.INVISIBLE + } else { + btnAddFencesNoGoZone.visibility = View.VISIBLE + btnAddFencesSafeZone.visibility = View.VISIBLE + } + } + + val countString = String.format(getString(R.string.txt_fences_set), "$fencesCount/5") + tvVirtualFencesCount.text = countString + } + } + + private fun goPreviewFencesZone(fencesBean: FencesBean) { + Util.checkLocationPermissionsGpsEnabled(mContext, { + mPetBean?.apply { + val intent = Intent(mContext, PreviewFencesZoneActivity::class.java) + intent.putExtra(ConstantString.Fence, fencesBean) + intent.putExtra(ConstantString.Pet, mPetBean) + startActivity(intent) + } + }) + } + + private fun goAddSafeZone() { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) return + Util.checkLocationPermissionsGpsEnabled(mContext, { + val intent = Intent(mContext, AddSafeZoneActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.Fence, mGetFencesBean) + startActivity(intent) + }) + } + + private fun goAddNoGoZone() { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) return + Util.checkLocationPermissionsGpsEnabled(mContext, { + val intent = Intent(mContext, AddNoGoZoneActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.Fence, mGetFencesBean) + startActivity(intent) + }) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnAddFencesSafeZone -> { + if (mFencesList.size + mFencesNoGoList.size >= 5) { + showToast(R.string.txt_created_many_fences) + return + } + goAddSafeZone() + } + + btnAddFencesNoGoZone -> { + if (mFencesList.size + mFencesNoGoList.size >= 5) { + showToast(R.string.txt_created_many_fences) + return + } + goAddNoGoZone() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/LedLightActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/LedLightActivity.kt new file mode 100644 index 0000000..3d18324 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/LedLightActivity.kt @@ -0,0 +1,387 @@ +package com.abbidot.tracker.ui.activity.device.set + +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.view.Gravity +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.core.view.isGone +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.LedLightColorAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityLedLightBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.LedLightViewModel +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.TrackerInfoViewModel +import com.clj.fastble.BleManager +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo + + +/** + * LedLight颜色光源设置 + */ +class LedLightActivity : BaseActivity(ActivityLedLightBinding::inflate) { + + private val mLedLightViewModel: LedLightViewModel by viewModels() + private val mTrackerManageSetViewModel: TrackerInfoViewModel by viewModels() + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private var mPetBean: PetBean? = null + private var mMapDeviceBean: MapDeviceBean? = null + private var mBleTrackDeviceBean: BleTrackDeviceBean? = null + private lateinit var mLedLightColorAdapter: LedLightColorAdapter + private lateinit var mLedModeList: MutableList + + //防止多次按返回键执行 + private var isFinishSave = false + + override fun getTopBar() = mViewBinding.ilLedLightBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.tracker_manage_set_led) + setLeftBackImage(R.drawable.icon_white_back_svg) +// initStatus() + + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + + mLedModeList = mutableListOf( + getString(R.string.tracker_light_mode_sustained), + getString(R.string.tracker_light_mode_breathing), +// getString(R.string.tracker_light_mode_slow_flash), + getString(R.string.tracker_light_mode_flash) + ) + + ViewUtil.instance.showQMUICommonListItemView( + mContext, + mViewBinding.ledLightOpenAndClose, + null, + getString(R.string.tracker_manage_set_led) + ) + mViewBinding.ledLightOpenAndClose.switch.apply { + setOnCheckedChangeListener { _, isChecked -> + mMapDeviceBean?.let { + if (isChecked) { + it.ledSwitch = ConstantInt.Open + setBleLedMode(it.ledMode) + } else { + it.ledSwitch = ConstantInt.Close + setBleLedMode(0) + } + } + } + } + + val builder = ViewUtil.instance.showBottomSheetList(mContext, { dialog, _, position, _ -> + mMapDeviceBean?.apply { + dialog.dismiss() + ledMode = position + 1 + mViewBinding.ledLightMode.detailText = mLedModeList[position] + mViewBinding.ledLightOpenAndClose.switch.let { + if (it.isChecked) { + setBleLedMode(ledMode) + } else { + it.isChecked = true + } + } + } + }) + for (i in 0 until mLedModeList.size) builder.addItem(mLedModeList[i]) + + mViewBinding.ledLightMode.apply { + ViewUtil.instance.showQMUICommonListItemView(mContext, this, { + builder.build().show() + }, getString(R.string.txt_light_mode)) +// mMapDeviceBean?.apply { +// detailText = when { +// ledMode - 1 >= mLedModeList.size -> { +// mLedModeList[mLedModeList.size - 1] +// } +// +// ledMode == 0 -> { +// mLedModeList[0] +// } +// +// else -> mLedModeList[ledMode - 1] +// } +// } + } + + val colorListResId = mutableListOf( + R.color.white, + R.color.colormenu, + R.color.corner_color, + R.color.blue_light, + R.color.deep_blue, + R.color.red_blue, + R.color.yellow_color, + R.color.yellow_red_color + ) + mLedLightColorAdapter = LedLightColorAdapter(mContext, colorListResId) + mLedLightColorAdapter.setOnItemClickListener(object : + BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + setLedLightColor(ContextCompat.getColor(mContext, colorListResId[pos])) + } + }) + + + mViewBinding.apply { + ilLedLightIssueLayout.let { + it.tvIssueOneBigTitle.setText(R.string.txt_led_light_set) + it.tvIssueTwoSmallOneTitle.setText(R.string.txt_bluetooth_setup) + it.tvIssueTwoSmallOneTitle.setText(R.string.txt_bluetooth_setup) + it.tvIssueTwoSmallTitleOneContent.setText(R.string.txt_bluetooth_setup_dec) + it.tvIssueTwoSmallTwoTitle.setText(R.string.txt_network_setup) + it.tvIssueTwoSmallTitleTwoContent.setText(R.string.txt_network_setup_dec) + } + ViewUtil.instance.setRecyclerViewGridLayout( + mContext, + colorSelectRecyclerView, + mLedLightColorAdapter, + spanCount = 2, + bottom = AppUtils.dpToPx(9), + itemWidth = AppUtils.dpToPx(30), + offset = AppUtils.dpToPx(290) + ) + + selectColorPaintView.setOnColorSelectListener { + setLedLightColor(it) + } + + setOnClickListenerViews( + ledLightBluetoothTips.trbBleConnectState, + btnLedLightSetSave, + btnLedLightIssue, + ilLedLightIssueLayout.tvCloseIssueBtn + ) + } + + mPetBean?.apply { + //查找是否连接了蓝牙 + SRBleUtil.instance.getConnectMacDevice(macID)?.let { + updateConState(it) + } + mTrackerManageSetViewModel.getPetTrackerInfo(this@LedLightActivity, deviceId) + } + } + + override fun liveDataObserve() { + mTrackerManageSetViewModel.mTrackerInfoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + mMapDeviceBean = data + setDeviceData(data) + } + } + }) + } + mLedLightViewModel.mSetLedLightLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { +// XEventBus.post(EventName.UpdateData + "TrackerManageSetActivity") +// XEventBus.post(EventName.UpdateData + "TrackerManageFragment") + mPetBean?.apply { + SRBleUtil.instance.isConnectBleSendNotifyData(macID) + } + showToast(R.string.txt_change_successful, isFinish = true) + } + }) + } + + //接收开蓝牙操作,然后搜索连接设备 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + mViewBinding.ledLightBluetoothTips.trbBleConnectState.let { view -> + ViewUtil.instance.bleConActionState(this, view, conState) + } + } + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + mPetBean?.apply { + if (ble.mac == macID) { + updateConState(ble) + } + } + } + } + + private fun setDeviceData(mapDeviceBean: MapDeviceBean) { + mapDeviceBean.apply { + mViewBinding.ledLightOpenAndClose.switch.isChecked = ledSwitch == ConstantInt.Open + + setLedLightColor( + Color.rgb(ledRedValue, ledGreenValue, ledBlueValue), true + ) + if (ledMode == 0 || ledMode > mLedModeList.size) ledMode = 1 + mViewBinding.ledLightMode.detailText = mLedModeList[ledMode - 1] + } + } + + /** + * 更新设备连接状态 + */ + private fun updateConState(trackBleDevice: BleTrackDeviceBean) { + mBleTrackDeviceBean = trackBleDevice + mViewBinding.ledLightBluetoothTips.trbBleConnectState.let { view -> + val bgColor = if (trackBleDevice.conState == ConState.CONNECTED) { + view.text = getString(R.string.tracker_manage_set_ble_connect) + ContextCompat.getColor(mContext, R.color.cyan_color1) + } else { +// showToast(R.string.txt_ble_connect_fail) + view.text = getString(R.string.tracker_manage_set_ble_unconnect) + ContextCompat.getColor(mContext, R.color.red_color4) + } + view.setBackgroundColor(bgColor) + } + ViewUtil.instance.bleConStateToast(this, trackBleDevice.conState, Gravity.CENTER) + } + + private fun setBleLedMode(mode: Int) { + mBleTrackDeviceBean?.apply { + SRBleUtil.instance.setBleLedSwitch(mContext, bleDevice, mode) + } + } + + override fun leftBackOnClick() { + if (isFinishSave) return + //是否是蓝牙连接了,修改设备数据返回,同步服务器 + if (null != mBleTrackDeviceBean) { + //判断是否有蓝牙连接 + if (BleManager.getInstance().isConnected(mBleTrackDeviceBean!!.bleDevice)) { + finishAndSave() + } else { + super.leftBackOnClick() + } + } else { + super.leftBackOnClick() + } + } + + /** + * 保存设置数据 + */ + private fun finishAndSave() { + mMapDeviceBean?.apply { + isFinishSave = true + mLedLightViewModel.setLedLight(this@LedLightActivity, this) + } + } + + /** + * @param show 只显示,不作为命令处理 + */ + private fun setLedLightColor(color: Int, show: Boolean = false) { + val drawableColor = ColorDrawable(color) + mViewBinding.currentSelectColor.appHeadImage.setImageDrawable(drawableColor) +// mViewBinding.ledLightBarColorImage.setBackgroundColor(color) + + val red = color and 0xff0000 shr 16 + val green = color and 0x00ff00 shr 8 + val blue = color and 0x0000ff + + mMapDeviceBean?.apply { + ledRedValue = red + ledGreenValue = green + ledBlueValue = blue + } + +// mViewBinding.tvLedLightCurrentRgb.text = String.format("#%06X", -0x1 and color) + mViewBinding.tvLedLightCurrentRgb.text = String.format("#%06X", 0xffffff and color) + + if (!show) { + mBleTrackDeviceBean?.let { + //判断是否有蓝牙连接 + if (BleManager.getInstance().isConnected(it.bleDevice)) { + SRBleUtil.instance.writeData( + it.bleDevice, SRBleCmdUtil.instance.ledColor( + SRBleCmdUtil.CMD_WRITE, red, green, blue + ) + ) + } + } + } + } + + /** + * 断开连接设备 + */ + private fun operateDeviceTips(messageID: Int, type: Int = 0) { + ViewUtil.instance.showDialog(mContext, messageID, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + when (type) { + 0 -> { + mBleTrackDeviceBean?.let { + mConnectionDeviceViewModel.disconnect(it.bleDevice) + } + } + } + } + }, okTextResId = R.string.start_txt_yes) + } + + /** + * 手动连接/断开设备 + */ + private fun connectionBtn(btnString: String) { + mPetBean?.apply { + ViewUtil.instance.connectionBtn(mContext, btnString, macID) { + mConnectionDeviceViewModel.connectDeviceToMac(this@LedLightActivity, macID) + } + } + } + + /** + * 显示led问题 + */ + private fun showLedIssue() { + mViewBinding.apply { + if (ilLedLightIssueLayout.root.isGone) { + YoYo.with(Techniques.BounceInUp).duration(700).onStart { + ilLedLightIssueLayout.root.visibility = View.VISIBLE + }.playOn(ilLedLightIssueLayout.root) + } else { + YoYo.with(Techniques.SlideOutDown).duration(600).onEnd { + ilLedLightIssueLayout.root.visibility = View.GONE + }.playOn(ilLedLightIssueLayout.root) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v) { + //手动连接设备 + ledLightBluetoothTips.trbBleConnectState -> connectionBtn(ledLightBluetoothTips.trbBleConnectState.text.toString()) + btnLedLightSetSave -> finishAndSave() + btnLedLightIssue -> showLedIssue() + ilLedLightIssueLayout.tvCloseIssueBtn -> showLedIssue() + } + } + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.selectColorPaintView.recycle() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/LiveDurationSetActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/LiveDurationSetActivity.kt new file mode 100644 index 0000000..98da5f0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/LiveDurationSetActivity.kt @@ -0,0 +1,106 @@ +package com.abbidot.tracker.ui.activity.device.set + +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.DurationSetAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityLiveDurationSetBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.TrackerSetViewModel + +class LiveDurationSetActivity : + BaseActivity(ActivityLiveDurationSetBinding::inflate) { + + private val mTrackerSetViewModel: TrackerSetViewModel by viewModels() + + private lateinit var mDurationSetAdapter: DurationSetAdapter + + //选中的下标 + private var mSelectIndex = -1 + + private var mMapDeviceBean: MapDeviceBean? = null + + override fun getTopBar() = mViewBinding.ilLiveDurationSetTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_live_duration) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mMapDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.MapDeviceBean, MapDeviceBean::class.java + ) + } + + setListData() + + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvLiveDurationSetList, mDurationSetAdapter, top = AppUtils.dpToPx(10) + ) + } + } + + override fun liveDataObserve() { + mTrackerSetViewModel.mSetLiveTimeLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.txt_change_successful) + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mDurationSetAdapter, mDurationSetAdapter.getData(), mSelectIndex + ) + } + }) + } + } + + private fun setListData() { + val durationList = mutableListOf() + val timeList = mutableListOf(5, 10, 15, 30, 60) + for (time in timeList) { + DataBean().apply { + imageId = time + name = String.format(getString(R.string.txt_duration_min_unit), time) + durationList.add(this) + } + } + + mMapDeviceBean?.apply { + for (i in 0 until durationList.size) { + val data = durationList[i] + if (liveTime / 60 == data.imageId) { + mSelectIndex = i + data.selected = true + break + } + } + } + mDurationSetAdapter = DurationSetAdapter(this, durationList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + setLiveDuration(pos) + } + }) + } + } + + fun setLiveDuration(pos: Int) { + if (mSelectIndex == pos) return + mMapDeviceBean?.apply { + mSelectIndex = pos + val dataBean = mDurationSetAdapter.getData()[pos] + mTrackerSetViewModel.setLiveTime( + this@LiveDurationSetActivity, deviceId, dataBean.imageId + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/PowerOffActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/PowerOffActivity.kt new file mode 100644 index 0000000..a7be887 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/PowerOffActivity.kt @@ -0,0 +1,150 @@ +package com.abbidot.tracker.ui.activity.device.set + +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityPowerOffBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.clj.fastble.BleManager + +class PowerOffActivity : BaseActivity(ActivityPowerOffBinding::inflate) { + + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.ilPowerOffTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mViewBinding.apply { + setOnClickListenerViews(btnPowerOffYes,ilPowerOffBluetoothTips.trbBleConnectState) + } + + connectBle() + } + + override fun liveDataObserve() { + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + showLoading(false) + mViewBinding.ilPowerOffBluetoothTips.trbBleConnectState.let { view -> + val bgColor = if (ble.conState == ConState.CONNECTED) { + ViewUtil.instance.viewShowFadeInAnimation(mViewBinding.btnPowerOffYes) + view.text = getString(R.string.tracker_manage_set_ble_connect) + ContextCompat.getColor(mContext, R.color.cyan_color1) + } else { + ViewUtil.instance.viewHideFadeInAnimation( + mViewBinding.btnPowerOffYes, hideType = View.INVISIBLE + ) + view.text = getString(R.string.tracker_manage_set_ble_unconnect) + ContextCompat.getColor(mContext, R.color.red_color4) + } + view.setBackgroundColor(bgColor) + } + } + + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + ViewUtil.instance.viewHideFadeInAnimation( + mViewBinding.btnPowerOffYes, hideType = View.INVISIBLE + ) + mViewBinding.ilPowerOffBluetoothTips.trbBleConnectState.let { view -> + ViewUtil.instance.bleConActionState(this, view, conState) + } + } + + //接收发送指令后设备返回的数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mPetBean?.let { + if (it.macID == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + } + + /** + * 解析数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 4 && data1 == 1 && data2 == 0) { + showToast(R.string.txt_done, true) + } + } + } + + private fun connectBle() { + mPetBean?.apply { + //是否连接了蓝牙 + if (BleManager.getInstance().isConnected(macID)) { + ViewUtil.instance.viewShowFadeInAnimation(mViewBinding.btnPowerOffYes) + mViewBinding.ilPowerOffBluetoothTips.trbBleConnectState.let { + it.setBackgroundColor( + ContextCompat.getColor(mContext, R.color.cyan_color1) + ) + it.text = getString(R.string.tracker_manage_set_ble_connect) + } + } else { + mConnectionDeviceViewModel.connectDeviceToMac( + this@PowerOffActivity, macID + ) + } + } + } + + private fun bleTurnOff() { + mPetBean?.apply { + SRBleUtil.instance.getConnectMacDevice(macID)?.let { + setButtonEnabled(mViewBinding.btnPowerOffYes) + val cmd = SRBleCmdUtil.instance.shutdownDevice() + SRBleUtil.instance.writeData(it.bleDevice, cmd) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPowerOffYes -> bleTurnOff() + + ilPowerOffBluetoothTips.trbBleConnectState -> { + mPetBean?.apply { + ViewUtil.instance.connectionBtn( + mContext, + ilPowerOffBluetoothTips.trbBleConnectState.text.toString(), + macID + ) { + connectBle() + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/TrackingDurationSetActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/TrackingDurationSetActivity.kt new file mode 100644 index 0000000..c6155ad --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/set/TrackingDurationSetActivity.kt @@ -0,0 +1,110 @@ +package com.abbidot.tracker.ui.activity.device.set + +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.DurationSetAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityTrackingDurationSetBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.TrackerSetViewModel + +class TrackingDurationSetActivity : + BaseActivity(ActivityTrackingDurationSetBinding::inflate) { + + private val mTrackerSetViewModel: TrackerSetViewModel by viewModels() + + private lateinit var mDurationSetAdapter: DurationSetAdapter + + //选中的下标 + private var mSelectIndex = -1 + + private var mMapDeviceBean: MapDeviceBean? = null + + override fun getTopBar() = mViewBinding.ilTrackingDurationSetTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.tracker_manage_set_duration) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mMapDeviceBean = Util.getParcelableAdaptive( + intent, ConstantString.MapDeviceBean, MapDeviceBean::class.java + ) + } + + setListData() + + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvTrackingDurationSetList, mDurationSetAdapter, top = AppUtils.dpToPx(10) + ) + } + } + + override fun liveDataObserve() { + mTrackerSetViewModel.mReportIntervalLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.txt_change_successful) + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mDurationSetAdapter, mDurationSetAdapter.getData(), mSelectIndex + ) + mMapDeviceBean?.apply { + SRBleUtil.instance.isConnectBleSendNotifyData(deviceMacId) + } + } + }) + } + } + + private fun setListData() { + val durationList = mutableListOf() + val timeList = mutableListOf(1, 3, 5, 10, 15) + for (time in timeList) { + DataBean().apply { + imageId = time + name = String.format(getString(R.string.txt_duration_min_unit), time) + durationList.add(this) + } + } + mMapDeviceBean?.apply { + for (i in 0 until durationList.size) { + val data = durationList[i] + if (gnssInterval / 60 == data.imageId) { + mSelectIndex = i + data.selected = true + break + } + } + } + mDurationSetAdapter = DurationSetAdapter(this, durationList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + setTrackingDuration(pos) + } + }) + } + } + + fun setTrackingDuration(pos: Int) { + if (mSelectIndex == pos) return + mMapDeviceBean?.apply { + mSelectIndex = pos + val dataBean = mDurationSetAdapter.getData()[pos] + mTrackerSetViewModel.reportInterval( + this@TrackingDurationSetActivity, deviceServerId, dataBean.imageId + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPasswordActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPasswordActivity.kt new file mode 100644 index 0000000..3b6df32 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPasswordActivity.kt @@ -0,0 +1,309 @@ +package com.abbidot.tracker.ui.activity.device.wifi + +import android.content.Intent +import android.text.InputFilter +import android.view.Gravity +import android.view.View +import android.widget.RelativeLayout +import androidx.activity.viewModels +import androidx.core.view.marginBottom +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.bean.WiFiBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityAddWifiPasswordBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.CountDownTimerViewModel +import com.clj.fastble.BleManager +import com.clj.fastble.utils.HexUtil +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class AddWifiPasswordActivity : + BaseActivity(ActivityAddWifiPasswordBinding::inflate) { + + private var isShowPassword = false + private var mConnectingPointNum = 1 + + private var mWiFiBean: WiFiBean? = null + private var mBleTrackDeviceBean: BleTrackDeviceBean? = null + private var mPetBean: PetBean? = null + + private var mDecWiFiAddressData: HistoryDataBean? = null + private var isFirstBind = false + private var mPetCurrentLat = 0.0 + private var mPetCurrentLon = 0.0 + + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + + override fun getTopBar() = mViewBinding.ilAddWifiPwdTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mWiFiBean = + Util.getParcelableAdaptive(intent, ConstantString.WiFi, WiFiBean::class.java) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + + mDecWiFiAddressData = Util.getParcelableAdaptive( + intent, ConstantString.HistoryDataBean, HistoryDataBean::class.java + ) + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetCurrentLat = getDouble(ConstantString.Latitude, 0.0) + mPetCurrentLon = getDouble(ConstantString.Longitude, 0.0) + } + + mViewBinding.apply { + + mWiFiBean?.let { + tvAddWifiPwdName.text = it.name + } + + ilAddWifiPwdPassword.let { + it.tvInputNameTitle.text = getString(R.string.login_txt_password) + it.etInputContent.setHint(R.string.login_txt_password) + ViewUtil.instance.setShowAndHidePassword( + it.etInputContent, it.ivInputRepresentImage + ) + val filters = arrayOf(InputFilter.LengthFilter(32)) + it.etInputContent.filters = filters + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvAddWifiPwdErrorTip, it.etInputContent + ) + it.etInputContent.addTextChangedListener { edit -> + edit?.let { e -> + btnAddWifiPwdConnect.isEnabled = e.length >= 8 + } + } + } + + lavAddWifiPwdAnim.setAnimation("lottie/dfu_loading.json") + + setOnClickListenerViews( + ilAddWifiPwdPassword.ivInputRepresentImage, btnAddWifiPwdConnect, btnAddWifiPwdSkip + ) + } + + setListenKeyboardChange(1) + + mPetBean?.apply { + mBleTrackDeviceBean = SRBleUtil.instance.getConnectMacDevice(macID) + } + + mWiFiBean?.let { + if (it.auth_type == ConstantInt.Type0) { + mViewBinding.ilAddWifiPwdPassword.root.visibility = View.GONE + noPasswordConnect() + } + } + } + + override fun liveDataObserve() { + mCountDownTimerViewModel.apply { + mCountDowningLiveData.observe(this@AddWifiPasswordActivity) { + var connectingPoint = "" + when (mConnectingPointNum) { + 1 -> connectingPoint = "." + 2 -> connectingPoint = ".." + 3 -> connectingPoint = "..." + } + mConnectingPointNum++ + val str = getString(R.string.txt_connect_ing) + connectingPoint + mViewBinding.btnAddWifiPwdConnect.text = str + } + mCountDownEndLiveData.observe(this@AddWifiPasswordActivity) { + mConnectingPointNum = 1 + mCountDownTimerViewModel.startCountDown(3) + } + } + + //接收蓝牙返回数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mPetBean?.apply { + if (macID == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + } + + /** + * * 解析蓝牙返回的数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 33 && data1 == 1) { + sendPasswordEnd() + when (data2) { + 0 -> { + mViewBinding.btnAddWifiPwdConnect.isEnabled = false + showToast(R.string.txt_successful, gravity = Gravity.CENTER) + mViewBinding.root.postDelayed({ + goEditWifiActivity() + }, 1000) + } + + 6 -> showToast(R.string.txt_fail) + else -> ViewUtil.instance.setInputViewErrorShow( + mContext, + mViewBinding.ilAddWifiPwdPassword.rlEtInputLayout, + mViewBinding.tvAddWifiPwdErrorTip + ) + } + } + } + } + + override fun showSoftKeyboardCallback() { + super.showSoftKeyboardCallback() + mViewBinding.btnAddWifiPwdSkip.apply { + val setBottomMargin = AppUtils.dpToPx(320) + if (setBottomMargin != marginBottom) { + val params = layoutParams as RelativeLayout.LayoutParams + params.bottomMargin = setBottomMargin + layoutParams = params + } + } + } + + override fun hideSoftKeyboardCallback() { + super.hideSoftKeyboardCallback() + mViewBinding.btnAddWifiPwdSkip.apply { + val setBottomMargin = AppUtils.dpToPx(28) + if (setBottomMargin != marginBottom) { + val params = layoutParams as RelativeLayout.LayoutParams + params.bottomMargin = setBottomMargin + layoutParams = params + } + } + } + + private fun sendPasswordStart() { + showLoading(true, R.string.txt_please_wait) + mViewBinding.apply { + btnAddWifiPwdConnect.isEnabled = false + lavAddWifiPwdAnim.playAnimation() + lavAddWifiPwdAnim.visibility = View.VISIBLE + } + mCountDownTimerViewModel.startCountDown(3) + } + + private fun sendPasswordEnd() { + showLoading(false) + mViewBinding.btnAddWifiPwdConnect.apply { + isEnabled = true + setText(R.string.txt_connect) + } + mViewBinding.lavAddWifiPwdAnim.apply { + cancelAnimation() + visibility = View.GONE + } + mCountDownTimerViewModel.stopCountDown() + } + + private fun noPasswordConnect() { + mBleTrackDeviceBean?.let { ble -> + mWiFiBean?.let { + sendPasswordStart() + val cmdByteArray = SRBleCmdUtil.instance.setConnectWiFi(it.mac, "", it.auth_type) + SRBleUtil.instance.writeData(ble.bleDevice, cmdByteArray) + } + } + } + + private fun savePassword() { + val wifiPassword = mViewBinding.ilAddWifiPwdPassword.etInputContent.text.toString() + + mBleTrackDeviceBean?.apply { + //判断是否有蓝牙连接 + if (BleManager.getInstance().isConnected(bleDevice)) { + mWiFiBean?.let { + lifecycleScope.launch { + sendPasswordStart() + val cmdByteArray = + SRBleCmdUtil.instance.setConnectWiFi(it.mac, wifiPassword, it.auth_type) + LogUtil.e( + "长度=${cmdByteArray.size},要发送的包:" + HexUtil.formatHexString( + cmdByteArray, true + ) + ) + //发送第一个包 + if (cmdByteArray.size > 20) { + SRBleUtil.instance.writeData(bleDevice, cmdByteArray.sliceArray(0..19)) + } else SRBleUtil.instance.writeData(bleDevice, cmdByteArray) + for (i in 1 until SRBleCmdUtil.instance.byteToInt(cmdByteArray[2])) { + var cmdPackage = cmdByteArray.sliceArray(0..2) + //当前的包序号 + cmdPackage = cmdPackage.plus((i + 1).toByte()) + val startIndex = 20 + 16 * (i - 1) + cmdPackage = if (cmdByteArray.size - 20 - 16 * (i - 1) > 16) { + cmdPackage.plus(cmdByteArray.sliceArray(startIndex until 20 + 16 * i)) + } else cmdPackage.plus(cmdByteArray.sliceArray(startIndex until cmdByteArray.size)) + + delay(500) + SRBleUtil.instance.writeData(bleDevice, cmdPackage) + } + } + } + } else showToast(R.string.tracker_manage_set_ble_unconnect, gravity = Gravity.CENTER) + } + } + + private fun goEditWifiActivity() { + Util.checkLocationPermissionsGpsEnabled(mContext, { + val intent = Intent(mContext, EditWifiPowerZoneActivity::class.java) + intent.putExtra(ConstantString.WiFi, mWiFiBean) + mDecWiFiAddressData?.let { + intent.putExtra(ConstantString.HistoryDataBean, it) + } + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.Latitude, mPetCurrentLat) + intent.putExtra(ConstantString.Longitude, mPetCurrentLon) + startActivityFinish(intent) + }) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilAddWifiPwdPassword.ivInputRepresentImage -> { + isShowPassword = !isShowPassword + ViewUtil.instance.showAndHidePasswordClick( + ilAddWifiPwdPassword.etInputContent, + ilAddWifiPwdPassword.ivInputRepresentImage, + isShowPassword + ) + } + + btnAddWifiPwdSkip -> goEditWifiActivity() + + btnAddWifiPwdConnect -> mWiFiBean?.let { + if (it.auth_type == ConstantInt.Type0) noPasswordConnect() + else savePassword() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone1Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone1Activity.kt new file mode 100644 index 0000000..9784596 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone1Activity.kt @@ -0,0 +1,235 @@ +package com.abbidot.tracker.ui.activity.device.wifi + +import android.animation.ValueAnimator +import android.content.Intent +import android.view.Gravity +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.activity.viewModels +import androidx.core.animation.doOnEnd +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityAddWifiPowerZone1Binding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.clj.fastble.BleManager + +class AddWifiPowerZone1Activity : + BaseActivity(ActivityAddWifiPowerZone1Binding::inflate) { + + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private val mAnimatorDuration = 2000L + private var mHaveSignal = true + + private var mPetBean: PetBean? = null + private var isFirstBind = false + private var mPetCurrentLat = 0.0 + private var mPetCurrentLon = 0.0 + + override fun getTopBar() = mViewBinding.ilAddWifiZone1TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + mPetCurrentLat = getDouble(ConstantString.Latitude, 0.0) + mPetCurrentLon = getDouble(ConstantString.Longitude, 0.0) + } + + if (isFirstBind) setLeftBackImage(0) + else setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + mPetBean?.let { + ViewUtil.instance.setPetTypeHead( + ivAddWifiZone1PetHead.appHeadImage, it.imgurl, it.petType + ) + } + + tvAddWifiZone1LaterBtn.visibility = if (isFirstBind) View.VISIBLE + else View.INVISIBLE + + svAddWifiZone1Signal.setValue(2) + setOnClickListenerViews( + btnAddWifiZone1Next, + tvAddWifiZone1LaterBtn, + ilAddWifiZone1BluetoothTips.trbBleConnectState + ) + mHaveSignalAnimator.start() + } + + connectBle() + } + + override fun liveDataObserve() { + //添加成功结束页面 + XEventBus.observe(this, EventName.RefreshWiFiZone) { + finish() + } + + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + showLoading(false) + mViewBinding.ilAddWifiZone1BluetoothTips.trbBleConnectState.let { view -> + val bgColor = if (ble.conState == ConState.CONNECTED) { + mPetBean?.macID = ble.mac + ViewUtil.instance.viewShowFadeInAnimation(mViewBinding.btnAddWifiZone1Next) + view.text = getString(R.string.tracker_manage_set_ble_connect) + ContextCompat.getColor(mContext, R.color.cyan_color1) + } else { + ViewUtil.instance.viewHideFadeInAnimation(mViewBinding.btnAddWifiZone1Next) + view.text = getString(R.string.tracker_manage_set_ble_unconnect) + ContextCompat.getColor(mContext, R.color.red_color4) + } + view.setBackgroundColor(bgColor) + ViewUtil.instance.bleConStateToast(this, ble.conState, Gravity.CENTER) + } + } + + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + ViewUtil.instance.viewHideFadeInAnimation(mViewBinding.btnAddWifiZone1Next) + if (conState == ConState.DEVICE_NOT_FOUND) { + ViewUtil.instance.showDialog( + mContext, + R.string.txt_no_search_device, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + } + }, + okTextResId = R.string.txt_sure, + cancelTextResId = R.string.txt_cancel + ) + } + mViewBinding.ilAddWifiZone1BluetoothTips.trbBleConnectState.let { view -> + ViewUtil.instance.bleConActionState(this, view, conState) + } + } + } + + private val mHaveSignalAnimator = ValueAnimator.ofInt(0, 3).apply { + duration = mAnimatorDuration +// repeatCount = ValueAnimator.INFINITE + //匀速变化 + interpolator = LinearInterpolator() + addUpdateListener { + val value = it.animatedValue as Int + mViewBinding.svAddWifiZone1Signal.setValue(value) + } + + doOnEnd { + startDelay = 3000 + if (mHaveSignal) { + mViewBinding.rvAddWifiZone1Room.playCloseAnimator() + setIntValues(3, 0) + } else { + mViewBinding.rvAddWifiZone1Room.playOpenAnimator() + setIntValues(0, 3) + } + mHaveSignal = !mHaveSignal + start() + } + } + + private fun connectBle() { + mPetBean?.apply { +// if (isFirstBind) +// mConnectionDeviceViewModel.connectDeviceToDeviceOutId( +// this@AddWifiPowerZone1Activity, deviceOutId +// ) + + //是否连接了蓝牙 + if (BleManager.getInstance().isConnected(macID)) { + ViewUtil.instance.viewShowFadeInAnimation(mViewBinding.btnAddWifiZone1Next) + mViewBinding.ilAddWifiZone1BluetoothTips.trbBleConnectState.let { + it.setBackgroundColor( + ContextCompat.getColor(mContext, R.color.cyan_color1) + ) + it.text = getString(R.string.tracker_manage_set_ble_connect) + } + } else { + mConnectionDeviceViewModel.connectDeviceToMac( + this@AddWifiPowerZone1Activity, macID + ) + } + } + } + + private fun goNext() { + Util.checkLocationPermissionsGpsEnabled(mContext, { + Intent(mContext, AddWifiPowerZone2Activity::class.java).let { + mPetBean?.let { pet -> + it.putExtra(ConstantString.Pet, pet) + } + it.putExtra(ConstantString.isFirstBind, isFirstBind) + it.putExtra(ConstantString.Latitude, mPetCurrentLat) + it.putExtra(ConstantString.Longitude, mPetCurrentLon) + if (isFirstBind) { + startActivityFinish(it) + } else { + startActivity(it) + } + } + }) + } + + /** + * 手动连接/断开设备 + */ + private fun connectionBtn(btnString: String) { + if (btnString == getString(R.string.tracker_manage_set_ble_unconnect)) { + connectBle() + } else if (btnString == getString(R.string.tracker_manage_set_ble_connect)) { + ViewUtil.instance.operateDialogTips(mContext, R.string.txt_disconnect_tips) { + mPetBean?.apply { + SRBleUtil.instance.disconnectToMac(macID) + } +// if (isFirstBind) { +// BleManager.getInstance().disconnectAllDevice() +// } else { +// mPetBean?.apply { +// SRBleUtil.instance.disconnectToMac(macID) +// } +// } + } + } + } + + override fun listenBackPressed() { + if (!isFirstBind) super.listenBackPressed() + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnAddWifiZone1Next -> goNext() + + tvAddWifiZone1LaterBtn -> startActivityFinish( + Intent( + mContext, HomeV2Activity::class.java + ) + ) + + ilAddWifiZone1BluetoothTips.trbBleConnectState -> connectionBtn( + ilAddWifiZone1BluetoothTips.trbBleConnectState.text.toString() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone2Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone2Activity.kt new file mode 100644 index 0000000..fff7aff --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone2Activity.kt @@ -0,0 +1,106 @@ +package com.abbidot.tracker.ui.activity.device.wifi + +import android.content.Intent +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityAddWifiPowerZone2Binding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.common.map.WifiZone2MapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class AddWifiPowerZone2Activity : + BaseActivity(ActivityAddWifiPowerZone2Binding::inflate) { + + private lateinit var mWifiZone2MapCommon: WifiZone2MapCommon + private lateinit var mFragment: Fragment + + private var mPetBean: PetBean? = null + private var mPetCurrentLat = 0.0 + private var isFirstBind = false + private var mPetCurrentLon = 0.0 + + override fun getTopBar() = mViewBinding.ilAddWifiZone2TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + mPetCurrentLat = getDouble(ConstantString.Latitude, 0.0) + mPetCurrentLon = getDouble(ConstantString.Longitude, 0.0) + } + + if (isFirstBind) setLeftBackImage(0) + else setLeftBackImage(R.drawable.icon_white_back_svg) + + mWifiZone2MapCommon = WifiZone2MapCommon() + mFragment = mWifiZone2MapCommon.getMapFragment(mContext) + + mViewBinding.apply { + tvWifiZone2LaterBtn.visibility = if (isFirstBind) View.VISIBLE + else View.INVISIBLE + supportFragmentManager.commit { + add(R.id.fc_wifi_power_zone2_map_fragment, mFragment) + } + + setOnClickListenerViews(btnAddWifiZone2Next, tbRefreshLocationBtn, tvWifiZone2LaterBtn) + } + } + + override fun liveDataObserve() { + //添加成功结束页面 + XEventBus.observe(this, EventName.RefreshWiFiZone) { + finish() + } + } + + private fun goNext() { + val petAddressData = mWifiZone2MapCommon.getDecWiFiAddressData() + if (null == petAddressData || petAddressData.latitude == 0.0 || petAddressData.longitude == 0.0) { + } else { + val intent = Intent(mContext, AddWifiPowerZone3Activity::class.java) + mPetBean?.let { pet -> + intent.putExtra(ConstantString.Pet, pet) + } + intent.putExtra(ConstantString.Latitude, mPetCurrentLat) + intent.putExtra(ConstantString.Longitude, mPetCurrentLon) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + intent.putExtra(ConstantString.HistoryDataBean, petAddressData) + startActivity(intent) + } + } + + override fun listenBackPressed() { + if (!isFirstBind) super.listenBackPressed() + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnAddWifiZone2Next -> Util.checkBluetoothPermissionsEnabled(mContext, { + goNext() + }) + + tvWifiZone2LaterBtn -> { +// XEventBus.post(EventName.RefreshWiFiZone) + startActivityFinish(Intent(mContext, HomeV2Activity::class.java)) + } + + tbRefreshLocationBtn -> { + ViewUtil.instance.viewAlphaAndRotationObjectAnimator(v) + mWifiZone2MapCommon.refreshLocation() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone3Activity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone3Activity.kt new file mode 100644 index 0000000..63f2ecb --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/AddWifiPowerZone3Activity.kt @@ -0,0 +1,287 @@ +package com.abbidot.tracker.ui.activity.device.wifi + +import android.animation.AnimatorSet +import android.content.Intent +import android.view.Gravity +import android.view.View +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.WiFiListAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.bean.WiFiBleListBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityAddWifiPowerZone3Binding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager +import com.google.gson.Gson + + +class AddWifiPowerZone3Activity : + BaseActivity(ActivityAddWifiPowerZone3Binding::inflate) { + + // private lateinit var mWifiManager: WifiManager + private lateinit var mWiFiListAdapter: WiFiListAdapter + + //解析后json数据 + private var mReceiveWifiJsonData = "" + + private lateinit var mGson: Gson + + private var mBleTrackDeviceBean: BleTrackDeviceBean? = null + + private var mDecWiFiAddressData: HistoryDataBean? = null + private var isFirstBind = false + private var mPetBean: PetBean? = null + private var mPetCurrentLat = 0.0 + private var mPetCurrentLon = 0.0 + + private var mAnimatorSet: AnimatorSet? = null + + override fun getTopBar() = mViewBinding.ilAddWifiZone3TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + setLeftBackImage(R.drawable.icon_white_back_svg) + mRightImageButton = addRightImageButton(R.drawable.icon_refresh) + + intent.extras?.apply { + mDecWiFiAddressData = Util.getParcelableAdaptive( + intent, ConstantString.HistoryDataBean, HistoryDataBean::class.java + ) + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + mPetCurrentLat = getDouble(ConstantString.Latitude, 0.0) + mPetCurrentLon = getDouble(ConstantString.Longitude, 0.0) + } + + if (null == mPetBean) finish() + + mGson = Gson() +// mWifiManager = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager + + mViewBinding.apply { + mWiFiListAdapter = WiFiListAdapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + goSetWifiPassword(pos) + } + }) + } + ViewUtil.instance.setRecyclerViewDecorationLinearLayoutManager( + mContext, rvAddWifiZone3WifiNetwork, mWiFiListAdapter + ) + } + + //查找是否连接了蓝牙 + mPetBean?.apply { + SRBleUtil.instance.getConnectMacDevice(macID)?.let { + updateConState(it) + } + + if (BleManager.getInstance().isConnected(macID)) { + sendWiFiCmd() + } else { + showToast(R.string.tracker_manage_set_ble_unconnect, gravity = Gravity.CENTER) + } + } + +// checkPermission() + } + + private fun sendWiFiCmd() { + mBleTrackDeviceBean?.apply { + //判断是否有蓝牙连接 + if (BleManager.getInstance().isConnected(bleDevice)) { + showLoading(true, R.string.txt_please_wait) + mRightImageButton?.let { + it.isEnabled = false + mAnimatorSet = ViewUtil.instance.viewAlphaAndRotationObjectAnimator(it, true) + } + mWiFiListAdapter.setData(null) + SRBleUtil.instance.writeData( + bleDevice, SRBleCmdUtil.instance.getWiFiZoneList() + ) + } else showToast(R.string.tracker_manage_set_ble_unconnect, gravity = Gravity.CENTER) + } + } + + override fun liveDataObserve() { + //添加成功结束页面 + XEventBus.observe(this, EventName.RefreshWiFiZone) { + finish() + } + + //接收蓝牙连接状态 + XEventBus.observe( + this, EventName.ConnectDeviceState + ) { trackBleDevice: BleTrackDeviceBean -> + mPetBean?.apply { + if (trackBleDevice.mac == macID) { + updateConState(trackBleDevice) + } + } + } + + //接收蓝牙返回数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mPetBean?.apply { + if (macID == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + } + + private fun goSetWifiPassword(pos: Int) { + if (mWiFiListAdapter.getData().isNotEmpty()) { + val wiFiBean = mWiFiListAdapter.getData()[pos] + val intent = Intent(mContext, AddWifiPasswordActivity::class.java) + intent.putExtra(ConstantString.WiFi, wiFiBean) + mDecWiFiAddressData?.let { + intent.putExtra(ConstantString.HistoryDataBean, it) + } + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.Latitude, mPetCurrentLat) + intent.putExtra(ConstantString.Longitude, mPetCurrentLon) + startActivity(intent) + } + } + + /** + * 解析蓝牙返回的数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 0x0F && data1 == 0x00) { + if (data2 == 0) { + val data3 = SRBleCmdUtil.instance.byteToInt(data[3]) + val data4 = SRBleCmdUtil.instance.byteToInt(data[4]) + //当前第一个包 + if (data4 == 1) { + mReceiveWifiJsonData = "" + } + + val dataLength = data[5] + // 截取byteArray的子数组,从索引start到end-1 + val start = 6 // 截取开始的索引 + val end = start + dataLength // 截取结束的索引(不包括) + val subArray = data.sliceArray(start until end) + val byteToString = subArray.decodeToString() + mReceiveWifiJsonData += byteToString + + //最后一个包 + if (data3 == data4) { + mAnimatorSet?.cancel() + mRightImageButton?.isEnabled = true + LogUtil.e("接受到的完整JSON字符串:$mReceiveWifiJsonData") + // 定义TypeToken来表示Item类型的列表 +// val itemType = object : TypeToken>() {}.type +// val wifiList = mGson.fromJson>( +// mGson.toJson(mReceiveWifiJsonData), +// itemType +// ) + try { + val wifiList = mGson.fromJson( + mReceiveWifiJsonData, WiFiBleListBean::class.java + ) + mWiFiListAdapter.setData(wifiList.wifi, true) + + showLoading(false) + } catch (_: Exception) { + sendWiFiCmd() + } + } + } else { + mAnimatorSet?.cancel() + mRightImageButton?.isEnabled = true + showToast(R.string.no_data) + } + } + } + } + + /** + * 更新设备连接状态 + */ + private fun updateConState(trackBleDevice: BleTrackDeviceBean) { + mBleTrackDeviceBean = trackBleDevice + if (trackBleDevice.conState == ConState.CONNECTED) { +// showToast(R.string.tracker_manage_set_ble_connect) + } else { + showToast(R.string.tracker_manage_set_ble_unconnect, gravity = Gravity.CENTER) + } + } + +// private fun checkPermission() { +// //从 Android 6.0(API 23)开始,定位权限现在被用于 Wi-Fi 扫描 +// Util.checkPermissions(mContext, object : Util.RequestPermissionCallback { +// override fun onRequestPermissionSuccess() { +// addListData() +// } +// +// override fun onRequestPermissionFail() { +// showToast(R.string.txt_wifi_permission) +// } +// }, getString(R.string.txt_wifi_permission), Manifest.permission.ACCESS_FINE_LOCATION) +// } + +// private fun addListData() { +// val list = getConnectedWifiSSID() +// mWiFiListAdapter.setData(list, true) +// } + + /* + /** + * 接收开启wifi操作 + */ + private val mActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + checkPermission() + } + + @SuppressLint("MissingPermission") + private fun getConnectedWifiSSID(): MutableList? { + // 首先,你需要开启WiFi扫描 + if (mWifiManager.isWifiEnabled) { + val scanResults = mWifiManager.scanResults + scanResults.sortBy { -it.level } + return scanResults + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { +// val intent = Intent(Settings.Panel.ACTION_WIFI) +// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) +// startActivity(intent) + + val intent = Intent(Settings.Panel.ACTION_WIFI) + launchActivity(mActivityResultLauncher, intent) + } else { + mWifiManager.isWifiEnabled = true + } + } + return null + } + */ + + override fun onClick(v: View?) { + when (v!!) { + mRightImageButton -> sendWiFiCmd() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/EditWifiPowerZoneActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/EditWifiPowerZoneActivity.kt new file mode 100644 index 0000000..3bcc7cd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/EditWifiPowerZoneActivity.kt @@ -0,0 +1,473 @@ +package com.abbidot.tracker.ui.activity.device.wifi + +import android.content.Intent +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.SelectFencesTypeCardShadeAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.bean.WiFiBean +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityEditPowerZoneBinding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.common.map.EditWifiZoneMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.WiFiZoneViewModel +import com.clj.fastble.BleManager + + +class EditWifiPowerZoneActivity : + BaseActivity(ActivityEditPowerZoneBinding::inflate) { + + private val mWiFiZoneViewModel: WiFiZoneViewModel by viewModels() + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private lateinit var mSelectTypeShadeAdapter: SelectFencesTypeCardShadeAdapter + private var mWiFiBean: WiFiBean? = null + + private lateinit var mWiFiZoneBean: WiFiZoneBean + + private var mPetBean: PetBean? = null + private var mPetCurrentLat = 0.0 + private var mPetCurrentLon = 0.0 + + private lateinit var mEditZoneMapCommon: EditWifiZoneMapCommon + private lateinit var mFragment: Fragment + + private var mShowCenterLocationType = ConstantInt.OtherLocationType + + //是否是编辑 + private var isEdit = false + private var isFirstBind = false + private var mBleTrackDeviceBean: BleTrackDeviceBean? = null + + override fun getTopBar() = mViewBinding.editPowerZoneTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_edit_power_zone) + setLeftBackImage(R.drawable.icon_white_back_svg) + + initWifiData() + + if (isEdit) mRightImageButton = addRightImageButton(R.drawable.icon_gray_delete_svg) + + mEditZoneMapCommon = EditWifiZoneMapCommon() + mFragment = mEditZoneMapCommon.getMapFragment(mContext, mWiFiZoneBean) { mapLoadOk() } + + mViewBinding.apply { + ilWifiNameInputLayout.tvInputNameTitle.text = getString(R.string.txt_zone_name) + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivEditWifiZoneRefreshBtn, mShowCenterLocationType + ) + setOnClickListenerViews( + ivEditWifiZoneRefreshBtn, + btnSaveWifiZone, + ivEditWifiZoneMapTypeBtn, + btnCancelWifiZone, + mRightImageButton, + ilEditPowerZoneBluetoothTips.trbBleConnectState + ) + + setListData() + + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, rvEditWifiZoneType, mSelectTypeShadeAdapter, right = AppUtils.dpToPx(8) + ) + + supportFragmentManager.commit { + add(R.id.fc_edit_power_zone_map_fragment, mFragment) + } + + setListenKeyboardChange() + + if (null != mBleTrackDeviceBean) { + ilEditPowerZoneBluetoothTips.trbBleConnectState.let { + it.setBackgroundColor(ContextCompat.getColor(mContext, R.color.cyan_color1)) + it.text = getString(R.string.tracker_manage_set_ble_connect) + } + } + } + } + + override fun liveDataObserve() { + //接收蓝牙返回数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mPetBean?.apply { + if (macID == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { trackDevice: BleTrackDeviceBean -> + showLoading(false) + mViewBinding.ilEditPowerZoneBluetoothTips.trbBleConnectState.let { view -> + val bgColor = if (trackDevice.conState == ConState.CONNECTED) { + view.text = getString(R.string.tracker_manage_set_ble_connect) + ContextCompat.getColor(mContext, R.color.cyan_color1) + } else { + view.text = getString(R.string.tracker_manage_set_ble_unconnect) + ContextCompat.getColor(mContext, R.color.red_color4) + } + view.setBackgroundColor(bgColor) + } + } + + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + mViewBinding.ilEditPowerZoneBluetoothTips.trbBleConnectState.let { view -> + ViewUtil.instance.bleConActionState(this, view, conState, Gravity.CENTER) + } + } + + mWiFiZoneViewModel.apply { + mAddWiFiZoneLiveData.observe(this@EditWifiPowerZoneActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshWiFiZone) + if (isFirstBind) { + showToast(R.string.txt_add_successful, gravity = Gravity.CENTER) + mViewBinding.root.postDelayed({ + startActivityFinish( + Intent( + mContext, HomeV2Activity::class.java + ) + ) + }, 1500) + } else { + showToast(R.string.txt_add_successful, true, gravity = Gravity.CENTER) + } + } + }) + } + + mUpdateWiFiZoneLiveData.observe(this@EditWifiPowerZoneActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshWiFiZone) + showToast(R.string.txt_change_successful, true, gravity = Gravity.CENTER) + } + }) + } + + mDeleteWiFiZoneLiveData.observe(this@EditWifiPowerZoneActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.DeleteWiFiZone) + showToast(R.string.txt_delete_success, true, gravity = Gravity.CENTER) + } + }) + } + } + } + + /** + * 地图加载好了 + */ + private fun mapLoadOk() { + mWiFiZoneBean.apply { + mEditZoneMapCommon.setShowWifiLocation(latitude, longitude, isEdit) + mPetBean?.let { + mEditZoneMapCommon.setPetLatLon( + it.imgurl, it.petType, mPetCurrentLat, mPetCurrentLon + ) + } + } + } + + private fun initWifiData() { + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + //设备里的wifi数据,添加使用到 + mWiFiBean = + Util.getParcelableAdaptive(intent, ConstantString.WiFi, WiFiBean::class.java) + //选地址页面的数据 + val decPetAddressData = Util.getParcelableAdaptive( + intent, ConstantString.HistoryDataBean, HistoryDataBean::class.java + ) + //宠物相关数据,包括deviceId + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + //编辑传过来的wiFiZone数据 + val wiFiZoneBean = Util.getParcelableAdaptive( + intent, ConstantString.WiFiZone, WiFiZoneBean::class.java + ) + if (null == wiFiZoneBean) { + isEdit = false + mWiFiZoneBean = WiFiZoneBean() + decPetAddressData?.let { + mWiFiZoneBean.address = it.address + mWiFiZoneBean.latitude = it.latitude + mWiFiZoneBean.longitude = it.longitude + } + } else { + isEdit = true + mWiFiZoneBean = wiFiZoneBean + } + mPetBean?.let { + mWiFiZoneBean.deviceId = it.deviceId + //查找当前Mac的设备 + mBleTrackDeviceBean = SRBleUtil.instance.getConnectMacDevice(it.macID) + } + + mPetCurrentLat = getDouble(ConstantString.Latitude, 0.0) + mPetCurrentLon = getDouble(ConstantString.Longitude, 0.0) + } + + mWiFiZoneBean.apply { + mWiFiBean?.let { + wifiName = it.name + wifiMac = it.mac + wifiZoneName = getString(R.string.txt_home) + } + mViewBinding.let { + it.tvWifiNameContent.text = wifiName + it.tvWifiGpsLocation.text = address + it.ilWifiNameInputLayout.etInputContent.setText(wifiZoneName) + it.tvWifiGpsLocation.text = address + } + } + } + + override fun onWindowFocusChanged(hasFocus: Boolean) { + super.onWindowFocusChanged(hasFocus) + mViewBinding.apply { + ViewUtil.instance.setMapViewBottomMarginOverlap( + rlEditPowerZoneMapLayout, rlEditWifiInfoLayout.height, AppUtils.dpToPx(20) + ) + } + } + + private fun setListData() { + val typeList = mutableListOf() + DataBean().apply { + imageId = R.drawable.icon_wifi_zone_home_type_svg + typeList.add(this) + } + DataBean().apply { + imageId = R.drawable.icon_wifi_zone_office_type_svg + typeList.add(this) + } + mSelectTypeShadeAdapter = SelectFencesTypeCardShadeAdapter(mContext, typeList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mSelectTypeShadeAdapter, typeList, pos + ) + mWiFiZoneBean.type = pos + 1 + mViewBinding.apply { + ilWifiNameInputLayout.let { + val zoneName = it.etInputContent.text.toString() + if (TextUtils.isEmpty(zoneName)) { + val name = if (pos == 0) getString(R.string.txt_home) + else getString(R.string.txt_office) + mWiFiZoneBean.wifiZoneName = name + it.etInputContent.setText(name) + } else if (zoneName == getString(R.string.txt_home)) { + val name = getString(R.string.txt_office) + mWiFiZoneBean.wifiZoneName = name + it.etInputContent.setText(name) + } else if (zoneName == getString(R.string.txt_office)) { + val name = getString(R.string.txt_home) + mWiFiZoneBean.wifiZoneName = name + it.etInputContent.setText(name) + } + } + } + } + }) + } + + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mSelectTypeShadeAdapter, typeList, mWiFiZoneBean.type - 1 + ) + } + + private fun saveWiFiZone() { + val name = mViewBinding.ilWifiNameInputLayout.etInputContent.text.toString() + if (!TextUtils.isEmpty(name)) { + mWiFiZoneBean.wifiZoneName = name + } + if (isEdit) { + mWiFiZoneViewModel.updateWifiZone(this, mWiFiZoneBean) + } else { + sendAddWiFiZoneCmd() + } + } + + private fun deleteWifiZone() { + ViewUtil.instance.showDialog( + mContext, R.string.txt_delete_tips, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + sendDeleteWiFiZoneCmd() + } + }, okTextResId = R.string.txt_sure + ) + } + + /** + * 解析数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + + mRightImageButton?.isEnabled = true + + if (data1 == 0x01 && data2 == 0x00) { + //添加wifi 指令成功 + if (data0 == 0x10) { + LogUtil.e("设置设备wifi指令成功") + mWiFiZoneViewModel.addWifiZone(this, mWiFiZoneBean) + } + //删除成功 + else if (data0 == 0x11) { + LogUtil.e("删除设备wifi指令成功") + mWiFiZoneViewModel.deleteWifiZone(this@EditWifiPowerZoneActivity, mWiFiZoneBean) + } + } else if (data1 == 0x01 && data2 == 0x01) { + showLoading(false) + setButtonEnabled(mViewBinding.btnSaveWifiZone,ConstantInt.Type1) + showToast(R.string.txt_duplicate_wifi_not, gravity = Gravity.CENTER) + } else { + setButtonEnabled(mViewBinding.btnSaveWifiZone,ConstantInt.Type1) + showLoading(false) + showToast("Fail", gravity = Gravity.CENTER) + LogUtil.e("设备wifi指令失败") + } + } + } + + + /** + * 添加WiFi指令 + */ + private fun sendAddWiFiZoneCmd() { + senCmd(SRBleCmdUtil.instance.setWiFiZone(mWiFiZoneBean)) + } + + /** + * 删除WiFi指令 + */ + private fun sendDeleteWiFiZoneCmd() { + senCmd(SRBleCmdUtil.instance.deleteWiFiZone(mWiFiZoneBean.wifiMac)) + } + + private fun senCmd(cmd: ByteArray) { + mBleTrackDeviceBean?.apply { + //判断是否有蓝牙连接 + if (BleManager.getInstance().isConnected(bleDevice)) { + showLoading(true, R.string.txt_please_wait) + mRightImageButton?.isEnabled = false + setButtonEnabled(mViewBinding.btnSaveWifiZone,ConstantInt.Type0) + SRBleUtil.instance.writeData(bleDevice, cmd) + } else { + showToast(R.string.tracker_manage_set_ble_unconnect, gravity = Gravity.CENTER) + } + } + } + + /** + * 手动连接/断开设备 + */ + private fun connectionBtn(btnString: String) { + mPetBean?.apply { + if (btnString == getString(R.string.tracker_manage_set_ble_unconnect)) { + if (isFirstBind) mConnectionDeviceViewModel.connectDeviceToDeviceOutId( + this@EditWifiPowerZoneActivity, deviceOutId + ) + else mConnectionDeviceViewModel.connectDeviceToMac( + this@EditWifiPowerZoneActivity, macID + ) + } else if (btnString == getString(R.string.tracker_manage_set_ble_connect)) { + ViewUtil.instance.operateDialogTips(mContext, R.string.txt_disconnect_tips) { + if (isFirstBind) { + BleManager.getInstance().disconnectAllDevice() + } else { + SRBleUtil.instance.disconnectToMac(macID) + } + } + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ivEditWifiZoneRefreshBtn -> { + mWiFiZoneBean.let { + when (mShowCenterLocationType) { + ConstantInt.PetLocationType -> { + mShowCenterLocationType = ConstantInt.UserLocationType + } + + ConstantInt.UserLocationType -> { + mShowCenterLocationType = ConstantInt.OtherLocationType + } + + ConstantInt.OtherLocationType -> { + mShowCenterLocationType = + if (mPetCurrentLat == 0.0 && mPetCurrentLon == 0.0) { + ConstantInt.UserLocationType + } else { + ConstantInt.PetLocationType + } + } + } + mEditZoneMapCommon.switchShowLocation( + mShowCenterLocationType, it.latitude, it.longitude + ) + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivEditWifiZoneRefreshBtn, mShowCenterLocationType + ) + } + } + + ivEditWifiZoneMapTypeBtn -> mEditZoneMapCommon.switchSatelliteAndNormalMapType() + + btnSaveWifiZone -> saveWiFiZone() + mRightImageButton -> deleteWifiZone() + btnCancelWifiZone -> { + if (isFirstBind) { + finish() + } else { + if (!isEdit) XEventBus.post(EventName.RefreshWiFiZone) + finish() + } + } + + ilEditPowerZoneBluetoothTips.trbBleConnectState -> connectionBtn( + ilEditPowerZoneBluetoothTips.trbBleConnectState.text.toString() + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/PowerSavingSetActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/PowerSavingSetActivity.kt new file mode 100644 index 0000000..789b830 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/device/wifi/PowerSavingSetActivity.kt @@ -0,0 +1,164 @@ +package com.abbidot.tracker.ui.activity.device.wifi + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.WiFiZoneAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityPowerSavingSetBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.WiFiZoneViewModel +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +class PowerSavingSetActivity : + BaseActivity(ActivityPowerSavingSetBinding::inflate) { + + private val mWiFiZoneViewModel: WiFiZoneViewModel by viewModels() + + private lateinit var mZoneAdapter: WiFiZoneAdapter + + private var mPetBean: PetBean? = null + private var mPetCurrentLat = 0.0 + private var mPetCurrentLon = 0.0 + + override fun getTopBar() = mViewBinding.ilPowerSavingSetTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_power_saving_set) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + + mZoneAdapter = WiFiZoneAdapter(mContext, null).apply { + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + goAddWifiZone() + } + }) + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + goEditWiFiZone(pos) + } + }) + } + + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvPowerSavingZone, mZoneAdapter, top = QMUIDisplayHelper.dpToPx(16) + ) + + setOnClickListenerViews(btnPowerSavingSetAdd) + } + + mPetBean?.apply { + mWiFiZoneViewModel.getWifiZoneList(this@PowerSavingSetActivity, deviceId) + } + } + + override fun liveDataObserve() { + //添加成功刷新页面 + XEventBus.observe(this, EventName.RefreshWiFiZone) { + mPetBean?.apply { + mWiFiZoneViewModel.getWifiZoneList(this@PowerSavingSetActivity, deviceId) + } + } + XEventBus.observe(this, EventName.DeleteWiFiZone) { + mPetBean?.apply { + mWiFiZoneViewModel.getWifiZoneList(this@PowerSavingSetActivity, deviceId) + } + } + + mWiFiZoneViewModel.mWiFiZoneListLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mZoneAdapter.setData(null) + mPetCurrentLat = latitude + mPetCurrentLon = longitude + list?.let { wifi -> + setWifiListData(wifi) + } + } + } + }) + + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) { + mZoneAdapter.showNoDataAddButton(false) + } + } + } + + private fun goEditWiFiZone(pos: Int) { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) { + return + } + Util.checkLocationPermissionsGpsEnabled(mContext, { + if (mZoneAdapter.getData().isNotEmpty()) { + val wiFiZoneBean = mZoneAdapter.getData()[pos] + val intent = Intent(mContext, EditWifiPowerZoneActivity::class.java) + intent.putExtra(ConstantString.WiFiZone, wiFiZoneBean) +// mDecWiFiAddressData?.let { +// intent.putExtra(ConstantString.HistoryDataBean, it) +// } + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.Latitude, mPetCurrentLat) + intent.putExtra(ConstantString.Longitude, mPetCurrentLon) + startActivity(intent) + } + }) + } + + private fun setWifiListData(list: MutableList) { + list.apply { + mViewBinding.let { + if (size > 0) { + it.btnPowerSavingSetAdd.visibility = if (size == 2) View.INVISIBLE + else View.VISIBLE + mZoneAdapter.setData(list, true) + } else { + it.btnPowerSavingSetAdd.visibility = View.INVISIBLE + } + + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share != ConstantInt.NoShare) { + it.btnPowerSavingSetAdd.visibility = View.INVISIBLE + } + } + } + } + + private fun goAddWifiZone() { + Intent(mContext, AddWifiPowerZone1Activity::class.java).apply { + mPetBean?.let { + putExtra(ConstantString.Pet, it) + } + putExtra(ConstantString.Latitude, mPetCurrentLat) + putExtra(ConstantString.Longitude, mPetCurrentLon) + startActivity(this) + } + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPowerSavingSetAdd -> goAddWifiZone() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/FeedbackActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/FeedbackActivity.kt new file mode 100644 index 0000000..d900746 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/FeedbackActivity.kt @@ -0,0 +1,275 @@ +package com.abbidot.tracker.ui.activity.help + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.FeedbackImageAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityFeedbackBinding +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FeedbackViewModel +import com.abbidot.tracker.vm.TakePhotoAndCompressViewModel +import com.abbidot.tracker.vm.UserProfileViewModel +import com.huantansheng.easyphotos.EasyPhotos +import com.huantansheng.easyphotos.models.album.entity.Photo +import kotlinx.coroutines.launch + +/** + * 反馈 + */ +class FeedbackActivity : BaseActivity(ActivityFeedbackBinding::inflate) { + + private val mTakePhotoAndCompressViewModel: TakePhotoAndCompressViewModel by viewModels() + private val mFeedbackViewModel: FeedbackViewModel by viewModels() + private val mUserInfoViewModel: UserProfileViewModel by viewModels() + + //反馈问题类型 type 1为APP 2为设备 3其它 + private var mFeedbackType = 1 + + //限制照片数量 + private val mLimitPhotoCount = 5 + private lateinit var mUploadImageUrlList: MutableList + + //已上传图片的数量 + private var mUploadImageIndex = 0 + + private lateinit var mFeedbackImageAdapter: FeedbackImageAdapter + + override fun getTopBar() = mViewBinding.ilSettingFeedbackBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_feedback_title) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mUploadImageUrlList = mutableListOf() + mFeedbackImageAdapter = FeedbackImageAdapter(mContext, this, null) + + mViewBinding.apply { + ilFeedbackPhoneLayout.let { + it.tvInputMustFill.visibility = View.VISIBLE + it.tvInputNameTitle.setText(R.string.txt_enter_email_tip) + it.etInputContent.setHint(R.string.txt_email_address) + } + + ViewUtil.instance.setRecyclerViewHorizontalLinearLayout( + mContext, settingFeedbackImageRv, mFeedbackImageAdapter, right = AppUtils.dpToPx(12) + ) + + radiogroupSettingFeedbackType.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + radiobuttonFeedbackAppUse.id -> { + mFeedbackType = 1 + } + + radiobuttonFeedbackProductUse.id -> { + mFeedbackType = 2 + } + + radiobuttonFeedbackProductSuggest.id -> { + mFeedbackType = 3 + } + } + } + + setOnClickListenerViews(btnSubmitSettingFeedback, llSelectImageBtnLayout) + + setViewClickHideInputMethod(llSettingFeedbackLayout) + } + + setListenKeyboardChange() + } + + override fun liveDataObserve() { + mTakePhotoAndCompressViewModel.mPhotoPathLiveData.observe(this) { + lifecycleScope.launch { + val list = mutableListOf() + list.addAll(mFeedbackImageAdapter.getData()) + it.forEach { file -> + ViewUtil.instance.addMenuBean(list, "", imageUrl = file.path) + } + mFeedbackImageAdapter.setData(list, true) + mViewBinding.apply { + settingFeedbackImageRv.scrollToPosition(list.size - 1) + if (list.size == mLimitPhotoCount) { + setBtnUploadState(false) + } + } + } + } + + mUserInfoViewModel.mUploadImageLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + mUploadImageUrlList.add(data) + LogUtil.e("第${mUploadImageIndex + 1}张图片上传完成") + mUploadImageIndex++ + uploadImageList() + } + }) + } + + mFeedbackViewModel.mFeedbackResultLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.txt_feedback_success, isFinish = true) + } + }) + } + } + + /** + * 设置是否可以上传按钮状态 + */ + private fun setBtnUploadState(isCanUpload: Boolean) { + mViewBinding.apply { + llSelectImageBtnLayout.isEnabled = isCanUpload + if (isCanUpload) { + tvFeedbackChooseFileTitle.setTextColor( + ContextCompat.getColor( + mContext, R.color.data_black_color + ) + ) + ivFeedbackChooseFileImage.drawable.setTint( + ContextCompat.getColor( + mContext, R.color.data_black_color + ) + ) + } else { + tvFeedbackChooseFileTitle.setTextColor( + ContextCompat.getColor( + mContext, R.color.gray_color10 + ) + ) + ivFeedbackChooseFileImage.drawable.setTint( + ContextCompat.getColor( + mContext, R.color.gray_color10 + ) + ) + } + } + } + + fun deletePhoto(list: MutableList, item: MenuTxtBean) { + lifecycleScope.launch { + for (i in 0 until list.size) { + val menuTxtBean = list[i] + if (menuTxtBean.imageUrl == item.imageUrl) { + list.removeAt(i) + mFeedbackImageAdapter.notifyItemRemoved(i) + setBtnUploadState(true) + break + } + } + } + } + + /** + * 开始提交反馈 + */ + private fun startSubmitFeedback() { + if (mViewBinding.radiogroupSettingFeedbackType.checkedRadioButtonId < 0) { + showToast(R.string.txt_feedback_notype) + return + } + val feedbackContent = mViewBinding.edSettingFeedbackContent.text.toString() + val feedbackEmail = mViewBinding.ilFeedbackPhoneLayout.etInputContent.text.toString() + if (TextUtils.isEmpty(feedbackContent) || TextUtils.isEmpty(feedbackEmail)) { + showToast(R.string.txt_not_null) + return + } + if (!Utils.isValidEmail(feedbackEmail)) { + showToast(R.string.txt_valid_email) + return + } + + mUploadImageIndex = 0 + mUploadImageUrlList.clear() + uploadImageList() + } + + /** + * 多张图片上传 + */ + private fun uploadImageList() { + val photoList = mFeedbackImageAdapter.getData() + if (photoList.size > mUploadImageIndex) { + val imageUrl = photoList[mUploadImageIndex].imageUrl + if (TextUtils.isEmpty(imageUrl)) { + mUploadImageIndex++ + uploadImageList() + } else { + mUserInfoViewModel.uploadImage(this, imageUrl) + } + } else { + LogUtil.e("图片已全部上传") + submitFeedbackOk() + } + } + + /** + * 完成提交反馈 + */ + private fun submitFeedbackOk() { + val feedbackContent = mViewBinding.edSettingFeedbackContent.text.toString() + val feedbackEmail = mViewBinding.ilFeedbackPhoneLayout.etInputContent.text.toString() + val userId = MMKVUtil.getString(MMKVKey.UserId) + val imageUrls = mUploadImageUrlList.joinToString(",") + mFeedbackViewModel.feedback( + this, mFeedbackType, userId, feedbackContent, imageUrls, feedbackEmail + ) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (RESULT_OK == resultCode) { + //相机或相册回调 + if (requestCode == ResultCode.ResultCode_1) { + if (null != data) { + //返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + val resultPhotos: ArrayList = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)!! + mTakePhotoAndCompressViewModel.dealCompressPhoto(mContext, resultPhotos) + } + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + mViewBinding.btnSubmitSettingFeedback -> startSubmitFeedback() + + mViewBinding.llSelectImageBtnLayout -> { + if (mFeedbackImageAdapter.getData().size == mLimitPhotoCount) { + showToast( + String.format( + getString(R.string.selector_reach_max_image_hint_easy_photos), + mLimitPhotoCount + ) + ) + return@apply + } + ViewUtil.instance.goSelectPhoto( + this@FeedbackActivity, + ResultCode.ResultCode_1, + count = mLimitPhotoCount - mFeedbackImageAdapter.getData().size + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpActiveTrackerActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpActiveTrackerActivity.kt new file mode 100644 index 0000000..1b26fef --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpActiveTrackerActivity.kt @@ -0,0 +1,16 @@ +package com.abbidot.tracker.ui.activity.help + +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityHelpActiveTrackerBinding + +class HelpActiveTrackerActivity : BaseActivity(ActivityHelpActiveTrackerBinding::inflate) { + + override fun getTopBar()=mViewBinding.ilHelpActiveTrackerBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_active_tracker) + setLeftBackImage(R.drawable.icon_white_back_svg) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpCreatePetFenceActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpCreatePetFenceActivity.kt new file mode 100644 index 0000000..1b69fae --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpCreatePetFenceActivity.kt @@ -0,0 +1,54 @@ +package com.abbidot.tracker.ui.activity.help + +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HelpTextImageTypeAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.databinding.ActivityHelpCreatePetFenceBinding +import com.abbidot.tracker.util.ViewUtil + +class HelpCreatePetFenceActivity : + BaseActivity(ActivityHelpCreatePetFenceBinding::inflate) { + + private lateinit var mHelpCreateFenceAdapter: HelpTextImageTypeAdapter + + override fun getTopBar() = mViewBinding.ilHelpCreatePetFenceBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_create_pet_fence) + setLeftBackImage(R.drawable.icon_white_back_svg) + + val menuList = mutableListOf() + ViewUtil.instance.addMenuBean( + menuList, getString(R.string.txt_create_fence_tip1), menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, "", imageResId = R.drawable.create_fence_help1, menuType = MultipleEntity.IMG + ) + ViewUtil.instance.addMenuBean( + menuList, getString(R.string.txt_create_fence_tip2), menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, "", imageResId = R.drawable.create_fence_help2, menuType = MultipleEntity.IMG + ) + ViewUtil.instance.addMenuBean( + menuList, getString(R.string.txt_create_fence_tip3), menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, "", imageResId = R.drawable.create_fence_help3, menuType = MultipleEntity.IMG + ) + ViewUtil.instance.addMenuBean( + menuList, getString(R.string.txt_create_fence_tip4), menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, "", imageResId = R.drawable.create_fence_help4, menuType = MultipleEntity.IMG + ) + + mHelpCreateFenceAdapter = HelpTextImageTypeAdapter(menuList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, mViewBinding.rvHelpCreatePetFenceContent, mHelpCreateFenceAdapter + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpPowerOnOffActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpPowerOnOffActivity.kt new file mode 100644 index 0000000..2e753c1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpPowerOnOffActivity.kt @@ -0,0 +1,26 @@ +package com.abbidot.tracker.ui.activity.help + +import coil.load +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityHelpPowerOnOffBinding + +class HelpPowerOnOffActivity : + BaseActivity(ActivityHelpPowerOnOffBinding::inflate) { + + override fun getTopBar() = mViewBinding.ilHelpPowerOnOffBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_power_on_off) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + ivHelpPowerOnOffGif.load(R.drawable.charge_power_on) + ivHelpShakeGif.load(R.drawable.gif_shake_on) + ilHelpPowerOnOffTitle.tvHelpTitleOne.setText(R.string.txt_power_on_off_tip) + ilHelpShakeTitle.tvHelpTitleOne.setText(R.string.txt_power_on_off_install_tip) + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpTrackerBatteryActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpTrackerBatteryActivity.kt new file mode 100644 index 0000000..a9015ff --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpTrackerBatteryActivity.kt @@ -0,0 +1,120 @@ +package com.abbidot.tracker.ui.activity.help + +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HelpTextImageTypeAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.databinding.ActivityHelpTrackerBatteryBinding +import com.abbidot.tracker.util.ViewUtil + +class HelpTrackerBatteryActivity : + BaseActivity(ActivityHelpTrackerBatteryBinding::inflate) { + + private lateinit var mHelpBatteryAdapter: HelpTextImageTypeAdapter + + override fun getTopBar() = mViewBinding.ilHelpTrackerBatteryBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_tracker_battery_life) + setLeftBackImage(R.drawable.icon_white_back_svg) + + val menuList = mutableListOf() + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_tracker_sleeping_mode)) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_low_power), + menuType = MultipleEntity.IMG_TEXT, + colorRedId = R.color.help_color1 + ) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_tracker_battery_tip1), + menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, + "", + imageResId = R.drawable.battery_life_help1, + menuType = MultipleEntity.IMG + ) + + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_in_wifi_zone)) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_low_power_usage), + menuType = MultipleEntity.IMG_TEXT, + colorRedId = R.color.help_color2 + ) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_tracker_battery_tip2), + menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, "", imageResId = R.drawable.battery_life_help2, menuType = MultipleEntity.IMG + ) + + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_outside_wifi_zone)) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_standard_power_usage), + menuType = MultipleEntity.IMG_TEXT, + colorRedId = R.color.help_color3 + ) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_tracker_battery_tip3), + menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, + "", + imageResId = R.drawable.battery_life_help3, + menuType = MultipleEntity.IMG + ) + + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_gps_update_interval)) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_tracker_battery_tip4), + menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_tracker_battery_tip5), + menuType = MultipleEntity.IMG_TEXT_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, + "", + imageResId = R.drawable.battery_life_help4, + menuType = MultipleEntity.IMG + ) + + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_in_live_mode)) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_highest_power_usage), + menuType = MultipleEntity.IMG_TEXT, + colorRedId = R.color.help_color4 + ) + ViewUtil.instance.addMenuBean( + menuList, + getString(R.string.txt_tracker_battery_tip6), + menuType = MultipleEntity.IMG_IMG + ) + ViewUtil.instance.addMenuBean( + menuList, + "", + imageResId = R.drawable.battery_life_help5, + menuType = MultipleEntity.IMG + ) + + mHelpBatteryAdapter = HelpTextImageTypeAdapter(menuList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, mViewBinding.rvHelpTrackerBatteryContent, mHelpBatteryAdapter + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpTrackerStatusActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpTrackerStatusActivity.kt new file mode 100644 index 0000000..c0c2111 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HelpTrackerStatusActivity.kt @@ -0,0 +1,18 @@ +package com.abbidot.tracker.ui.activity.help + +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityHelpTrackerStatusBinding + +class HelpTrackerStatusActivity : + BaseActivity(ActivityHelpTrackerStatusBinding::inflate) { + + override fun getTopBar() = mViewBinding.ilHelpTrackerStatusBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_check_tracker_status) + setLeftBackImage(R.drawable.icon_white_back_svg) + mViewBinding.ilHelpTrackerStatusTitle1.tvHelpTitleOne.setText(R.string.txt_check_tracker_status_top_tip) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/help/HowWorkActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HowWorkActivity.kt new file mode 100644 index 0000000..0120b48 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/help/HowWorkActivity.kt @@ -0,0 +1,48 @@ +package com.abbidot.tracker.ui.activity.help + +import android.content.Intent +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HowWorkAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.databinding.ActivityHowWorkBinding +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +class HowWorkActivity : BaseActivity(ActivityHowWorkBinding::inflate) { + + private lateinit var mHowWorkAdapter: HowWorkAdapter + + override fun getTopBar() = mViewBinding.ilHowWorkBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_tracker_guide) + setLeftBackImage(R.drawable.icon_white_back_svg) + + val menuList = mutableListOf() + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_power_on_off)) + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_active_tracker)) + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_check_tracker_status)) + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_tracker_battery_life)) + ViewUtil.instance.addMenuBean(menuList, getString(R.string.txt_create_pet_fence)) + mHowWorkAdapter = HowWorkAdapter(this, menuList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + when (pos) { + 0 -> startActivity(Intent(mContext, HelpPowerOnOffActivity::class.java)) + 1 -> startActivity(Intent(mContext, HelpActiveTrackerActivity::class.java)) + 2 -> startActivity(Intent(mContext, HelpTrackerStatusActivity::class.java)) + 3 -> startActivity(Intent(mContext, HelpTrackerBatteryActivity::class.java)) + 4 -> startActivity(Intent(mContext, HelpCreatePetFenceActivity::class.java)) + } + } + }) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, mViewBinding.rvHowWorkList, this, top = QMUIDisplayHelper.dpToPx(4) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/map/LiveActivityV2.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/map/LiveActivityV2.kt new file mode 100644 index 0000000..0502c38 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/map/LiveActivityV2.kt @@ -0,0 +1,969 @@ +package com.abbidot.tracker.ui.activity.map + +import android.content.Intent +import android.graphics.Typeface +import android.view.View +import android.widget.RelativeLayout +import androidx.activity.viewModels +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HomeMapDeviceMsgAdapter +import com.abbidot.tracker.adapter.HomeMapDeviceStateAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.BleReportDataBean +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.LiveDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityLiveV2Binding +import com.abbidot.tracker.dialog.CommonDialog1 +import com.abbidot.tracker.dialog.SelectMapTypeDialog +import com.abbidot.tracker.ui.DebugActivity +import com.abbidot.tracker.ui.common.map.HomeMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.CountDownTimerViewModel +import com.abbidot.tracker.vm.FencesMapViewModel +import com.abbidot.tracker.vm.FindBleDeviceViewModel +import com.abbidot.tracker.vm.MapLiveViewModel +import com.abbidot.tracker.vm.MapViewModel +import com.abbidot.tracker.vm.TrackerSetViewModel +import com.abbidot.tracker.widget.MapDeviceNetView +import com.clj.fastble.BleManager +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class LiveActivityV2 : BaseActivity(ActivityLiveV2Binding::inflate) { + + private val mFencesMapViewModel: FencesMapViewModel by viewModels() + private val mMapViewModel: MapViewModel by viewModels() + private val mFindBleDeviceViewModel: FindBleDeviceViewModel by viewModels() + private val mMapLiveViewModel: MapLiveViewModel by viewModels() + private val mTrackerManageViewModel: TrackerSetViewModel by viewModels() + private val mCountDownViewModel: CountDownTimerViewModel by viewModels() + private val mConDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private var mSelectMapTypeDialog: SelectMapTypeDialog? = null + + private lateinit var mDeviceStateList: MutableList + private lateinit var mDeviceStateAdapter: HomeMapDeviceStateAdapter + private lateinit var mDeviceMsgList: MutableList + private lateinit var mDeviceMsgAdapter: HomeMapDeviceMsgAdapter + + @Inject + lateinit var mHomeMapCommon: HomeMapCommon + private lateinit var mFragment: Fragment + + private var mMapDeviceBean: MapDeviceBean? = null + + //启动动画移动地图摄像机 + private var isAnimMoveCamera = true + + //是否显示围栏 + private var isShowFence = true + + //地图类型,标准和卫星地图 + private var mMapType = ConstantInt.Type0 + private var mShowCenterLocation = ConstantInt.PetLocationType + + //是否是直播跳转 + private var isLiveJump = true + private var mLiveStatus = 1 + private var mPetBean: PetBean? = null + private var mTrackBleDevice: BleTrackDeviceBean? = null + + //直播是否开始 + private var isStartLive = false + + // private val mLiveStateTips = mutableListOf( +// R.string.txt_starting_live, R.string.txt_con_network, R.string.txt_gps_position +// ) + private var mStartLiveIndex = 0 + + //直播时间 分钟 + private var mLiveTime = 5 + private lateinit var mSpeedUnit: String + private lateinit var mDistanceUnit: String + + //是否打开了找设备的声音 + private var isOpenSound = false + + //设备Led灯开关 + private var isLedOpen = false + + override fun getTopBar() = null + + override fun initData() { + super.initData() + mSpeedUnit = Util.getMetricUnits(mContext, ConstantInt.Type2) + mDistanceUnit = Util.getMetricUnits(mContext, ConstantInt.Type3) + intent.extras?.apply { + isLiveJump = getBoolean(ConstantString.JumpActivity, true) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + isShowFence = Util.getShowFenceSp() + + mViewBinding.apply { + ilLiveV2BluetoothFindDevice.ivMapLiveV2BleConState.setValue(0) + mFragment = mHomeMapCommon.getMapFragment( + mContext, miLiveV2MapAddressView, dnLiveV2MapNetView + ) { mapLoadOk() } + dnLiveV2MapNetView.setShowNetType(MapDeviceNetView.WIFI_NET_TYPE) + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivMapLiveV2RefreshBtn, mShowCenterLocation + ) + ilLiveV2OperateLayout.tvLiveV2Speed.text = "--$mSpeedUnit" + ilLiveV2OperateLayout.tvLiveV2Distance.text = "--$mDistanceUnit" + llLiveV2MapTopPet.ivTopPetBtnSmall.setImageResource(R.drawable.icon_map_type) + lavLiveV2StateAnim.let { + it.setAnimation("lottie/loading_dog.json") + it.playAnimation() + } + + mDeviceStateList = mutableListOf() + mDeviceMsgList = mutableListOf() + mMapViewModel.addDeviceDefaultStateData(mContext, mDeviceStateList) + mDeviceStateAdapter = HomeMapDeviceStateAdapter(mContext, mDeviceStateList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvMapLiveV2DeviceState, mDeviceStateAdapter, bottom = 10 + ) + + mDeviceMsgAdapter = HomeMapDeviceMsgAdapter(mContext, mDeviceMsgList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvLiveV2MapDeviceMsg, mDeviceMsgAdapter, bottom = 10 + ) + + ilLiveV2BluetoothFindDevice.liveV2BluetoothSignal.setValue(0) + ilLiveV2BluetoothFindDevice.findBleDeviceRadoView.setValue(0) + + setOnClickListenerViews( + ilLiveV2OperateLayout.llLiveV2StopLive, + ivMapLiveV2RefreshBtn, + ivMapLiveV2BluetoothBtn, + ilLiveV2BluetoothFindDevice.btnLiveV2StopRadar, + llLiveV2MapTopPet.ivTopPetBtnSmall, + ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.root, + ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.cbDeviceLightSwitch, + ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.cbDeviceLightSwitch, + ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.root, + ilLiveV2BluetoothFindDevice.ivLiveV2FindDeviceSound, + ilLiveV2OperateLayout.btnLiveV2Issue, + ilMapLiveV2IssueLayout.tvCloseIssueBtn + ) + + supportFragmentManager.commit { + add(R.id.fc_live_v2_map_fragment, mFragment) + } + + mPetBean?.let { pet -> + if (!isLiveJump) { + findBleDevice(pet.macID) + llLiveV2StateLayout.visibility = View.GONE + } + + if (AppUtils.isDebug()) { + llLiveV2MapTopPet.homeDataPetHeadSmall.root.setOnLongClickListener { + goToDebugActivity(pet) + true + } + } + } + } + } + + override fun liveDataObserve() { + mCountDownViewModel.mCountDownEndLiveData.observe(this) { + when (mStartLiveIndex) { +// 0 -> { +// mStartLiveIndex++ +// //显示Connecting to network 30秒 +// mViewBinding.tvLiveV2StateContent.setText(mLiveStateTips[mStartLiveIndex]) +// mCountDownViewModel.startCountDown(30) +// } +// +// 1 -> { +// mStartLiveIndex++ +// //显示GPS positioning +// mCountDownViewModel.startCountDown(3 * 60) +// mViewBinding.tvLiveV2StateContent.setText(mLiveStateTips[mStartLiveIndex]) +// } +// +// 2 -> { +// if (isStartLive) { +// mStartLiveIndex++ +// //显示Starting 5-min Live +// val str = String.format(getString(R.string.txt_start_min_live), mLiveTime) +// mViewBinding.tvLiveV2StateContent.text = str +// mCountDownViewModel.startCountDown(2) +// } else { +// //直播超时 +// mLiveStatus = -1 +// stopLive() +// } +// } +// + 2 -> { + mViewBinding.llLiveV2StateLayout.visibility = View.VISIBLE + mStartLiveIndex++ + //显示Starting 5-min Live + val str = String.format(getString(R.string.txt_start_min_live), "$mLiveTime") + mViewBinding.tvLiveV2StateContent.text = str + mCountDownViewModel.startCountDown(5) + } + + 3 -> mViewBinding.llLiveV2StateLayout.visibility = View.GONE + } + } + //获取当前宠物位置和围栏信息 + mMapViewModel.mMapDeviceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + mMapDeviceBean = data + setMapData(data) + mMapViewModel.setDeviceStateAndWarningData( + mContext, + mPetBean, + data, + mDeviceStateList, + mDeviceStateAdapter, + mDeviceMsgList, + mDeviceMsgAdapter, + mViewBinding.rvLiveV2MapDeviceMsg, + true + ) + } + } + + override fun onRequestError(exceptionCode: String?) { + LogUtil.e("LiveV2Activity获取当前宠物位置和围栏信息onRequestError,再次请求") + if (exceptionCode == mNetworkRequestsFailLimit) { + finish() + } else { + mPetBean?.apply { + //获取首页设备信息状态 + mMapViewModel.getMapDeviceStatus(deviceId, false) + } + } + } + }, showLoading = false) + } + + mMapLiveViewModel.mMapLiveStatusLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mPetBean?.apply { +// SRBleUtil.instance.isConnectBleSendNotifyData(macID) + when (mLiveStatus) { + //直播超时 + -1 -> { + XEventBus.post(EventName.LiveOpenTimeOut) + finish() + } + //直播结束 + 0 -> { + mViewBinding.llLiveV2StateLayout.visibility = View.GONE +// showToast(R.string.txt_change_successful, true) + finish() + } + //直播开始 + else -> { + //直播开启后,就去获取直播轨迹 + mMapLiveViewModel.mGetPetLivePointTimeStamp = + System.currentTimeMillis() / 1000 + mMapLiveViewModel.getPetLivePoint(deviceId) + } + } + } + } + + override fun onRequestError(exceptionCode: String?) { + LogUtil.e("HomeV2Activity直播开启错误onRequestError,再次请求") + if (exceptionCode == mNetworkRequestsFailLimit) { + finish() + } else { + mPetBean?.apply { + mMapLiveViewModel.setupDeviceLiveStatus( + this@LiveActivityV2, deviceId, mLiveStatus + ) + } + } + } + }) + } + + //获取直播轨迹 + mMapLiveViewModel.mMapLiveLiveData.observe(this) { + mHomeMapCommon.startRefreshUserLocation() + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + setTrackData(this) + } + } + }, isShowRequestErrorTip = false) + } + + //接收蓝牙连接状态 + XEventBus.observe( + this, EventName.ConnectDeviceState + ) { trackBleDevice: BleTrackDeviceBean -> + mMapDeviceBean?.apply { + if (trackBleDevice.mac == deviceMacId) { + updateConState(trackBleDevice) + } + } + } + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + if (conState == ConState.DEVICE_NOT_FOUND) { + openNetLive() + } + } + + mTrackerManageViewModel.mLedSwitchLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { +// mViewBinding.cbLiveV2DeviceLightSwitch.isChecked = +// !mViewBinding.cbLiveV2DeviceLightSwitch.isChecked +// showToast(R.string.txt_please_wait) + } + }) + } + + //蓝牙上报的数据 + XEventBus.observe(this, EventName.BleReportData) { reportData: BleReportDataBean -> + mPetBean?.apply { + if (macID == reportData.mac) updateBleReportData(reportData) + } + } + + //接收发送指令后设备返回的数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mPetBean?.apply { + if (macID == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + } + + + /** + * 跳转debug调试页面 + */ + private fun goToDebugActivity(petBean: PetBean) { + val intent = Intent(mContext, DebugActivity::class.java) + ArrayList().apply { + add(petBean) + intent.putParcelableArrayListExtra(ConstantString.Pet, this) + } + intent.putExtra(ConstantString.Index, 0) + startActivity(intent) + } + + /** + * 解析蓝牙返回的数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 0x06 && data1 == 1) { + val data3 = SRBleCmdUtil.instance.byteToInt(data[3]) + //开启直播 + if (data2 == 1 && data3 == 0) { + LogUtil.e("开启直播") + } else if (data2 == 0 && data3 == 0) { + //停止轨迹Live直播 + LogUtil.e("//停止轨迹Live直播") + } + } + } + } + + /** + * 开启网络上报直播 + */ + private fun openNetLive() { + if (isLiveJump) { + mPetBean?.apply { + if (!BleManager.getInstance().isConnected(macID)) { + //刚开始显示Starting Live. + mCountDownViewModel.startCountDown(2) + mMapLiveViewModel.setupDeviceLiveStatus( + this@LiveActivityV2, deviceId, mLiveStatus + ) + } + } + } + } + + /** + * 更新蓝牙上报的数据 + */ + private fun updateBleReportData(bleReportDataBean: BleReportDataBean) { + mHomeMapCommon.startRefreshUserLocation() + bleReportDataBean.apply { + if (haveDeviceStateData) { + if (null == mMapDeviceBean) mMapDeviceBean = MapDeviceBean() + mMapDeviceBean?.let { + Util.bleReportDataToMapDevice(it, bleReportDataBean) + + val isOpen = ledSwitch > ConstantInt.Close + mViewBinding.ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.cbDeviceLightSwitch.isChecked = + isOpen + mViewBinding.ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.cbDeviceLightSwitch.isChecked = + isOpen + + if (haveLocationData && liveFlag == ConstantInt.Type1) { + if (!isStartLive) { + isStartLive = true + liveStartTip() + } + lifecycleScope.launch { + locationList?.let { location -> + liveDrawLines(location) + } + } + mHomeMapCommon.setMapDeviceBean(it) + } else if (liveFlag == ConstantInt.Type0) { + //直播自动结束 + if (isStartLive) { + XEventBus.post(EventName.LiveAutoEnd) + finish() + } + } + } + + mMapViewModel.setDeviceStateAndWarningData( + mContext, + mPetBean, + mMapDeviceBean!!, + mDeviceStateList, + mDeviceStateAdapter, + mDeviceMsgList, + mDeviceMsgAdapter, + mViewBinding.rvLiveV2MapDeviceMsg + ) + } + } + } + + /** + * 远程设置led开关 + */ + private fun setLedSwitch() { + isLedOpen = !isLedOpen + mViewBinding.apply { + //屏蔽点击就变状态 +// cbLiveV2DeviceLightSwitch.isChecked = !cbLiveV2DeviceLightSwitch.isChecked + ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.cbDeviceLightSwitch.isChecked = isLedOpen + ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.cbDeviceLightSwitch.isChecked = + isLedOpen + val ledSwitch = if (isLedOpen) ConstantInt.Open + else ConstantInt.Close + + setLedSwitch(ledSwitch) + mMapDeviceBean?.apply { + mTrackerManageViewModel.setLedSwitch(null, deviceServerId, ledSwitch) + } + } + } + + /** + *发送设备开启关闭led蓝牙指令 + */ + private fun setLedSwitch(mode: Int) { + mTrackBleDevice?.apply { + SRBleUtil.instance.setBleLedSwitch(mContext, bleDevice, mode) + } + } + + /** + * 更新设备连接状态 + */ + private fun updateConState(trackBleDevice: BleTrackDeviceBean) { + mTrackBleDevice = trackBleDevice + if (trackBleDevice.conState == ConState.CONNECTED) { + if (isLiveJump) { + if (isStartLive) { + mMapLiveViewModel.stopGetData() + } else { + mCountDownViewModel.startCountDown(2) + lifecycleScope.launch { + //延时几秒发送消息 + delay(1000) + trackBleDevice.apply { + SRBleUtil.instance.writeData( + bleDevice, SRBleCmdUtil.instance.setLiveSwitch(1, mLiveTime * 60L) + ) + } + } + } + } +// showToast(R.string.tracker_manage_set_ble_connect) +// mViewBinding.ilLiveV2BluetoothFindDevice.ivMapLiveV2BleConState.setValue(2) + } else if (trackBleDevice.conState == ConState.CONNECTION_FAIL) { + if (isLiveJump) { + openNetLive() + } + } else if (trackBleDevice.conState == ConState.DISCONNECTED) { + if (isLiveJump && isStartLive) { + mPetBean?.apply { + //直播开启后,就去获取直播轨迹 + mMapLiveViewModel.mGetPetLivePointTimeStamp = System.currentTimeMillis() / 1000 + mMapLiveViewModel.getPetLivePoint(deviceId) + } + } + } else { +// showToast(R.string.tracker_manage_set_ble_unconnect) +// mViewBinding.ilLiveV2BluetoothFindDevice.ivMapLiveV2BleConState.setValue(0) + } + } + + + /** + * 显示地图类型选择弹窗 + */ + private fun showMapTypeDialog() { + if (null == mSelectMapTypeDialog) { + mSelectMapTypeDialog = ViewUtil.instance.getMapTypeDialog( + mContext, object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + mMapType = Util.getMapTypeSp() + if (pos == mMapType) { + return + } + mHomeMapCommon.switchSatelliteAndNormalMapType() + } + }) { v, isChecked -> + if (v.id == R.id.cb_dialog_map_fences_switch) { + isShowFence = isChecked + MMKVUtil.putBoolean(MMKVKey.ShowFence, isChecked) + if (isChecked) { + mMapDeviceBean?.fences?.let { fences -> + mFencesMapViewModel.setFencesData(mContext, fences, mFragment) + } + } else { + mFencesMapViewModel.setFencesData(mContext, null, mFragment) + } + } + } + } else { + mSelectMapTypeDialog!!.mapTypeSpToUpdate() + } + mSelectMapTypeDialog?.show() + } + + /** + * 设置地图相关数据 + */ + private fun setMapData(mapDeviceBean: MapDeviceBean) { + mapDeviceBean.apply { + mLiveTime = liveTime / 60 + + mPetBean?.apply { + //查找是否连接了蓝牙 + val bleConDevice = SRBleUtil.instance.getConnectMacDevice(macID) + if (null == bleConDevice) { + if (isLiveJump) { + openNetLive() + //没有连接,一进入页面就尝试连接 + mConDeviceViewModel.connectDeviceToMac(this@LiveActivityV2, macID) + } + } else { + updateConState(bleConDevice) + } + } + + mViewBinding.let { + isLedOpen = ledSwitch == ConstantInt.Open + it.ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.cbDeviceLightSwitch.isChecked = + isLedOpen + it.ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.cbDeviceLightSwitch.isChecked = + isLedOpen +// it.tvLiveV2Speed.text = String.format( +// getString(R.string.txt_mph_unit), +// Utils.formatDecimal(Util.kmhToMph(speed.toDouble())) +// ) + } + + if (isShowFence) { + fences?.let { fences -> + mFencesMapViewModel.setFencesData(mContext, fences, mFragment) + } + } + + mHomeMapCommon.let { + it.setMapDeviceBean(this) + it.refreshPetCurrentLocation(latitude, longitude, isAnimMoveCamera) + it.startRippleCircleAnim() + isAnimMoveCamera = false +// val userAndPetDistance = it.getUserAndPetDistance() +// mViewBinding.tvLiveV2Distance.text = String.format( +// getString(R.string.txt_ft_unit), Utils.formatDecimal(userAndPetDistance) +// ) + } + } + } + + + /** + * 显示选择宠物弹窗 + */ + private fun showPetNameAndHead() { + mPetBean?.apply { + showLoading(true) + mViewBinding.llLiveV2MapTopPet.homeDataPetNameSmall.let { + it.text = petName + it.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0) + } + mViewBinding.llLiveV2MapTopPet.homeDataPetHeadSmall.appHeadImage.let { + ViewUtil.instance.setPetTypeHead(it, imgurl, petType) + ViewUtil.instance.viewAlphaAndRotationObjectAnimator(it) + } + //重新设置地图宠物头像 + mHomeMapCommon.setPetHeadIcon(imgurl, petType) + + //获取首页设备信息状态 + mMapViewModel.getMapDeviceStatus(deviceId, false) + } + } + + private fun setTrackData(liveDataBean: LiveDataBean) { + //测试经纬度移动 +// mFencesViewModel.setFencesData(mContext!!, isInFence, mFencesList, mFragment) + + mViewBinding.apply { + + liveDataBean.deviceInfo?.apply { + if (liveFlag == ConstantInt.Type0) { + //直播自动结束 + if (isStartLive) { + XEventBus.post(EventName.LiveAutoEnd) + finish() + } + return + } else if (liveFlag == ConstantInt.Type1) { + if (!isStartLive) { + liveStartTip() + isStartLive = true + } + } + + val speedStr = Util.metricConvertUnits( + speed, ConstantInt.Type2, 2 + ).toString() + mSpeedUnit + ilLiveV2OperateLayout.tvLiveV2Speed.text = speedStr + + mHomeMapCommon.setMapDeviceBean(this) + val isOpen = ledSwitch == ConstantInt.Open + ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.cbDeviceLightSwitch.isChecked = isOpen + ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.cbDeviceLightSwitch.isChecked = + isOpen + mMapViewModel.setDeviceStateAndWarningData( + mContext, + mPetBean, + this, + mDeviceStateList, + mDeviceStateAdapter, + mDeviceMsgList, + mDeviceMsgAdapter, + rvLiveV2MapDeviceMsg, + true + ) + + liveDataBean.livePoints?.apply { + if (size > 0) { + mMapLiveViewModel.mGetPetLivePointTimeStamp = + System.currentTimeMillis() / 1000 + //只有一个点的时候,不用休眠 + val sleepTime = + if (size - 1 > 0) mMapLiveViewModel.mRefreshTime / (size - 1) * 1000L + else 0 + lifecycleScope.launch { + forEach { + liveDrawLine(it.latitude, it.longitude) + //休眠几秒再画线 + if (sleepTime > 0) delay(sleepTime) + } + } + } + } + } + } + } + + /** + * 直播开始提示 + */ + private fun liveStartTip() { + LogUtil.e("直播开始了") +// mStartLiveIndex = 2 + //直播开始显示Starting 5-min Live +// mCountDownViewModel.startCountDown(2) + } + + /** + * 直播画线 + */ + private fun liveDrawLine(latitude: Double, longitude: Double) { + if (mStartLiveIndex == 0) { + mStartLiveIndex = 2 + //直播开始显示Starting 5-min Live + mCountDownViewModel.startCountDown(1) + } + mHomeMapCommon.addTrackLine(latitude, longitude) + val userAndPetDistance = mHomeMapCommon.getUserAndPetDistance() + val distanceStr = Util.metricConvertUnits( + userAndPetDistance.toFloat(), ConstantInt.Type3, 2 + ).toString() + mDistanceUnit + mViewBinding.ilLiveV2OperateLayout.tvLiveV2Distance.text = distanceStr + } + + /** + * 直播画线 + */ + private fun liveDrawLines(trackList: MutableList) { + if (mStartLiveIndex == 0) { + mStartLiveIndex = 2 + //直播开始显示Starting 5-min Live + mCountDownViewModel.startCountDown(1) + } + mHomeMapCommon.addTrackLines(trackList) + val userAndPetDistance = mHomeMapCommon.getUserAndPetDistance() + val distanceStr = Util.metricConvertUnits( + userAndPetDistance.toFloat(), ConstantInt.Type3, 2 + ).toString() + mDistanceUnit + mViewBinding.ilLiveV2OperateLayout.tvLiveV2Distance.text = distanceStr + } + + /** + * 显示隐藏找蓝牙设备布局 + */ + private fun showAndHideFindLayout() { + mViewBinding.apply { + miLiveV2MapAddressView.visibility = View.GONE + val relativeLayout = ivMapLiveV2RefreshBtn.layoutParams as RelativeLayout.LayoutParams + ivMapLiveV2RefreshBtn.visibility = View.GONE + if (ilLiveV2BluetoothFindDevice.root.visibility == View.GONE) { + //找蓝牙设备布局显示 + YoYo.with(Techniques.BounceInUp).duration(800).onStart { + ilLiveV2OperateLayout.root.visibility = View.GONE + ilLiveV2BluetoothFindDevice.root.visibility = View.VISIBLE + }.onEnd { + //刷新定位按钮显示动画 + ViewUtil.instance.viewShowFadeInAnimation(ivMapLiveV2RefreshBtn) + }.playOn(ilLiveV2BluetoothFindDevice.root) + + ivMapLiveV2BluetoothBtn.visibility = View.GONE + relativeLayout.removeRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + relativeLayout.addRule(RelativeLayout.ABOVE, R.id.il_live_v2_bluetooth_find_device) + } else { + //找蓝牙设备布局退出 + YoYo.with(Techniques.SlideOutDown).duration(600).onEnd { + ilLiveV2BluetoothFindDevice.root.visibility = View.GONE + ilLiveV2OperateLayout.root.visibility = View.VISIBLE + }.playOn(ilLiveV2BluetoothFindDevice.root) + //直播操作按钮布局显示 + YoYo.with(Techniques.BounceInUp).duration(800).delay(400).onEnd { + //蓝牙图标按钮显示动画 + ViewUtil.instance.viewShowFadeInAnimation(ivMapLiveV2BluetoothBtn) + //刷新定位按钮显示动画 + ViewUtil.instance.viewShowFadeInAnimation(ivMapLiveV2RefreshBtn) + }.playOn(ilLiveV2OperateLayout.root) + + relativeLayout.removeRule(RelativeLayout.ABOVE) + relativeLayout.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM) + } + + root.postDelayed({ + miLiveV2MapAddressView.visibility = View.VISIBLE + mHomeMapCommon.setMarkerInfoViewOffset() + }, 1000) + } + } + + /** + * 显示直播问题 + */ + private fun showLiveIssue() { + mViewBinding.apply { + miLiveV2MapAddressView.visibility = View.GONE + if (ilMapLiveV2IssueLayout.root.visibility == View.GONE) { + YoYo.with(Techniques.BounceInUp).duration(700).onStart { + ilLiveV2OperateLayout.root.visibility = View.GONE + ilMapLiveV2IssueLayout.root.visibility = View.VISIBLE + }.playOn(ilMapLiveV2IssueLayout.root) + } else { + YoYo.with(Techniques.SlideOutDown).duration(600).onEnd { + ilMapLiveV2IssueLayout.root.visibility = View.GONE + YoYo.with(Techniques.BounceInUp).duration(600).onStart { + ilLiveV2OperateLayout.root.visibility = View.VISIBLE + }.playOn(ilLiveV2OperateLayout.root) + }.playOn(ilMapLiveV2IssueLayout.root) + } + + root.postDelayed({ + miLiveV2MapAddressView.visibility = View.VISIBLE + mHomeMapCommon.setMarkerInfoViewOffset() + }, 1000) + } + } + + /** + * 地图加载好了 + */ + private fun mapLoadOk() { + showPetNameAndHead() + } + + /** + * 找蓝牙设备 + */ + private fun findBleDevice(mac: String) { + Util.checkBluetoothPermissionsEnabled(mContext, { + showAndHideFindLayout() + mViewBinding.ilLiveV2BluetoothFindDevice.apply { + mFindBleDeviceViewModel.startScanFind( + this@LiveActivityV2, + mac.uppercase(), + liveV2BluetoothSignal, + findBleDeviceRadoView, + ivMapLiveV2BleConState + ) + } + }) + } + + // override fun onBackPressed() { +// stopMapLive() +// } + override fun listenBackPressed() { + if (isLiveJump) stopMapLive() + else super.listenBackPressed() + } + + private fun stopMapLive() { + CommonDialog1( + mContext, + "", + getString(R.string.txt_stop_live_tips), + false, + 5f, + Typeface.BOLD, + R.color.data_black_color, + canceledOnTouchOutside = true, + okClickListener = object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + mMapDeviceBean?.apply { + mLiveStatus = 0 + stopLive() + } + } + }).show() + } + + /** + * 停止直播 + */ + private fun stopLive() { + mTrackBleDevice?.apply { + SRBleUtil.instance.writeData( + bleDevice, SRBleCmdUtil.instance.setLiveSwitch(0, 0) + ) + } + mMapLiveViewModel.setupDeviceLiveStatus( + this@LiveActivityV2, mPetBean!!.deviceId, 0 + ) + } + + /** + * 设置找设备声音音量 + */ + private fun setSound() { + if (isOpenSound) { + mFindBleDeviceViewModel.setVolume(0) + mViewBinding.ilLiveV2BluetoothFindDevice.ivLiveV2FindDeviceSound.setImageResource(R.drawable.icon_sound_off_svg) + } else { + mFindBleDeviceViewModel.setVolume(mFindBleDeviceViewModel.mMaxVolume) + mViewBinding.ilLiveV2BluetoothFindDevice.ivLiveV2FindDeviceSound.setImageResource(R.drawable.icon_sound_on_svg) + } + isOpenSound = !isOpenSound + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ivMapLiveV2RefreshBtn -> { + mShowCenterLocation = if (mShowCenterLocation == ConstantInt.PetLocationType) { + ConstantInt.UserLocationType + } else { + ConstantInt.PetLocationType + } + ViewUtil.instance.setMapSwitchLocationButtonImage( + ivMapLiveV2RefreshBtn, mShowCenterLocation + ) + mHomeMapCommon.switchShowLocation(mShowCenterLocation) + } + + ivMapLiveV2BluetoothBtn -> mPetBean?.apply { + findBleDevice(macID) + } + + ilLiveV2BluetoothFindDevice.btnLiveV2StopRadar -> { + if (isLiveJump) { + showAndHideFindLayout() + mFindBleDeviceViewModel.stopFindDevice() + } else { + finish() + } + } + + llLiveV2MapTopPet.ivTopPetBtnSmall -> showMapTypeDialog() + ilLiveV2OperateLayout.llLiveV2StopLive -> stopMapLive() + ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.root, ilLiveV2OperateLayout.ilLiveV2DataLightSwitch.cbDeviceLightSwitch, ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.cbDeviceLightSwitch, ilLiveV2BluetoothFindDevice.ilLiveV2RadarLightSwitch.root -> setLedSwitch() + + ilLiveV2OperateLayout.btnLiveV2Issue -> showLiveIssue() + ilMapLiveV2IssueLayout.tvCloseIssueBtn -> showLiveIssue() + ilLiveV2BluetoothFindDevice.ivLiveV2FindDeviceSound -> setSound() + } + } + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.lavLiveV2StateAnim.cancelAnimation() +// stopLive() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/BadgeDetailActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/BadgeDetailActivity.kt new file mode 100644 index 0000000..5e03d8d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/BadgeDetailActivity.kt @@ -0,0 +1,58 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityBadgeDetailBinding +import com.abbidot.tracker.util.Util + +class BadgeDetailActivity : + BaseActivity(ActivityBadgeDetailBinding::inflate) { + + private var mDataBean: DataBean? = null + + override fun getTopBar() = mViewBinding.petBadgesDetailTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.pet_manage_detail_title2) + + intent.extras?.apply { + mDataBean = + Util.getParcelableAdaptive(intent, ConstantString.PetLabels, DataBean::class.java) + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + mDataBean?.let { + setTopBarTitle(it.name) + tvPetBadgesDetailDec.text = it.unit + ivPetBadgesDetailImage.setImageResource(it.imageId) + + if (it.state == ConstantInt.Open) { + ivPetBadgesDetailImage.setBackgroundResource(R.drawable.icon_badges_unlock_bg_svg) + tvPetBadgesDetailState.setText(R.string.txt_congratulations) + tvPetBadgesDetailUnlockTime.text = it.value + tvPetBadgesDetailUnlockTime.visibility = View.VISIBLE + } else { + ivPetBadgesDetailImage.setBackgroundResource(0) + tvPetBadgesDetailState.setText(R.string.txt_how_to_get) + tvPetBadgesDetailUnlockTime.visibility = View.INVISIBLE + } + } + + setOnClickListenerViews(btnPetBadgesDetailOk) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPetBadgesDetailOk -> finish() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/BadgesListActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/BadgesListActivity.kt new file mode 100644 index 0000000..3aee50a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/BadgesListActivity.kt @@ -0,0 +1,66 @@ +package com.abbidot.tracker.ui.activity.pet + +import androidx.activity.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.PetsParentBadgesAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.ParentBadgeBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityBadgesListBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.PetInfoViewModel + +class BadgesListActivity : + BaseActivity(ActivityBadgesListBinding::inflate) { + + private val mPetViewModel: PetInfoViewModel by viewModels() + + private lateinit var mParentBadgesAdapter: PetsParentBadgesAdapter + + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.petBadgesListTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_badges) + + intent.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + val parentBadges = mutableListOf() + ParentBadgeBean().apply { + parentName = getString(R.string.txt_adventure) + + val badges = if (null==mPetBean) mPetViewModel.getBadges(mContext, "") + else mPetViewModel.getBadges(mContext, mPetBean!!.createDate) +// for (i in 0 until 23) badges.add(DataBean()) + badgeList = badges + parentBadges.add(this) + } +// ParentBadgeBean().apply { +// parentName = getString(R.string.txt_social) +// val badges = mutableListOf() +// for (i in 0 until 3) badges.add(DataBean()) +// badgeList = badges +// parentBadges.add(this) +// } +// ParentBadgeBean().apply { +// parentName = getString(R.string.txt_sporty) +// val badges = mutableListOf() +// for (i in 0 until 3) badges.add(DataBean()) +// badgeList = badges +// parentBadges.add(this) +// } + mParentBadgesAdapter = PetsParentBadgesAdapter(mContext, parentBadges) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvPetParentBadgesList, mParentBadgesAdapter + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/FamilyMembersActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/FamilyMembersActivity.kt new file mode 100644 index 0000000..454f43b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/FamilyMembersActivity.kt @@ -0,0 +1,135 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.FamilyMembersAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityFamilyMembersBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FamilyViewModel + +/** + * 家人成员 + */ +class FamilyMembersActivity : + BaseActivity(ActivityFamilyMembersBinding::inflate) { + + private val mFamilyViewModel: FamilyViewModel by viewModels() + + private lateinit var mFamilyMembersAdapter: FamilyMembersAdapter + + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.familyMembersTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_members) + + intent.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mFamilyMembersAdapter = FamilyMembersAdapter(mContext, null) + + mViewBinding.apply { + + mPetBean?.let { + ViewUtil.instance.setPetTypeHead( + ilFamilyMembersPetHead.appHeadImage, it.imgurl, it.petType + ) + mFamilyViewModel.getFamilies(this@FamilyMembersActivity, it.deviceId) + } + + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvFamilyMembers, mFamilyMembersAdapter, bottom = AppUtils.dpToPx(12) + ) + setOnClickListenerViews(btnFamilyMembersAddInvite) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshFamily) { + mPetBean?.apply { + mFamilyViewModel.getFamilies(this@FamilyMembersActivity, deviceId) + } + } + + mFamilyViewModel.apply { + mFamilyListLiveData.observe(this@FamilyMembersActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mFamilyMembersAdapter.setData(null) + it.getOrNull()?.let { familyPet -> + familyPet.families?.let { list -> + mFamilyMembersAdapter.setData(list, true) + } + } + } + }) + } + + mFamilyRemoveLiveData.observe(this@FamilyMembersActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.DeleteFamily) + showToast(R.string.txt_delete_success) + mPetBean?.apply { + mFamilyViewModel.getFamilies(this@FamilyMembersActivity, deviceId) + } + } + }) + } + + mInviteLiveData.observe(this@FamilyMembersActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.tracker_manage_set_add_family_succ) + } + }) + } + } + } + + fun deleteFamily(familyBean: FamilyBean) { + val str = String.format( + mContext.getString(R.string.txt_family_delete), familyBean.acceptEmail + ) + ViewUtil.instance.showDialog(mContext, str, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mFamilyViewModel.removeFamily(this@FamilyMembersActivity, familyBean.familyId) + } + }, okTextResId = R.string.start_txt_yes) + } + + fun resendFamily(familyBean: FamilyBean) { + mFamilyViewModel.invite(this, familyBean) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnFamilyMembersAddInvite -> { + mPetBean?.let { pet -> + Intent(mContext, InviteFamilyActivity::class.java).let { + it.putExtra(ConstantString.Pet, pet) + startActivity(it) + } + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/InviteFamilyActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/InviteFamilyActivity.kt new file mode 100644 index 0000000..5ba8876 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/InviteFamilyActivity.kt @@ -0,0 +1,184 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.text.InputType +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.FamilyAvatarAdapter +import com.abbidot.tracker.adapter.FamilyRelationAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityInviteFamilyBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.FamilyViewModel + +/** + * 邀请家人 + */ +class InviteFamilyActivity : + BaseActivity(ActivityInviteFamilyBinding::inflate) { + + private val mFamilyViewModel: FamilyViewModel by viewModels() + + private lateinit var mRelationAdapter: FamilyRelationAdapter + private lateinit var mAvatarAdapter: FamilyAvatarAdapter + private lateinit var mFamilyBean: FamilyBean + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.inviteFamilyTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.tracker_manage_set_add_family_invite) + + mFamilyBean = FamilyBean().apply { + userId = MMKVUtil.getString(MMKVKey.UserId) + userName = MMKVUtil.getString(MMKVKey.UserName) + sendEmail = MMKVUtil.getString(MMKVKey.Email) + } + + intent.extras?.apply { + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mPetBean?.apply { + mFamilyBean.let { + it.deviceId = deviceId + it.petId = petId + it.petName = petName + } + } + + mViewBinding.apply { + ilInviteFamilyEmail.let { + it.tvInputNameTitle.text = getString(R.string.txt_email_address) + it.etInputContent.setHint(R.string.txt_email_address) + it.etInputContent.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + ViewUtil.instance.setInputViewErrorHideClick( + mContext, it.rlEtInputLayout, tvEmailErrorTipInviteFamily, it.etInputContent + ) + } + + setData() + + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, rvInviteFamilyRelation, mRelationAdapter, right = AppUtils.dpToPx(12) + ) + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, rvInviteFamilyAvatar, mAvatarAdapter, right = AppUtils.dpToPx(16) + ) + + setViewClickHideInputMethod(vInviteFamilyAnimate) + setViewClickHideInputMethod(llInviteFamilyLayout) + setViewClickHideInputMethod(rvInviteFamilyAvatar) + setOnClickListenerViews(btnInviteFamilyInvite) + } + + setListenKeyboardChange(210) + } + + override fun liveDataObserve() { + mFamilyViewModel.mInviteLiveData.observe(this) { + mViewBinding.btnInviteFamilyInvite.isEnabled = true + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshFamily) + showToast(R.string.tracker_manage_set_add_family_succ, true) + } + }) + } + } + + private fun setData() { + val relationList = mutableListOf() + DataBean().apply { + name = getString(R.string.tracker_manage_set_family) + selected = true + relationList.add(this) + } + DataBean().apply { + name = getString(R.string.txt_friend) + relationList.add(this) + } + DataBean().apply { + name = getString(R.string.txt_love) + relationList.add(this) + } + DataBean().apply { + name = getString(R.string.txt_colleague) + relationList.add(this) + } + mRelationAdapter = FamilyRelationAdapter(mContext, relationList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mRelationAdapter, relationList, pos + ) + mFamilyBean.relation = pos + 1 + } + }) + } + + val avatarList = mutableListOf() + DataBean().apply { + imageId = R.drawable.icon_family_boy_avatar + selected = true + avatarList.add(this) + } + DataBean().apply { + imageId = R.drawable.icon_family_girl_avatar + avatarList.add(this) + } + mAvatarAdapter = FamilyAvatarAdapter(mContext, avatarList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + hideInputMethod(mViewBinding.root) + ViewUtil.instance.updateDataBeanOnlySelectedItem( + mAvatarAdapter, avatarList, pos + ) + mFamilyBean.avatar = pos + 1 + } + }) + } + } + + private fun inviteOk() { + mViewBinding.apply { + val email = ilInviteFamilyEmail.etInputContent.text.toString() + if (TextUtils.isEmpty(email) || !Utils.isValidEmail(email)) { + ViewUtil.instance.setInputViewErrorShow( + mContext, ilInviteFamilyEmail.rlEtInputLayout, tvEmailErrorTipInviteFamily + ) + return + } + mFamilyBean.acceptEmail = email + btnInviteFamilyInvite.isEnabled = false + mFamilyViewModel.invite(this@InviteFamilyActivity, mFamilyBean) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnInviteFamilyInvite -> inviteOk() + } + } + } + + override fun onDestroy() { + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetHeightActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetHeightActivity.kt new file mode 100644 index 0000000..b59f228 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetHeightActivity.kt @@ -0,0 +1,176 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.content.Intent +import android.os.Build +import android.view.View +import android.widget.NumberPicker +import android.widget.NumberPicker.OnValueChangeListener +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityPetHeightBinding +import com.abbidot.tracker.util.NumberPickerValueFill0Format +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class PetHeightActivity : BaseActivity(ActivityPetHeightBinding::inflate), + OnValueChangeListener { + + private var mPetBean: PetBean? = null + private var isFirstBind = false + + override fun getTopBar() = mViewBinding.ilPetHeightTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.acccount_height) + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + var maxTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.HeightType, ConstantInt.MaxType + ) + var minTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.HeightType, ConstantInt.MinType + ) + var defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + ConstantInt.DogPetType, ConstantInt.HeightType + ) + var defaultValue2 = 0 + mPetBean?.apply { + if (petType == ConstantInt.CatPetType) mViewBinding.ivSetHeightPetType.setImageResource( + R.drawable.icon_cat_height_tip_svg + ) + defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + petType, ConstantInt.HeightType + ) + minTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.HeightType, ConstantInt.MinType + ) + maxTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.HeightType, ConstantInt.MaxType + ) + if (height > 0) { + var convertHeight = Util.metricConvertUnits(height, ConstantInt.Type0) + if (convertHeight < minTensValue) { + convertHeight = minTensValue.toFloat() + } + val values = convertHeight.toString().split(".") + defaultValue1 = values[0].toInt() + defaultValue2 = values[1].toInt() + } + } + + mViewBinding.apply { + if (isFirstBind) btnPetHeightSave.setText(R.string.register_txt_next) + root.setBackgroundResource(R.color.white) + tvShowHeightUnit.text = Util.getMetricUnits(mContext, ConstantInt.Type0) + npDialogPetHeightTens.let { +// ReflectHelper.of(it).setField("SELECTOR_WHEEL_ITEM_COUNT", 2) +// // 获取Class实例 +// val clazz = it.javaClass +// // 获取Field实例 +// val field = clazz.getDeclaredField("SELECTOR_WHEEL_ITEM_COUNT") +// // 提升访问权限 +// field.isAccessible = true +// // 修改字段值 +// field.set(it, 2) + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + minTensValue, + maxTensValue, + defaultValue1, + 20, + NumberPickerValueFill0Format(), + this@PetHeightActivity + ) + } + + val valueList = mutableListOf() + for (i in 0..9) { + valueList.add("$i") + } + npDialogPetHeightUnit.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + 0, + 9, + defaultValue2, + 20, + valueChangeListener = this@PetHeightActivity + ) + it.displayedValues = valueList.toTypedArray() + } + npDialogHeightUnitSwitch.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, it, 0, 1, 0, 20 + ) + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + it.selectionDividerHeight = 0 + } + val unitArray = arrayOf(getString(R.string.txt_in), getString(R.string.txt_cm)) + it.displayedValues = unitArray + } + + setOnClickListenerViews(btnPetHeightSave) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshPet) { + finish() + } + } + + private fun saveHeight() { + val value = + "${mViewBinding.npDialogPetHeightTens.value}.${mViewBinding.npDialogPetHeightUnit.value}" + if (isFirstBind) { + mPetBean?.apply { + height = Util.convertMetricUnits(value.toFloat(), ConstantInt.Type0) + val intent = Intent(mContext, PetWeightActivity::class.java) + intent.putExtra(ConstantString.Pet, this) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + } else { +// val petHeight = Util.convertMetricUnits(mContext, value.toFloat(), ConstantInt.Type0) + val intent = Intent() + intent.putExtra(ConstantString.PetHeight, value.toFloat()) + setResult(ResultCode.ResultCode_2, intent) + finish() + } + } + + override fun onValueChange(picker: NumberPicker, oldVal: Int, newVal: Int) { + mViewBinding.apply { + when (picker) { + npDialogPetHeightTens -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npDialogPetHeightUnit, btnPetHeightSave + ) + + npDialogPetHeightUnit -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npDialogPetHeightTens, btnPetHeightSave + ) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnPetHeightSave -> saveHeight() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetProfileActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetProfileActivity.kt new file mode 100644 index 0000000..6cfcbf8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetProfileActivity.kt @@ -0,0 +1,600 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import coil.load +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.PetLabelsAdapter +import com.abbidot.tracker.adapter.TextViewAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.LabelBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityPetProfileBinding +import com.abbidot.tracker.dialog.CommonListDialog +import com.abbidot.tracker.dialog.SelectPetLabelDialog +import com.abbidot.tracker.dialog.SetPetWeightDialog +import com.abbidot.tracker.dialog.ShowCalenderDialog +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.PetInfoViewModel +import com.abbidot.tracker.vm.TakePhotoAndCompressViewModel +import com.abbidot.tracker.vm.UserProfileViewModel +import com.haibin.calendarview.Calendar +import com.huantansheng.easyphotos.EasyPhotos +import com.huantansheng.easyphotos.models.album.entity.Photo + +/** + * 宠物Profile + */ +class PetProfileActivity : + BaseActivity(ActivityPetProfileBinding::inflate), + BaseDialog.OnDialogOkListener { + + private val mPetViewModel: PetInfoViewModel by viewModels() + private val mTakePhotoAndCompressViewModel: TakePhotoAndCompressViewModel by viewModels() + private val mUserInfoViewModel: UserProfileViewModel by viewModels() + + private var mProfileTypeDialog: CommonListDialog? = null + private lateinit var mTypeAdapter: TextViewAdapter + private var mBirthdayDialog: ShowCalenderDialog? = null + private var mProfileSexDialog: CommonListDialog? = null + private lateinit var mSexAdapter: TextViewAdapter + private var mSetWeightDialog: SetPetWeightDialog? = null + + private var mSelectLabelDialog: SelectPetLabelDialog? = null + private lateinit var mLabelsAdapter: PetLabelsAdapter + private lateinit var mSelectLabelList: MutableList + private lateinit var mSelectLabelsAdapter: PetLabelsAdapter + + private var mPetBean: PetBean? = null + private lateinit var mOldPetBean: PetBean + + //选取头像本地路径 + private var mSelectPetHeadPath = "" + + //是否编辑 + private var isEdit = false + + override fun getTopBar() = mViewBinding.petProfileTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_pet_profile) + + intent?.extras?.apply { + isEdit = getBoolean(ConstantString.Edit, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + if (null == mPetBean) mPetBean = PetBean() + + mOldPetBean = mPetBean!!.copy() + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + ilPetProfileType.let { + it.tvInputNameTitle.text = getString(R.string.txt_type) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + } + ilPetProfileBreed.let { + it.tvInputNameTitle.text = getString(R.string.acccount_breed) + } + ilPetProfileBirthday.let { + it.tvInputNameTitle.text = getString(R.string.acccount_birthday) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.ivInputRepresentImage.setImageResource(R.drawable.icon_profile_birthday_edit_svg) + } + ilPetProfileSex.let { + it.tvInputNameTitle.text = getString(R.string.txt_sex) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + } + ilPetProfileWeight.let { + it.tvInputNameTitle.text = getString(R.string.acccount_weight) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.tvInputUnitDescribe.visibility = View.VISIBLE + it.tvInputUnitDescribe.text = Util.getMetricUnits(mContext, ConstantInt.Type1) + } + ilPetProfileHeight.let { + it.tvInputNameTitle.text = getString(R.string.acccount_height) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.tvInputUnitDescribe.visibility = View.VISIBLE + it.tvInputUnitDescribe.text = Util.getMetricUnits(mContext, ConstantInt.Type0) + } + + setListData() + mSelectLabelList = mutableListOf() + mSelectLabelsAdapter = PetLabelsAdapter(mContext, mSelectLabelList) + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, + rvPetProfileLabel, + mSelectLabelsAdapter, + right = AppUtils.dpToPx(6), + bottom = AppUtils.dpToPx(8) + ) + + setOnClickListenerViews( + ilPetProfileType.root, + ilPetProfileType.ivInputRepresentImage, + ilPetProfileBirthday.root, + ilPetProfileBirthday.ivInputRepresentImage, + ilPetProfileSex.root, + ilPetProfileSex.ivInputRepresentImage, + ilPetProfileWeight.root, + ilPetProfileWeight.ivInputRepresentImage, + ilPetProfileHeight.root, + ilPetProfileHeight.ivInputRepresentImage, + rlPetProfileLabels, + ilPetProfileHeadLayout.ilPetProfileHead.appHeadImage, + btnPetProfileSave + ) + + setViewClickHideInputMethod(llPetProfileHeadLayout) + setViewClickHideInputMethod(ilPetProfileHeadLayout.root) + } + + mTakePhotoAndCompressViewModel.registerCropImageActivityResult(this) + + setPetData() + mPetViewModel.getPetLabels(this) + + setListenKeyboardChange() + } + + override fun liveDataObserve() { + mPetViewModel.apply { + mPetLabelsLiveData.observe(this@PetProfileActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { labels -> + val labelList = mutableListOf() + for (i in 0 until labels.size) { + LabelBean().run { + name = labels[i] + if (mSelectLabelList.size > 0) { + for (label in mSelectLabelList) { + if (name == label.name) check = true + } + } + labelList.add(this) + } + } + mLabelsAdapter.setData(labelList, true) + } + } + }) + } + + mAddPetLiveData.observe(this@PetProfileActivity) { + setButtonEnabled(mViewBinding.btnPetProfileSave, ConstantInt.Type1) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshPet) + showToast(R.string.txt_add_successful, isFinish = true) + } + }) + } + + mUpdatePetInfoLiveData.observe(this@PetProfileActivity) { + setButtonEnabled(mViewBinding.btnPetProfileSave, ConstantInt.Type1) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshPet) + showToast(R.string.txt_change_successful, isFinish = true) + } + }) + } + } + + mTakePhotoAndCompressViewModel.mPhotoPathLiveData.observe(this) { + if (it.size > 0) { + mSelectPetHeadPath = it[0].path + mViewBinding.ilPetProfileHeadLayout.ilPetProfileHead.appHeadImage.load(it[0]) + } + } + + mUserInfoViewModel.mUploadImageLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { +// showToast(R.string.txt_upload_successful) + mPetBean?.imgurl = this + addAndUpdatePetProfile() + } + } + }) + } + } + + override fun leftBackOnClick() { + var name = mViewBinding.ilPetProfileNameLayout.etInputContent.text.toString() + val breed = mViewBinding.ilPetProfileBreed.etInputContent.text.toString() + name = name.trimStart() + name = name.trimEnd() + mPetBean?.let { + it.petName = name + it.breed = breed + + if (mOldPetBean.hasSameContent(it)) super.leftBackOnClick() + else ViewUtil.instance.showDialog( + mContext, + R.string.txt_weather_save, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + savePetProfile() + } + }, + okTextResId = R.string.txt_sure, + cancelListener = object : BaseDialog.OnDialogCancelListener { + override fun onCancelClick(dialog: BaseDialog<*>) { + dialog.dismiss() + finish() + } + }) + } + } + + private fun setPetData() { + mViewBinding.apply { + mPetBean?.let { + ViewUtil.instance.setPetTypeHead( + ilPetProfileHeadLayout.ilPetProfileHead.appHeadImage, it.imgurl, it.petType + ) + + if (isEdit) { + ilPetProfileNameLayout.etInputContent.setText(it.petName) + ilPetProfileType.etInputContent.setText( + mPetViewModel.getTypeName( + mContext, it.petType + ) + ) + ilPetProfileBreed.etInputContent.setText(it.breed) + ilPetProfileSex.etInputContent.setText( + mPetViewModel.getSexName( + mContext, it.gender + ) + ) + ilPetProfileBirthday.etInputContent.setText( + Utils.stringToDate( + it.birthdayDate, + Utils.DATE_FORMAT_PATTERN_CN, + Utils.DATE_FORMAT_PATTERN_EN3 + ) + ) + ilPetProfileWeight.etInputContent.setText( + Util.metricConvertUnits( + it.weight, ConstantInt.Type1 + ).toString() + ) + ilPetProfileHeight.etInputContent.setText( + Util.metricConvertUnits( + it.height, ConstantInt.Type0 + ).toString() + ) + mSelectLabelList = mPetViewModel.getLabelData(it.labels) + mSelectLabelsAdapter.setData(mSelectLabelList, true) + } + } + } + } + + + private fun setListData() { + val typeList = mutableListOf() + DataBean().run { + name = getString(R.string.txt_dog) + menuType = ConstantInt.DogPetType + typeList.add(this) + } + DataBean().run { + name = getString(R.string.txt_cat) + menuType = ConstantInt.CatPetType + typeList.add(this) + } + DataBean().run { + name = getString(R.string.txt_other) + menuType = ConstantInt.OtherPetType + typeList.add(this) + } + mTypeAdapter = TextViewAdapter(mContext, typeList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val dataBean = typeList[pos] + mPetBean?.apply { + petType = dataBean.menuType + if (TextUtils.isEmpty(mSelectPetHeadPath) && TextUtils.isEmpty(imgurl)) ViewUtil.instance.setPetTypeHead( + mViewBinding.ilPetProfileHeadLayout.ilPetProfileHead.appHeadImage, + imgurl, + petType + ) + mSetWeightDialog?.updatePetData(this) + } + mViewBinding.ilPetProfileType.etInputContent.setText(dataBean.name) + mProfileTypeDialog?.dismiss() + } + }) + } + + val sexList = mutableListOf() + DataBean().run { + name = getString(R.string.pet_manage_gender_male) + menuType = ConstantInt.Man + sexList.add(this) + } + DataBean().run { + name = getString(R.string.pet_manage_gender_female) + menuType = ConstantInt.WoMan + sexList.add(this) + } + mSexAdapter = TextViewAdapter(mContext, sexList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val dataBean = sexList[pos] + mPetBean?.apply { + gender = dataBean.menuType + } + mViewBinding.ilPetProfileSex.etInputContent.setText(dataBean.name) + mProfileSexDialog?.dismiss() + } + }) + } + + mLabelsAdapter = PetLabelsAdapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + if (mLabelsAdapter.getData().isNotEmpty()) { + val label = mLabelsAdapter.getData()[pos] + label.check = !label.check + notifyItemChanged(pos) + } + } + }) + } + } + + /** + * 显示已选的标签 + */ + private fun showSelectPetLabels() { + val labelList = mLabelsAdapter.getData() + mSelectLabelList.clear() + mSelectLabelsAdapter.notifyItemRangeChanged(0, mSelectLabelList.size) + for (lab in labelList) { + if (lab.check) { + LabelBean().apply { + name = lab.name + mSelectLabelList.add(this) + } + } + } + mSelectLabelsAdapter.setData(mSelectLabelList, true) + + val textColor = if (mSelectLabelList.size > 0) ContextCompat.getColor( + mContext, R.color.data_black_color + ) + else ContextCompat.getColor(mContext, R.color.block_color2) + mViewBinding.tvPetProfileLabelTitle.setTextColor(textColor) + + mViewBinding.svPetProfile.fullScroll(View.FOCUS_DOWN) + + mPetBean?.apply { + labels = if (mSelectLabelList.size > 0) mSelectLabelList.joinToString(",") { it.name } + else "" + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (RESULT_OK == resultCode) { + //相机或相册回调 + if (requestCode == ResultCode.ResultCode_1 && null != data) { + //返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + val resultPhotos: ArrayList = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)!! + //返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取 +// data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false) + mTakePhotoAndCompressViewModel.goCropAndCompressImage(resultPhotos) + } + } + } + + private val mActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (null != it.data) { + if (it.resultCode == ResultCode.ResultCode_2) { + //接收身高设置页面的值 + it.data?.extras?.getFloat(ConstantString.PetHeight)?.apply { + mViewBinding.ilPetProfileHeight.etInputContent.setText("$this") + mPetBean?.height = Util.convertMetricUnits(this, ConstantInt.Type0) + } + } else if (it.resultCode == ResultCode.ResultCode_3) { + //接收体重设置页面的值 + it.data?.extras?.getFloat(ConstantString.PetWeight)?.apply { + mViewBinding.ilPetProfileWeight.etInputContent.setText("$this") + mPetBean?.weight = Util.convertMetricUnits(this, ConstantInt.Type1) + } + } + } + } + + /** + * 显示宠物类型弹窗 + */ + private fun showPetTypeDialog() { + if (null == mProfileTypeDialog) { + mProfileTypeDialog = CommonListDialog(mContext, mTypeAdapter, 3, 3) + } + mProfileTypeDialog?.show() + } + + /** + * 显示生日弹窗 + */ + private fun showBirthdayDialog() { + if (null == mBirthdayDialog) { + val calendar = java.util.Calendar.getInstance() + val cYear = calendar.get(java.util.Calendar.YEAR) + var cShowTimestamp = 0L + mPetBean?.apply { + cShowTimestamp = Utils.stringToTimestamp(birthdayDate, Utils.DATE_FORMAT_PATTERN_CN) + } + mBirthdayDialog = ShowCalenderDialog( + mContext, object : BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is Calendar) { + val selectBirthday = Utils.formatTime( + any.timeInMillis, Utils.DATE_FORMAT_PATTERN_EN3 + ) + mViewBinding.ilPetProfileBirthday.etInputContent.setText( + selectBirthday + ) + mPetBean?.apply { + birthdayDate = Utils.formatTime( + any.timeInMillis, Utils.DATE_FORMAT_PATTERN_CN + ) + } + } + } + }, minYear = cYear - 20, calenderShowTimestamp = cShowTimestamp + ) + } + mBirthdayDialog?.show() + } + + /** + * 显示性别弹窗 + */ + private fun showSexDialog() { + if (null == mProfileSexDialog) { + mProfileSexDialog = CommonListDialog(mContext, mSexAdapter, 3, 3) + } + mProfileSexDialog?.show() + } + + /** + * 显示体重弹窗 + */ + private fun showWeightDialog() { + mPetBean?.apply { + if (null == mSetWeightDialog) { + mSetWeightDialog = + SetPetWeightDialog(mContext, this, object : BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is String) { + mViewBinding.ilPetProfileWeight.etInputContent.setText(any) + weight = Util.convertMetricUnits( + any.toFloat(), ConstantInt.Type1 + ) + } + } + }) + } + mSetWeightDialog?.show() + } + } + + /** + * 显示标签弹窗 + */ + private fun showLabelDialog() { + if (null == mSelectLabelDialog) { + mSelectLabelDialog = SelectPetLabelDialog(mContext, mLabelsAdapter).apply { + setOkListener(this@PetProfileActivity) + } + } + mSelectLabelDialog?.show() + } + + private fun addAndUpdatePetProfile() { + mPetBean?.apply { + if (isEdit) { + mPetViewModel.updatePetInfo(this@PetProfileActivity, this) + } else { + mPetViewModel.addPet(this@PetProfileActivity, this) + } + } + } + + private fun savePetProfile() { + mViewBinding.apply { + var name = ilPetProfileNameLayout.etInputContent.text.toString() + val type = ilPetProfileType.etInputContent.text.toString() + val breed = ilPetProfileBreed.etInputContent.text.toString() + val birthday = ilPetProfileBirthday.etInputContent.text.toString() + val sex = ilPetProfileSex.etInputContent.text.toString() + val weight = ilPetProfileWeight.etInputContent.text.toString() + val height = ilPetProfileHeight.etInputContent.text.toString() + if (TextUtils.isEmpty(name) || TextUtils.isEmpty(type) || TextUtils.isEmpty( + birthday + ) || TextUtils.isEmpty(sex) || TextUtils.isEmpty(weight) || TextUtils.isEmpty(height) + ) { + showToast(R.string.txt_not_null) + return@apply + } + name = name.trimStart() + name = name.trimEnd() + mPetBean?.let { + it.petName = name + it.breed = breed + } + setButtonEnabled(btnPetProfileSave, ConstantInt.Type0) + if (TextUtils.isEmpty(mSelectPetHeadPath)) { + addAndUpdatePetProfile() + } else { + mUserInfoViewModel.uploadImage(this@PetProfileActivity, mSelectPetHeadPath) + } + } + } + + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + showSelectPetLabels() + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilPetProfileHeadLayout.ilPetProfileHead.appHeadImage -> ViewUtil.instance.goSelectPhoto( + this@PetProfileActivity, ResultCode.ResultCode_1 + ) + + ilPetProfileType.root, ilPetProfileType.ivInputRepresentImage -> showPetTypeDialog() + ilPetProfileBirthday.root, ilPetProfileBirthday.ivInputRepresentImage -> showBirthdayDialog() + ilPetProfileSex.root, ilPetProfileSex.ivInputRepresentImage -> showSexDialog() + ilPetProfileWeight.root, ilPetProfileWeight.ivInputRepresentImage -> { + val intent = Intent(mContext, PetWeightActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + launchActivity(mActivityResultLauncher, intent) + } + + ilPetProfileHeight.root, ilPetProfileHeight.ivInputRepresentImage -> { + val intent = Intent(mContext, PetHeightActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + launchActivity(mActivityResultLauncher, intent) + } + + rlPetProfileLabels -> showLabelDialog() + btnPetProfileSave -> savePetProfile() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetWeightActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetWeightActivity.kt new file mode 100644 index 0000000..a9de691 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/PetWeightActivity.kt @@ -0,0 +1,158 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.content.Intent +import android.view.View +import android.widget.NumberPicker +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityPetWeightBinding +import com.abbidot.tracker.ui.activity.pet.first.FirstPetProfileActivity +import com.abbidot.tracker.util.NumberPickerValueFill0Format +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class PetWeightActivity : BaseActivity(ActivityPetWeightBinding::inflate), + NumberPicker.OnValueChangeListener { + + private var mPetBean: PetBean? = null + private var isFirstBind = false + + override fun getTopBar() = mViewBinding.ilPetWeightTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.acccount_weight) + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + var maxTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.WeightType, ConstantInt.MaxType + ) + var minTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.WeightType, ConstantInt.MinType + ) + var defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + ConstantInt.DogPetType, ConstantInt.WeightType + ) + var defaultValue2 = 0 + mPetBean?.apply { + if (petType == ConstantInt.CatPetType) mViewBinding.ivSetPetWeightImage.setImageResource( + R.drawable.icon_cat_image_svg + ) + defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + petType, ConstantInt.WeightType + ) + minTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.WeightType, ConstantInt.MinType + ) + maxTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.WeightType, ConstantInt.MaxType + ) + if (weight > 0) { + var convertHeight = Util.metricConvertUnits(weight, ConstantInt.Type1) + if (convertHeight > maxTensValue) { + convertHeight = maxTensValue.toFloat() + 0.9f + } else if (convertHeight < minTensValue) { + convertHeight = minTensValue.toFloat() + } + val values = convertHeight.toString().split(".") + defaultValue1 = values[0].toInt() + defaultValue2 = values[1].toInt() + } + } + + mViewBinding.apply { + if (isFirstBind) btnSetPetWeightSave.setText(R.string.register_txt_next) + root.setBackgroundResource(R.color.white) + + tvSetPetWeightUnit.text = Util.getMetricUnits(mContext, ConstantInt.Type1) + + npSetPetWeightTens.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + minTensValue, + maxTensValue, + defaultValue1, + 20, + NumberPickerValueFill0Format(), + this@PetWeightActivity + ) + } + + val valueList = mutableListOf() + for (i in 0..9) { + valueList.add("$i") + } + npSetPetWeightUnit.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + 0, + 9, + defaultValue2, + 20, + valueChangeListener = this@PetWeightActivity + ) + it.displayedValues = valueList.toTypedArray() + } + setOnClickListenerViews(btnSetPetWeightSave) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshPet) { + finish() + } + } + + private fun saveWeight() { + val value = + "${mViewBinding.npSetPetWeightTens.value}.${mViewBinding.npSetPetWeightUnit.value}" + if (isFirstBind) { + mPetBean?.apply { + weight = Util.convertMetricUnits(value.toFloat(), ConstantInt.Type1) + val intent = Intent(mContext, FirstPetProfileActivity::class.java) + intent.putExtra(ConstantString.Pet, this) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + } else { +// val petWeight = Util.convertMetricUnits(mContext, value.toFloat(), ConstantInt.Type1) + val intent = Intent() + intent.putExtra(ConstantString.PetWeight, value.toFloat()) + setResult(ResultCode.ResultCode_3, intent) + finish() + } + } + + override fun onValueChange(picker: NumberPicker?, oldVal: Int, newVal: Int) { + mViewBinding.apply { + when (picker) { + npSetPetWeightTens -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npSetPetWeightUnit, btnSetPetWeightSave + ) + + npSetPetWeightUnit -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npSetPetWeightTens, btnSetPetWeightSave + ) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSetPetWeightSave -> saveWeight() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/ShowInviteDeviceActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/ShowInviteDeviceActivity.kt new file mode 100644 index 0000000..9ad447a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/ShowInviteDeviceActivity.kt @@ -0,0 +1,83 @@ +package com.abbidot.tracker.ui.activity.pet + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityShowInviteDeviceBinding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.vm.FamilyViewModel + +class ShowInviteDeviceActivity : + BaseActivity(ActivityShowInviteDeviceBinding::inflate) { + + private val mFamilyViewModel: FamilyViewModel by viewModels() + + override fun getTopBar() = mViewBinding.ilShowInviteDeviceTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + setOnClickListenerViews(btnShowInviteDeviceOk) + } + + val inviteArray = Util.getParcelableArrayListAdaptive( + intent, ConstantString.Type, FamilyBean::class.java + ) + if (inviteArray.isNullOrEmpty()) { + mFamilyViewModel.getDeviceInviteInfo(this) + } else { + showInviteDialog(inviteArray) + } + } + + override fun liveDataObserve() { + //获取设备邀请信息 + mFamilyViewModel.mInviteInfoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + MMKVUtil.putInt(MMKVKey.isExistNewInvite, 0) + it.getOrNull()?.apply { + if (inviteArray.isNotEmpty()) showInviteDialog(inviteArray) + } + } + }) + } + } + + private fun showInviteDialog(inviteArray: MutableList) { + MMKVUtil.putInt(MMKVKey.isBindDevice, ConstantInt.Type1) + MMKVUtil.putInt(MMKVKey.isBindPet, ConstantInt.Type1) + for (item in inviteArray) { + val content = String.format( + getString(R.string.txt_invites_you_check), item.userName, item.petName + ) + mViewBinding.tvShowInviteDeviceName.text = content + break + } + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnShowInviteDeviceOk -> startActivityFinish( + Intent( + mContext, HomeV2Activity::class.java + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstMeasurementUnitsActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstMeasurementUnitsActivity.kt new file mode 100644 index 0000000..93754ba --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstMeasurementUnitsActivity.kt @@ -0,0 +1,160 @@ +package com.abbidot.tracker.ui.activity.pet.first + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityFirstMeasurementUnitsBinding +import com.abbidot.tracker.ui.activity.pet.PetHeightActivity +import com.abbidot.tracker.ui.common.UnitSelectDialog +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + +/** + * 默认英制 + * Imperial: in\lbs\mph\ft + * Metric: cm\kg\km/h\m,km + * + * 英制范围: +身高数据范围:1.0~99.9 in; +体重数据范围:1.0~349.9 lbs。 + +公制范围: +身高数据范围:1.0~199.9 cm; +体重数据范围:1.0~159.9 kg。 + */ +class FirstMeasurementUnitsActivity : + BaseActivity(ActivityFirstMeasurementUnitsBinding::inflate) { + + private val mUserProfileViewModel: UserProfileViewModel by viewModels() + + private var isFirstBind = false + private var mPetBean: PetBean? = null + + private lateinit var mUnitSelectDialog: UnitSelectDialog + private var mMeasurementUnit = 1 + +// private var mWeightUnitDialog: CommonListDialog? = null + + override fun getTopBar() = mViewBinding.ilFirstMeasurementUnitsTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + ilFirstMeasurementUnitsSelect.let { + it.tvInputNameTitle.setText(R.string.txt_set_measurement_units) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + + val unit = Util.getMeasureUnitName(mContext) + it.etInputContent.setText(unit) + } + + setOnClickListenerViews( + btnFirstMeasurementUnitsSave, + ilFirstMeasurementUnitsSelect.rlEtInputLayout, + ilFirstMeasurementUnitsSelect.ivInputRepresentImage + ) + + mUnitSelectDialog = UnitSelectDialog(mContext) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshPet) { + finish() + } + mUserProfileViewModel.mUpdateMeasureUnitLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + setPetUnit() + } + } + }) + } + } + + +// private fun showWeightUnitDialog() { +// if (null == mWeightUnitDialog) { +// val weightUnitList = mutableListOf() +// DataBean().run { +// name = getString(R.string.txt_lb) +// weightUnitList.add(this) +// } +// DataBean().run { +// name = getString(R.string.txt_kg) +// weightUnitList.add(this) +// } +// val weightUnitAdapter = TextViewAdapter(mContext, weightUnitList).apply { +// setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { +// override fun onItemClick(itemView: View?, pos: Int) { +// mViewBinding.ilFirstMeasurementUnitsWeight.etInputContent.setText( +// weightUnitList[pos].name +// ) +// mWeightUnitDialog?.dismiss() +// } +// }) +// } +// mWeightUnitDialog = CommonListDialog(mContext, weightUnitAdapter, 3, 3) +// } +// mWeightUnitDialog?.show() +// } + + private fun setPetUnit() { + mViewBinding.apply { + MMKVUtil.putInt(MMKVKey.MeasureUnit, mMeasurementUnit) + if (isFirstBind) { + val intent = Intent(mContext, PetHeightActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } else finish() + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilFirstMeasurementUnitsSelect.rlEtInputLayout, ilFirstMeasurementUnitsSelect.ivInputRepresentImage -> mUnitSelectDialog.showUnitDialog( + object : BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is Int) { + mMeasurementUnit = any + val unit = + if (any == ConstantInt.Type1) getString(R.string.txt_imperial) + else getString(R.string.txt_metric) + ilFirstMeasurementUnitsSelect.etInputContent.setText( + unit + ) + } + } + }) + + btnFirstMeasurementUnitsSave -> mUserProfileViewModel.updateMeasureUnit( + this@FirstMeasurementUnitsActivity, mMeasurementUnit + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstPetProfileActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstPetProfileActivity.kt new file mode 100644 index 0000000..07caf83 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstPetProfileActivity.kt @@ -0,0 +1,290 @@ +package com.abbidot.tracker.ui.activity.pet.first + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import androidx.lifecycle.lifecycleScope +import coil.load +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityFirstPetProfileBinding +import com.abbidot.tracker.ui.activity.device.wifi.AddWifiPowerZone1Activity +import com.abbidot.tracker.ui.common.PetProfileCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.PetInfoViewModel +import com.abbidot.tracker.vm.PetRelationViewModel +import com.abbidot.tracker.vm.TakePhotoAndCompressViewModel +import com.abbidot.tracker.vm.TrackerInfoViewModel +import com.abbidot.tracker.vm.UserProfileViewModel +import com.huantansheng.easyphotos.EasyPhotos +import com.huantansheng.easyphotos.models.album.entity.Photo +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class FirstPetProfileActivity : + BaseActivity(ActivityFirstPetProfileBinding::inflate), + BaseDialog.OnDialogOkListener { + + private val mPetViewModel: PetInfoViewModel by viewModels() + private val mTrackerViewModel: TrackerInfoViewModel by viewModels() + private val mPetRelationViewModel: PetRelationViewModel by viewModels() + private val mTakePhotoAndCompressViewModel: TakePhotoAndCompressViewModel by viewModels() + private val mUserInfoViewModel: UserProfileViewModel by viewModels() + + private var isFirstBind = false + private var mPetBean: PetBean? = null + + //选取头像本地路径 + private var mSelectPetHeadPath = "" + + private lateinit var mProfileCommon: PetProfileCommon + + override fun getTopBar() = mViewBinding.ilFirstPetProfileTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + + mProfileCommon = PetProfileCommon(mContext, rvFirstPetProfileLabel, mPetBean) + + ViewUtil.instance.setPetTypeHead( + ilFirstPetProfileHeadLayout.ilPetProfileHead.appHeadImage, "", 1 + ) + mPetBean?.apply { + tvFirstPetProfilePetName.text = petName + ViewUtil.instance.setPetTypeHead( + ilFirstPetProfileHeadLayout.ilPetProfileHead.appHeadImage, imgurl, petType + ) + } + + ilFirstPetProfileBreed.let { + it.tvInputNameTitle.text = getString(R.string.acccount_breed) + } + ilFirstPetProfileBirthday.let { + it.tvInputNameTitle.text = getString(R.string.acccount_birthday) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + it.ivInputRepresentImage.setImageResource(R.drawable.icon_profile_birthday_edit_svg) + } + ilFirstPetProfileSex.let { + it.tvInputNameTitle.text = getString(R.string.txt_sex) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + } + + mProfileCommon.setSexListData(ilFirstPetProfileSex.etInputContent) + + setOnClickListenerViews( + rlFirstPetProfileLabels, + ilFirstPetProfileBirthday.root, + ilFirstPetProfileSex.root, + ilFirstPetProfileHeadLayout.ilPetProfileHead.appHeadImage, + btnFirstPetProfileNext, + tvFirstPetProfileLaterBtn + ) + } + + mTakePhotoAndCompressViewModel.registerCropImageActivityResult(this) + + mPetViewModel.getPetLabels(this) + } + + override fun liveDataObserve() { + //获取设备列表,方便把设备保存到本地 + mTrackerViewModel.getDeviceListLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + lifecycleScope.launch { + withContext(Dispatchers.IO) { + for (device in list) { + //保存本地数据库 + if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) { + MyDatabase.deviceDao().insert(device) + } + } + } + } + } + + showToast(R.string.txt_bind_success, isFinish = true) + mViewBinding.root.postDelayed({ + MMKVUtil.putInt(MMKVKey.isBindPet, ConstantInt.isBind) + mPetBean?.apply { + val intent = Intent(mContext, AddWifiPowerZone1Activity::class.java) + intent.putExtra(ConstantString.Pet, this) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + }, 1500) + } + }) + } + + //设备绑定宠物 + mPetRelationViewModel.mBindPetLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + XEventBus.post(EventName.RefreshPet) + mTrackerViewModel.getDeviceList(this@FirstPetProfileActivity, 1) + } + + override fun onRequestError(exceptionCode: String?) { + super.onRequestError(exceptionCode) + setButtonEnabled(mViewBinding.tvFirstPetProfileLaterBtn, ConstantInt.Type1) + setButtonEnabled(mViewBinding.btnFirstPetProfileNext, ConstantInt.Type1) + } + }) + } + + mPetViewModel.apply { + //获取宠物标签 + mPetLabelsLiveData.observe(this@FirstPetProfileActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { labels -> + mProfileCommon.getLabelData(labels) + } + } + }) + } + //添加宠物 + mAddPetLiveData.observe(this@FirstPetProfileActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { pet -> + mPetBean?.let { petBean -> + petBean.petId = pet.petId + mPetRelationViewModel.bindPet( + this@FirstPetProfileActivity, petBean.deviceId, petBean.petId + ) + } + } + } + + override fun onRequestError(exceptionCode: String?) { + super.onRequestError(exceptionCode) + setButtonEnabled(mViewBinding.tvFirstPetProfileLaterBtn, ConstantInt.Type1) + setButtonEnabled(mViewBinding.btnFirstPetProfileNext, ConstantInt.Type1) + } + }) + } + } + //选取照片 + mTakePhotoAndCompressViewModel.mPhotoPathLiveData.observe(this) { + if (it.size > 0) { + mSelectPetHeadPath = it[0].path + mViewBinding.ilFirstPetProfileHeadLayout.ilPetProfileHead.appHeadImage.load(it[0]) + } + } + + //上传照片 + mUserInfoViewModel.mUploadImageLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { +// showToast(R.string.txt_upload_successful) + mPetBean?.let { pet -> + pet.imgurl = this + mPetViewModel.addPet(this@FirstPetProfileActivity, pet) + } + } + } + }) + } + } + + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (RESULT_OK == resultCode) { + //相机或相册回调 + if (requestCode == ResultCode.ResultCode_1 && null != data) { + //返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + val resultPhotos: ArrayList = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS)!! + //返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取 +// data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false) + mTakePhotoAndCompressViewModel.goCropAndCompressImage(resultPhotos) + } + } + } + + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + mViewBinding.apply { + mProfileCommon.showSelectPetLabels(tvFirstPetProfileLabelTitle, svFirstPetProfile) + } + } + + private fun saveProfile() { + mViewBinding.apply { + val breed = ilFirstPetProfileBreed.etInputContent.text.toString() + val birthday = ilFirstPetProfileBirthday.etInputContent.text.toString() + val sex = ilFirstPetProfileSex.etInputContent.text.toString() + if (TextUtils.isEmpty(birthday) || TextUtils.isEmpty(sex)) { + showToast(R.string.txt_not_null) + return + } + setButtonEnabled(btnFirstPetProfileNext, ConstantInt.Type0) + mPetBean?.let { + it.breed = breed + } + + addPet() + } + } + + private fun addPet() { + setButtonEnabled(mViewBinding.tvFirstPetProfileLaterBtn, ConstantInt.Type0) + setButtonEnabled(mViewBinding.btnFirstPetProfileNext, ConstantInt.Type0) + if (TextUtils.isEmpty(mSelectPetHeadPath)) { + mPetBean?.apply { + mPetViewModel.addPet(this@FirstPetProfileActivity, this) + } + } else { + mUserInfoViewModel.uploadImage(this@FirstPetProfileActivity, mSelectPetHeadPath) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilFirstPetProfileHeadLayout.ilPetProfileHead.appHeadImage -> ViewUtil.instance.goSelectPhoto( + this@FirstPetProfileActivity, ResultCode.ResultCode_1 + ) + + rlFirstPetProfileLabels -> mProfileCommon.showLabelDialog(this@FirstPetProfileActivity) + + ilFirstPetProfileBirthday.root -> mProfileCommon.showBirthdayDialog(mViewBinding.ilFirstPetProfileBirthday.etInputContent) + + ilFirstPetProfileSex.root -> mProfileCommon.showSexDialog() + + btnFirstPetProfileNext -> saveProfile() + tvFirstPetProfileLaterBtn -> addPet() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetHeightActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetHeightActivity.kt new file mode 100644 index 0000000..1a23a65 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetHeightActivity.kt @@ -0,0 +1,144 @@ +package com.abbidot.tracker.ui.activity.pet.first + +import android.content.Intent +import android.view.View +import android.widget.NumberPicker +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityFirstSetPetHeightBinding +import com.abbidot.tracker.util.NumberPickerValueFill0Format +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class FirstSetPetHeightActivity : + BaseActivity(ActivityFirstSetPetHeightBinding::inflate), + NumberPicker.OnValueChangeListener { + + private var isFirstBind = false + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.ilFirstSetPetHeightTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + var maxTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.HeightType, ConstantInt.MaxType + ) + var minTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.HeightType, ConstantInt.MinType + ) + var defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + ConstantInt.DogPetType, ConstantInt.HeightType + ) + var defaultValue2 = 0 + mPetBean?.apply { + if (petType == ConstantInt.CatPetType) mViewBinding.ivFirstSetPetHeightType.setImageResource( + R.drawable.icon_cat_height_tip_svg + ) + defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + petType, ConstantInt.HeightType + ) + minTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.HeightType, ConstantInt.MinType + ) + maxTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.HeightType, ConstantInt.MaxType + ) + if (height > 0) { + val values = + Util.metricConvertUnits(height, ConstantInt.Type0).toString().split(".") + defaultValue1 = values[0].toInt() + defaultValue2 = values[1].toInt() + } + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + + tvFirstSetPetHeightUnit.text = Util.getMetricUnits(mContext, ConstantInt.Type0) + npFirstSetPetHeightTens.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + minTensValue, + maxTensValue, + defaultValue1, + 20, + NumberPickerValueFill0Format(), + this@FirstSetPetHeightActivity + ) + } + + val valueList = mutableListOf() + for (i in 0..9) { + valueList.add("$i") + } + npFirstSetPetHeightUnit.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + 0, + 9, + defaultValue2, + 20, + valueChangeListener = this@FirstSetPetHeightActivity + ) + it.displayedValues = valueList.toTypedArray() + } + + setOnClickListenerViews(btnFirstSetPetHeightNext) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshPet) { + finish() + } + } + + private fun saveHeight() { + mPetBean?.apply { + val value = + "${mViewBinding.npFirstSetPetHeightTens.value}.${mViewBinding.npFirstSetPetHeightUnit.value}" + height = Util.convertMetricUnits(value.toFloat(), ConstantInt.Type0) + val intent = Intent(mContext, FirstSetPetWeightActivity::class.java) + intent.putExtra(ConstantString.Pet, this) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnFirstSetPetHeightNext -> saveHeight() + } + } + } + + override fun onValueChange(picker: NumberPicker, oldVal: Int, newVal: Int) { + mViewBinding.apply { + when (picker) { + npFirstSetPetHeightTens -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npFirstSetPetHeightUnit, btnFirstSetPetHeightNext + ) + + npFirstSetPetHeightUnit -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npFirstSetPetHeightTens, btnFirstSetPetHeightNext + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetTypeActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetTypeActivity.kt new file mode 100644 index 0000000..856e144 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetTypeActivity.kt @@ -0,0 +1,105 @@ +package com.abbidot.tracker.ui.activity.pet.first + +import android.content.Intent +import android.text.TextUtils +import android.view.View +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityFirstSetPetTypeBinding +import com.abbidot.tracker.ui.activity.account.LoginV2Activity + +class FirstSetPetTypeActivity : + BaseActivity(ActivityFirstSetPetTypeBinding::inflate) { + + private var isFirstBind = false + private var mDeviceOutId = "" + private var mDeviceId = "" + private lateinit var mPetBean: PetBean + + override fun getTopBar() = mViewBinding.ilFirstSetPetTypeTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + mPetBean = PetBean() + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + } + + mDeviceId = MMKVUtil.getString(MMKVKey.FirstDeviceId) + mDeviceOutId = MMKVUtil.getString(MMKVKey.FirstDeviceOutId) + mPetBean.let { + it.deviceId = mDeviceId + it.deviceOutId = mDeviceOutId + it.macID = MMKVUtil.getString(MMKVKey.FirstDeviceMac) + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + + rgFirstSetPetTypeGroup.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + rbFirstSetPetTypeDog.id -> { + mPetBean.petType = ConstantInt.DogPetType + } + + rbFirstSetPetTypeCat.id -> { + mPetBean.petType = ConstantInt.CatPetType + } + } + } + + setViewClickHideInputMethod(llFirstSetPetTypeLayout) + + setOnClickListenerViews(btnFirstSetPetTypeNext, btnFirstSetPetTypeBackLogin) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshPet) { + finish() + } + } + + override fun listenBackPressed() { + + } + + private fun petTypeNext() { + mViewBinding.apply { + var name = ilFirstSetPetTypePetName.etInputContent.text.toString() + if (TextUtils.isEmpty(name)) { + showToast(R.string.txt_not_null) + return + } + name = name.trimStart() + name = name.trimEnd() + mPetBean.petName = name + val intent = Intent(mContext, FirstMeasurementUnitsActivity::class.java) + intent.putExtra(ConstantString.Pet, mPetBean) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnFirstSetPetTypeNext -> petTypeNext() + btnFirstSetPetTypeBackLogin -> startActivityFinish( + Intent( + mContext, LoginV2Activity::class.java + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetWeightActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetWeightActivity.kt new file mode 100644 index 0000000..4deece6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/pet/first/FirstSetPetWeightActivity.kt @@ -0,0 +1,144 @@ +package com.abbidot.tracker.ui.activity.pet.first + +import android.content.Intent +import android.view.View +import android.widget.NumberPicker +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityFirstSetPetWeightBinding +import com.abbidot.tracker.util.NumberPickerValueFill0Format +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class FirstSetPetWeightActivity : + BaseActivity(ActivityFirstSetPetWeightBinding::inflate), + NumberPicker.OnValueChangeListener { + + private var isFirstBind = false + private var mPetBean: PetBean? = null + + override fun getTopBar() = mViewBinding.ilFirstSetPetWeightTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle("") + + intent.extras?.apply { + isFirstBind = getBoolean(ConstantString.isFirstBind, false) + mPetBean = Util.getParcelableAdaptive(intent, ConstantString.Pet, PetBean::class.java) + } + + var maxTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.WeightType, ConstantInt.MaxType + ) + var minTensValue = Util.getPetHeightAndWeightRangeValue( + ConstantInt.DogPetType, ConstantInt.WeightType, ConstantInt.MinType + ) + var defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + ConstantInt.DogPetType, ConstantInt.WeightType + ) + var defaultValue2 = 0 + mPetBean?.apply { + if (petType == ConstantInt.CatPetType) mViewBinding.ivFirstSetPetWeightImage.setImageResource( + R.drawable.icon_cat_image_svg + ) + defaultValue1 = Util.getPetHeightAndWeightDefaultValue( + petType, ConstantInt.WeightType + ) + minTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.WeightType, ConstantInt.MinType + ) + maxTensValue = Util.getPetHeightAndWeightRangeValue( + petType, ConstantInt.WeightType, ConstantInt.MaxType + ) + if (weight > 0) { + val values = + Util.metricConvertUnits(weight, ConstantInt.Type1).toString().split(".") + defaultValue1 = values[0].toInt() + defaultValue2 = values[1].toInt() + } + } + + mViewBinding.apply { + root.setBackgroundResource(R.color.white) + + tvFirstSetPetWeightUnit.text = Util.getMetricUnits(mContext, ConstantInt.Type1) + + npFirstSetPetWeightTens.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + minTensValue, + maxTensValue, + defaultValue1, + 20, + NumberPickerValueFill0Format(), + this@FirstSetPetWeightActivity + ) + } + + val valueList = mutableListOf() + for (i in 0..9) { + valueList.add("$i") + } + npFirstSetPetWeightUnit.let { + ViewUtil.instance.setNumberPickerAttributes( + mContext, + it, + 0, + 9, + defaultValue2, + 20, + valueChangeListener = this@FirstSetPetWeightActivity + ) + it.displayedValues = valueList.toTypedArray() + } + setOnClickListenerViews(btnFirstSetPetWeightNext) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.RefreshPet) { + finish() + } + } + + private fun saveWeight() { + mPetBean?.apply { + val value = + "${mViewBinding.npFirstSetPetWeightTens.value}.${mViewBinding.npFirstSetPetWeightUnit.value}" + weight = Util.convertMetricUnits(value.toFloat(), ConstantInt.Type1) + val intent = Intent(mContext, FirstPetProfileActivity::class.java) + intent.putExtra(ConstantString.Pet, this) + intent.putExtra(ConstantString.isFirstBind, isFirstBind) + startActivity(intent) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnFirstSetPetWeightNext -> saveWeight() + } + } + } + + override fun onValueChange(picker: NumberPicker?, oldVal: Int, newVal: Int) { + mViewBinding.apply { + when (picker) { + npFirstSetPetWeightTens -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npFirstSetPetWeightUnit, btnFirstSetPetWeightNext + ) + + npFirstSetPetWeightUnit -> ViewUtil.instance.numberPickerLinkageValueNo0( + newVal, npFirstSetPetWeightTens, btnFirstSetPetWeightNext + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/set/AboutActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/set/AboutActivity.kt new file mode 100644 index 0000000..8196727 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/set/AboutActivity.kt @@ -0,0 +1,65 @@ +package com.abbidot.tracker.ui.activity.set + +import android.text.TextUtils +import android.view.View +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.databinding.ActivityAboutBinding +import com.abbidot.tracker.ui.activity.WebViewActivity +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIPackageHelper +import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView + +class AboutActivity : BaseActivity(ActivityAboutBinding::inflate) { + + private val mWebsiteUrl = "www.abbidot.com" + + override fun getTopBar() = mViewBinding.ilAboutTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_setting_about) + setLeftBackImage(R.drawable.icon_white_back_svg) + + val version = QMUIPackageHelper.getAppVersion(mContext) + val versionStr = String.format(getString(R.string.txt_about_version), ": V$version") + + mViewBinding.apply { + tvAboutVersion.text = versionStr + + //服务协议 + setButtonData(aboutServiceContract.trackerManageSettingItem, + getString(R.string.txt_service_contract)) + //隐私政策 + setButtonData(aboutPrivacyPolicy.trackerManageSettingItem, + getString(R.string.txt_privacy_policy)) + //官网 + setButtonData(aboutOfficialWebsite.trackerManageSettingItem, + getString(R.string.txt_website), mWebsiteUrl) + } + } + + private fun setButtonData(commonItemView: QMUICommonListItemView, + title: String, + content: String = "") { + commonItemView.apply { + text = title + if (!TextUtils.isEmpty(content)) detailText = content + setOnClickListenerViews(this) + } + } + + + override fun onClick(v: View?) { + when (v!!.id) { + //服务协议 + R.id.about_service_contract -> ViewUtil.instance.goServiceContract(mActivity) + //隐私政策 + R.id.about_privacy_policy -> ViewUtil.instance.goPrivacyPolicy(mActivity) + //官网 + R.id.about_official_website -> WebViewActivity.startWebViewActivity( + mActivity, + getString(R.string.txt_website), mWebsiteUrl) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/set/AdvancedSetActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/set/AdvancedSetActivity.kt new file mode 100644 index 0000000..6e0bbd3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/set/AdvancedSetActivity.kt @@ -0,0 +1,127 @@ +package com.abbidot.tracker.ui.activity.set + +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityAdvancedSetBinding +import com.abbidot.tracker.util.FileUtil +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class AdvancedSetActivity : + BaseActivity(ActivityAdvancedSetBinding::inflate) { + + private val mUserViewModel: UserProfileViewModel by viewModels() + + private val mClearCacheTipsType = 1 + private val mCloseAccountTipsType = 2 + + override fun getTopBar() = mViewBinding.ilAdvancedSetTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_advanced_set) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + ilAdvancedSetClearCache.root.let { + it.text = getString(R.string.txt_setting_clear) + it.setImageDrawable( + ContextCompat.getDrawable( + mContext, R.drawable.icon_database_restore_svg + ) + ) + } + ilAdvancedSetPayManager.root.let { + it.text = getString(R.string.txt_delete_account) + it.setImageDrawable( + ContextCompat.getDrawable( + mContext, R.drawable.ico_account + ) + ) + } + ilAdvancedSetDeleteAccount.root.let { + it.text = getString(R.string.txt_delete_account) + it.setImageDrawable( + ContextCompat.getDrawable( + mContext, R.drawable.ico_account + ) + ) + } + + setOnClickListenerViews(ilAdvancedSetClearCache.root, ilAdvancedSetDeleteAccount.root) + } + + getCacheSize() + } + + override fun liveDataObserve() { + mUserViewModel.mUserLogoffLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + Util.logOut(this@AdvancedSetActivity, lifecycleScope) + } + }) + } + } + + private fun dialogShowType(type: Int, string: String) { + ViewUtil.instance.showDialog(mContext, string, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + when (type) { + mClearCacheTipsType -> clearCache() + mCloseAccountTipsType -> mUserViewModel.logoff(this@AdvancedSetActivity) + } + } + }, okTextResId = R.string.txt_sure) + } + + /** + * 清除缓存 + */ + private fun clearCache() { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + FileUtil.clearCache(mActivity) + } + getCacheSize() + showToast(R.string.txt_clear_cache_success) + } + } + + /** + * 获取缓存大小 + */ + private fun getCacheSize() { + lifecycleScope.launch { + mViewBinding.ilAdvancedSetClearCache.root.detailText = withContext(Dispatchers.IO) { + FileUtil.getCacheSize(mActivity) + } + } + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilAdvancedSetClearCache.root -> dialogShowType( + mClearCacheTipsType, getString(R.string.txt_clear_cache_tips) + ) + + ilAdvancedSetDeleteAccount.root -> dialogShowType( + mCloseAccountTipsType, getString(R.string.txt_close_account_tips) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/set/FirstPermissionsActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/set/FirstPermissionsActivity.kt new file mode 100644 index 0000000..2dbe2a8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/set/FirstPermissionsActivity.kt @@ -0,0 +1,113 @@ +package com.abbidot.tracker.ui.activity.set + +import android.view.View +import androidx.core.view.isGone +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.adapter.FirstPermissionsAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.databinding.ActivityFirstPermissionsBinding +import com.abbidot.tracker.ui.common.PermissionsActivityCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + + +class FirstPermissionsActivity : + BaseActivity(ActivityFirstPermissionsBinding::inflate) { + + private lateinit var mPermissionsAdapter: FirstPermissionsAdapter + + lateinit var mPermissionsCommon: PermissionsActivityCommon + + override fun getTopBar() = null + + override fun initData() { + super.initData() + + mPermissionsCommon = PermissionsActivityCommon(mContext).apply { + registerGpsReceiver() + } + mPermissionsAdapter = + FirstPermissionsAdapter(mContext, mPermissionsCommon.addListData(mContext)).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + mPermissionsCommon.updateDataBeanSelectedItem( + mPermissionsAdapter, getData(), pos + ) + } + }) + } + + mViewBinding.apply { + + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvFirstPermissionList, mPermissionsAdapter, top = AppUtils.dpToPx(12) + ) + + btnFirstPermissionNext.visibility = View.GONE + + setOnClickListenerViews(btnFirstPermissionNext) + } + } + + override fun liveDataObserve() { + //蓝牙开关操作 + XEventBus.observe(this, EventName.BluetoothSwitch) { conState: Int -> + mPermissionsCommon.updatePermissionsState(mPermissionsAdapter, ConstantInt.SpecialType) + } + //gps开关操作 + XEventBus.observe(this, EventName.GPSSwitchState) { switchState: Int -> + mPermissionsCommon.updatePermissionsState(mPermissionsAdapter, ConstantInt.SpecialType) + } + } + + override fun onResume() { + super.onResume() + LogUtil.e("PermissionsActivity----------------->>>onResume") + mPermissionsCommon.updatePermissionsState(mPermissionsAdapter, ConstantInt.SpecialType) + } + + + override fun listenBackPressed() { + + } + + /** + * 更新下一步按钮状态 + */ + fun updateNextBtnState() { + val list = mPermissionsAdapter.getData() + var showBtn = true + for (data in list) { + if (data.state == ConstantInt.Type0) { + showBtn = false + break + } + } + mViewBinding.apply { + if (showBtn && btnFirstPermissionNext.isGone) { + ViewUtil.instance.viewShowFadeInAnimation(btnFirstPermissionNext, 1000) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnFirstPermissionNext -> { + setButtonEnabled(v) + startActivityFinish(Util.checkBindDeviceAndPetGoActivity(mContext)) + } + } + } + } + + override fun onDestroy() { + super.onDestroy() + mPermissionsCommon.unregisterGpsReceiver() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/set/NotificationsSetActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/set/NotificationsSetActivity.kt new file mode 100644 index 0000000..0a0c31e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/set/NotificationsSetActivity.kt @@ -0,0 +1,133 @@ +package com.abbidot.tracker.ui.activity.set + +import android.widget.CompoundButton +import androidx.activity.viewModels +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.TextViewAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityNotificationsSetBinding +import com.abbidot.tracker.vm.AppSetViewModel + + +class NotificationsSetActivity : + BaseActivity(ActivityNotificationsSetBinding::inflate), + CompoundButton.OnCheckedChangeListener { + + private val mAppSetViewModel: AppSetViewModel by viewModels() + + override fun getTopBar() = mViewBinding.ilNotificationsSetTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_notifications_set) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + + ilNotificationsSetDisplayFences.cbNotificationsSetItemSwitch.setOnCheckedChangeListener( + this@NotificationsSetActivity + ) + + ilNotificationsSetTrackerStatus.let { + it.tvNotificationsSetItemTitle.setText(R.string.txt_tracker_status) + it.tvNotificationsSetItemDescription.setText(R.string.txt_tracker_status_desc) + it.cbNotificationsSetItemSwitch.setOnCheckedChangeListener(this@NotificationsSetActivity) + } + + ilNotificationsSetTrackerActivity.let { + it.tvNotificationsSetItemTitle.setText(R.string.txt_activity) + it.tvNotificationsSetItemDescription.setText(R.string.txt_activity_desc) + it.cbNotificationsSetItemSwitch.setOnCheckedChangeListener(this@NotificationsSetActivity) + } + + val list = mutableListOf() + for (i in 0 until 10) { + DataBean().apply { + name = "aaaa$i" + list.add(this) + } + } + val layoutManager = LinearLayoutManager(mContext) + layoutManager.orientation = LinearLayoutManager.VERTICAL + layoutManager.stackFromEnd = true + layoutManager.reverseLayout = true + layoutManager.isItemPrefetchEnabled = false + rvList.layoutManager = layoutManager + rvList.setHasFixedSize(true) + + + rvList.addOnScrollListener(object : RecyclerView.OnScrollListener() { + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + layoutManager.isItemPrefetchEnabled = newState == RecyclerView.SCROLL_STATE_IDLE + } + }) + val textViewAdapter = TextViewAdapter(mContext, list) + rvList.adapter = textViewAdapter + } + + mAppSetViewModel.getNotificationSet() + } + + override fun liveDataObserve() { + mAppSetViewModel.apply { + mGetNotificationLiveData.observe(this@NotificationsSetActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { data -> + for (d in data) { + when (d.notificationType) { + 1 -> mViewBinding.ilNotificationsSetDisplayFences.cbNotificationsSetItemSwitch.isChecked = + d.status == 1 + + 2 -> mViewBinding.ilNotificationsSetTrackerStatus.cbNotificationsSetItemSwitch.isChecked = + d.status == 1 + + 3 -> mViewBinding.ilNotificationsSetTrackerActivity.cbNotificationsSetItemSwitch.isChecked = + d.status == 1 + } + } + } + } + }) + } + + mSetNotificationLiveData.observe(this@NotificationsSetActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.txt_change_successful) + } + }) + } + } + } + + private fun setNotificationType(notificationType: Int, isChecked: Boolean) { + val status = if (isChecked) 1 else 0 + mAppSetViewModel.setNotification(notificationType, status) + } + + override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { + if (!buttonView!!.isPressed) return + mViewBinding.apply { + when (buttonView) { + ilNotificationsSetDisplayFences.cbNotificationsSetItemSwitch -> setNotificationType( + 1, isChecked + ) + + ilNotificationsSetTrackerStatus.cbNotificationsSetItemSwitch -> setNotificationType( + 2, isChecked + ) + + ilNotificationsSetTrackerActivity.cbNotificationsSetItemSwitch -> setNotificationType( + 3, isChecked + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/set/PermissionsSetActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/set/PermissionsSetActivity.kt new file mode 100644 index 0000000..a651659 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/set/PermissionsSetActivity.kt @@ -0,0 +1,62 @@ +package com.abbidot.tracker.ui.activity.set + +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.PermissionsSetAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.databinding.ActivityPermissionsSetBinding +import com.abbidot.tracker.ui.common.PermissionsActivityCommon +import com.abbidot.tracker.util.ViewUtil + +class PermissionsSetActivity : + BaseActivity(ActivityPermissionsSetBinding::inflate) { + + lateinit var mPermissionsCommon: PermissionsActivityCommon + private lateinit var mPermissionsSetAdapter: PermissionsSetAdapter + + override fun getTopBar() = mViewBinding.ilPermissionsSetTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_permissions) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mPermissionsCommon = PermissionsActivityCommon(mContext).apply { + registerGpsReceiver() + } + mPermissionsSetAdapter = + PermissionsSetAdapter(mContext, mPermissionsCommon.addListData(mContext)) + + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvPermissionSetList, mPermissionsSetAdapter, top = AppUtils.dpToPx(12) + ) + } + } + + override fun liveDataObserve() { + //蓝牙开关操作 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + mPermissionsCommon.updatePermissionsState(mPermissionsSetAdapter, ConstantInt.Type0) + } + //gps开关操作 + XEventBus.observe(this, EventName.GPSSwitchState) { switchState: Int -> + mPermissionsCommon.updatePermissionsState(mPermissionsSetAdapter,ConstantInt.Type0) + } + } + + override fun onResume() { + super.onResume() + LogUtil.e("PermissionsSetActivity----------------->>>onResume") + mPermissionsCommon.updatePermissionsState(mPermissionsSetAdapter,ConstantInt.Type0) + } + + override fun onDestroy() { + super.onDestroy() + mPermissionsCommon.unregisterGpsReceiver() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/AddCreditCardPaymentActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/AddCreditCardPaymentActivity.kt new file mode 100644 index 0000000..1837991 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/AddCreditCardPaymentActivity.kt @@ -0,0 +1,359 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.content.res.ColorStateList +import android.graphics.Color +import android.text.InputFilter +import android.text.InputFilter.LengthFilter +import android.text.InputType +import android.text.TextUtils +import android.view.View +import android.view.View.OnFocusChangeListener +import androidx.appcompat.widget.AppCompatEditText +import androidx.core.widget.addTextChangedListener +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.ActivityAddCreditCardPaymentBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + + +class AddCreditCardPaymentActivity : + BaseActivity(ActivityAddCreditCardPaymentBinding::inflate), + OnFocusChangeListener { + + private var mCreditCard: CreditCardBean? = null + private var isEdit = false + + override fun getTopBar() = mViewBinding.ilAddPaymentMethodTopInfo.titleTopBar + + override fun initData() { + super.initData() + intent.extras?.apply { + mCreditCard = Util.getParcelableAdaptive( + intent, ConstantString.Card, CreditCardBean::class.java + ) + isEdit = getBoolean(ConstantString.Edit, false) + } + if (null == mCreditCard) mCreditCard = CreditCardBean() + + if (isEdit) { + setTopBarTitle(R.string.txt_edit_pay_method) + mRightImageButton = addRightImageButton(R.drawable.icon_delete_svg).apply { + imageTintList = ColorStateList.valueOf(Color.WHITE) + } + } else { + setTopBarTitle(R.string.txt_add_payment) + } + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + + ilAddCardNameOnCard.let { + it.tvInputView2NameTitle.setText(R.string.txt_name_card) + it.etInputView2Content.onFocusChangeListener = this@AddCreditCardPaymentActivity + showEditViewContent(it.etInputView2Content, mCreditCard!!.cardName) + } + + ilAddCardNumber.let { + it.tvInputView2NameTitle.setText(R.string.txt_card_number) + it.etInputView2Content.let { cardText -> + cardText.filters = arrayOf(LengthFilter(24)) + cardText.compoundDrawablePadding = AppUtils.dpToPx(6) + cardText.inputType = InputType.TYPE_CLASS_NUMBER + cardText.onFocusChangeListener = this@AddCreditCardPaymentActivity + cardText.addTextChangedListener { numberStr -> + numberStr?.apply { + val formatCardNumber = Util.formatCardNumber(numberStr.toString()) + if (formatCardNumber.length > numberStr.length) { + cardText.setText(formatCardNumber) + // 设置光标位置 + cardText.setSelection(formatCardNumber.length) + } + val cardLogoImageId = + Util.getCreditCardType(numberStr.toString().replace(" ", "")) + if (cardLogoImageId == 0) { + cardText.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, 0, 0 + ) + } else { + cardText.setCompoundDrawablesWithIntrinsicBounds( + cardLogoImageId, 0, 0, 0 + ) + } + } + } + } + showEditViewContent(it.etInputView2Content, mCreditCard!!.cardNumber) + } + etAddCardMmDdContent.let { + it.onFocusChangeListener = this@AddCreditCardPaymentActivity + it.addTextChangedListener { cardExpiry -> + formatCardExpiry(cardExpiry.toString()) + } + showEditViewContent(it, mCreditCard!!.expirationDate) + } + etAddCardCvcContent.let { + it.onFocusChangeListener = this@AddCreditCardPaymentActivity + showEditViewContent(it, mCreditCard!!.cvv) + } + cbAddCardSetPrimarySwitch.isChecked = mCreditCard!!.isPrimary + + setOnClickListenerViews( + btnSaveNewCreditCard, + ilAddCardNameOnCard.root, + ilAddCardNumber.root, + llAddCardCvcLayout, + llAddCardMmDdLayout + ) + } + } + + /** + * 编辑时显示还原内容 + */ + private fun showEditViewContent(editText: AppCompatEditText, content: String) { + if (TextUtils.isEmpty(content)) return + editText.apply { + visibility = View.VISIBLE + setText(content) + } + } + + /** + * 格式化有效期 + */ + private fun formatCardExpiry(cardExpiry: String) { + var formatCardExpiry = cardExpiry + val separator = "/" + if (formatCardExpiry.length == 3) { + formatCardExpiry = if (formatCardExpiry.contains(separator)) { + cardExpiry.substring(0, 2) + } else { + cardExpiry.substring(0, 2) + separator + cardExpiry.last() + } + mViewBinding.etAddCardMmDdContent.apply { + setText(formatCardExpiry) + setSelection(formatCardExpiry.length) + } + } + } + + /** + * 点击输入框并显示 + */ + private fun setClickEditViewVisible(editText: AppCompatEditText) { + editText.apply { + visibility = View.VISIBLE + requestFocus() + //弹出软键盘 + showInputMethod(this) + } + } + + /** + * 输入框失去焦点隐藏 + */ + private fun setEditViewNoFocusHide(editText: AppCompatEditText, hasFocus: Boolean) { + editText.apply { + val content = text.toString() + if (!hasFocus) { + if (TextUtils.isEmpty(content)) visibility = View.GONE + } + } + } + + override fun listenHideKeyboard() { + mViewBinding.apply { + setEditViewNoFocusHide(etAddCardCvcContent, false) + setEditViewNoFocusHide(etAddCardMmDdContent, false) + setEditViewNoFocusHide(ilAddCardNameOnCard.etInputView2Content, false) + setEditViewNoFocusHide(ilAddCardNumber.etInputView2Content, false) + } + } + + private fun confirmSaveCard() { + val cardNumber = + mViewBinding.ilAddCardNumber.etInputView2Content.text.toString().replace(" ", "") + val cardName = mViewBinding.ilAddCardNameOnCard.etInputView2Content.text.toString() + val creditCardValidity = mViewBinding.etAddCardMmDdContent.text.toString() + val cvv = mViewBinding.etAddCardCvcContent.text.toString() + + if (TextUtils.isEmpty(cardNumber) || TextUtils.isEmpty(cardName) || TextUtils.isEmpty( + creditCardValidity + ) || TextUtils.isEmpty(cvv) + ) { + showToast(R.string.txt_not_null) + return + } + + if (!creditCardValidity.contains("/")) { + showToast(R.string.txt_card_validity_format) + return + } else { + val dates = creditCardValidity.split("/") + try { + if (dates[0].toInt() > 12) { + showToast(R.string.txt_card_validity_format) + return + } + } catch (e: Exception) { + showToast(R.string.txt_card_validity_format) + return + } + } + if (Util.getCreditCardType(cardNumber) == 0 || cardNumber.length < 9) { + showToast(R.string.txt_card_number_format) + return + } + + lifecycleScope.launch(Dispatchers.IO) { + + val list = MyDatabase.creditCardDao().findAll(MyDatabase.CreditCardTableName) + + if (!isEdit) { + var isCardExist = false + list?.apply { + if (isNotEmpty()) { + for (c in list) { + if (c.cardNumber == cardNumber) { + isCardExist = true + } + } + } + } + if (isCardExist) { + launch(Dispatchers.Main) { + ViewUtil.instance.showDialog( + mContext, + R.string.txt_credit_card_exists, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + lifecycleScope.launch(Dispatchers.IO) { + saveCard(list) + } + } + }, + okTextResId = R.string.start_txt_yes, + cancelTextResId = R.string.txt_no + ) + } + } else saveCard(list) + } + } + } + + /** + * 保存信用卡 + */ + private fun saveCard(list: List?) { + mViewBinding.apply { + val cardNumber = ilAddCardNumber.etInputView2Content.text.toString().replace(" ", "") + val cardName = ilAddCardNameOnCard.etInputView2Content.text.toString() + val creditCardValidity = etAddCardMmDdContent.text.toString() + val cvv = etAddCardCvcContent.text.toString() + if (cbAddCardSetPrimarySwitch.isChecked) { + mCreditCard?.isPrimary = true + list?.apply { + if (isNotEmpty()) { + for (c in list) { + if (c.isPrimary) { + c.isPrimary = false + MyDatabase.creditCardDao().update(c) + } + } + } + } + } + + mCreditCard?.let { + if (isEdit) { + list?.apply { + if (isNotEmpty()) { + for (c in list) { + //因卡号唯一,编辑卡号删除存在的信用卡 + if (c.cardNumber == it.cardNumber) { + MyDatabase.creditCardDao().delete(c) + } + } + } + } + } + it.cardNumber = cardNumber + it.cardName = cardName + it.expirationDate = creditCardValidity + it.cvv = cvv + MyDatabase.creditCardDao().insert(it) + } + + runOnUiThread { +// val intent = Intent() +// intent.putExtra(ConstantString.Card, mCreditCard) + setResult(ResultCode.ResultCode_1, null) + showToast(R.string.txt_save_successful, isFinish = true) + } + } + } + + private fun deleteCard() { + ViewUtil.instance.showDialog( + mContext, R.string.txt_sure_delete_card, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + lifecycleScope.launch(Dispatchers.IO) { + mCreditCard?.apply { + MyDatabase.creditCardDao().delete(this) + } + } + dialog.dismiss() + setResult(ResultCode.ResultCode_1, null) + showToast(R.string.txt_delete_succe, isFinish = true) + } + }, okTextResId = R.string.start_txt_yes + ) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSaveNewCreditCard -> confirmSaveCard() + llAddCardMmDdLayout -> setClickEditViewVisible(etAddCardMmDdContent) + llAddCardCvcLayout -> setClickEditViewVisible(etAddCardCvcContent) + ilAddCardNameOnCard.root -> setClickEditViewVisible(ilAddCardNameOnCard.etInputView2Content) + ilAddCardNumber.root -> setClickEditViewVisible(ilAddCardNumber.etInputView2Content) + mRightImageButton -> deleteCard() + } + } + } + + override fun onFocusChange(v: View?, hasFocus: Boolean) { + mViewBinding.apply { + when (v!!) { + ilAddCardNameOnCard.etInputView2Content -> { + setEditViewNoFocusHide(ilAddCardNameOnCard.etInputView2Content, hasFocus) + } + + ilAddCardNumber.etInputView2Content -> { + setEditViewNoFocusHide(ilAddCardNumber.etInputView2Content, hasFocus) + } + + etAddCardMmDdContent -> { + setEditViewNoFocusHide(etAddCardMmDdContent, hasFocus) + } + + etAddCardCvcContent -> { + setEditViewNoFocusHide(etAddCardCvcContent, hasFocus) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/ChoosePlanActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/ChoosePlanActivity.kt new file mode 100644 index 0000000..d98a39e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/ChoosePlanActivity.kt @@ -0,0 +1,155 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.ChoosePlanAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityChoosePlanBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.SubscriptionManageViewModel + +class ChoosePlanActivity : + BaseActivity(ActivityChoosePlanBinding::inflate) { + + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + + private lateinit var mChoosePlanAdapter: ChoosePlanAdapter + + private var mSelectPlanIndex = 0 + private var mOrderBean: SubscriptionsOrderBean? = null + + //是否是升级套餐 + private var isUpgrade = false + + //设备充值类型,0=首次设备充值激活,1=已添加设备再次添加设备充值,2=给当前的设备续费或者升级套餐 + private var mRechargeType = ConstantInt.Type1 + + override fun getTopBar() = mViewBinding.ilChoosePlanTopBar.titleTopBar + + override fun initData() { + super.initData() + var packageTypeName = "" + intent.extras?.apply { + packageTypeName = getString(ConstantString.Title, "") + mOrderBean = Util.getParcelableAdaptive( + intent, ConstantString.LkSetMeal, SubscriptionsOrderBean::class.java + ) + isUpgrade = getBoolean(ConstantString.Upgrade, false) + mRechargeType = getInt(ConstantString.RechargeType, ConstantInt.Type1) + } + + if (isUpgrade) { + mRechargeType = ConstantInt.Type2 + setTopBarTitle(R.string.txt_upgrade_plan) + } else setTopBarTitle(R.string.txt_choose_your_plan) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + + mChoosePlanAdapter = ChoosePlanAdapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val list = getData() + if (pos > list.size - 1) return + val dataBean = list[pos] + if (dataBean.selected) return + for (i in 0 until list.size) { + val data = list[i] + if (data.selected) { + data.selected = false + notifyItemChanged(i) + break + } + } + dataBean.selected = true + mSelectPlanIndex = pos + notifyItemChanged(pos) + } + }) + } + + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvChoosePlanList, mChoosePlanAdapter, bottom = AppUtils.dpToPx(5) + ) + + setOnClickListenerViews(btnSelectChoosePlanContinue) + } + + mSubscriptionViewModel.getPackageList(packageTypeName) + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.PayBack) { + finish() + } + mSubscriptionViewModel.mPackageListLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + mOrderBean?.let { o -> + if (isUpgrade) { + val iterator = list.iterator() + while (iterator.hasNext()) { + val l = iterator.next() + //根据套餐时间往上升级 + if (o.mealUnit == ConstantString.PackageUnitMonth) { + if (l.mealUnit == ConstantString.PackageUnitMonth && o.mealPeriod > l.period) { + iterator.remove() + } + } else if (o.mealUnit == ConstantString.PackageUnitYear) { + if (l.mealUnit == ConstantString.PackageUnitMonth) { + iterator.remove() + } else if (l.mealUnit == ConstantString.PackageUnitYear && o.mealPeriod > l.period) { + iterator.remove() + } + } +// val price = +// if (l.discountedPrice > 0.0) l.discountedPrice else l.planPrice +// val oPrice = +// if (o.mealDiscountPrice > 0.0) o.mealDiscountPrice else o.mealPrice +// if (oPrice >= price) iterator.remove() +// else if (l.mealUnit != ConstantString.PackageUnitYear) iterator.remove() + } + } + if (list.size > 0) { + list[0].selected = true + mChoosePlanAdapter.setData(list, true) + } + } + } + } + }) + } + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSelectChoosePlanContinue -> Intent( + mContext, SureSubscriptionPlanActivity::class.java + ).let { + if (mChoosePlanAdapter.getData().isNotEmpty()) { + val cPlan = mChoosePlanAdapter.getData()[mSelectPlanIndex] + it.putExtra(ConstantString.SetMeal, cPlan) + it.putExtra(ConstantString.Upgrade, isUpgrade) + it.putExtra(ConstantString.LkSetMeal, mOrderBean) + it.putExtra(ConstantString.RechargeType, mRechargeType) + startActivity(it) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/MySubscriptionActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/MySubscriptionActivity.kt new file mode 100644 index 0000000..73e325d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/MySubscriptionActivity.kt @@ -0,0 +1,92 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import androidx.activity.viewModels +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.MySubscriptionAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityMySubscriptionBinding +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.SubscriptionManageViewModel + +class MySubscriptionActivity : + BaseActivity(ActivityMySubscriptionBinding::inflate) { + + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + + private lateinit var mSubscriptionAdapter: MySubscriptionAdapter + private var mPosition = 0 + + override fun getTopBar() = mViewBinding.ilMySubscriptionTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_subscription) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mViewBinding.apply { + mSubscriptionAdapter = MySubscriptionAdapter(mContext, null) +// .apply { +// setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { +// override fun onItemClick(itemView: View?, pos: Int) { +// Intent(mContext, SubscriptionDetailActivity::class.java).let { +// it.putExtra(ConstantString.SetMeal, getData()[pos]) +// startActivity(it) +// } +// } +// }) +// } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvMySubscriptionList, mSubscriptionAdapter, top = AppUtils.dpToPx(10) + ) + } + } + + override fun onResume() { + super.onResume() + mSubscriptionViewModel.getSubscriptionsOrder() + } + + override fun liveDataObserve() { +// XEventBus.observe(this, EventName.PayBack) { +// mSubscriptionViewModel.getSubscriptionsOrder() +// } + mSubscriptionViewModel.apply { + mSubscriptionsOrderLiveData.observe(this@MySubscriptionActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + mSubscriptionAdapter.setData(list, true) + } + } + }) + } + + mUpdateStatusListLiveData.observe(this@MySubscriptionActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.txt_successful) + mSubscriptionAdapter.getData()[mPosition].let { bean -> + bean.subscriptionStatus = + if (bean.subscriptionStatus == ConstantInt.Close) ConstantInt.Open + else ConstantInt.Close + mSubscriptionAdapter.notifyItemChanged(mPosition) + mViewBinding.rvMySubscriptionList.smoothScrollToPosition(mPosition) + } + } + }) + } + } + } + + fun updateSubscriptionStatus(subscriptionId: String, status: Int, position: Int) { + mPosition = position + val messageId = if (0 == status) R.string.txt_auto_recharge_title + else R.string.txt_auto_recharge_content + ViewUtil.instance.operateDialogTips(mContext, messageId) { + mSubscriptionViewModel.updateSubscriptionStatus(subscriptionId, status) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/PaymentMethodActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/PaymentMethodActivity.kt new file mode 100644 index 0000000..a985d24 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/PaymentMethodActivity.kt @@ -0,0 +1,76 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import androidx.fragment.app.Fragment +import androidx.viewpager2.widget.ViewPager2 +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.ViewPagerAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivityPaymentMethodBinding +import com.abbidot.tracker.ui.fragment.subscribe.CreditCardPaymentFragment +import com.abbidot.tracker.ui.fragment.subscribe.PaypalPaymentFragment +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil + +class PaymentMethodActivity : + BaseActivity(ActivityPaymentMethodBinding::inflate) { + + var mOrderBean: SubscriptionsOrderBean? = null + + //设备充值类型,0=首次设备充值激活,1=已添加设备再次添加设备充值,2=给当前的设备续费或者升级套餐 + var mRechargeType = ConstantInt.Type1 + lateinit var mFragments: MutableList + + override fun getTopBar() = mViewBinding.ilPaymentMethodTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_payment_method) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mOrderBean = Util.getParcelableAdaptive( + intent, ConstantString.LkSetMeal, SubscriptionsOrderBean::class.java + ) + mRechargeType = getInt(ConstantString.RechargeType, ConstantInt.Type1) + } + + mFragments = mutableListOf( + CreditCardPaymentFragment.newInstance(mContext), + PaypalPaymentFragment.newInstance(mContext) + ) + + mViewBinding.apply { + rgSwitchPaymentMethod.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + rbPaymentMethodCreditCard.id -> vp2SwitchPaymentMethod.currentItem = 0 + rbPaymentMethodPaypal.id -> vp2SwitchPaymentMethod.currentItem = 1 + } + } + + vp2SwitchPaymentMethod.let { + it.adapter = ViewPagerAdapter(mFragments, this@PaymentMethodActivity) + ViewUtil.instance.setViewPager2Config(it, + object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + when (position) { + 0 -> rgSwitchPaymentMethod.check(rbPaymentMethodCreditCard.id) + 1 -> rgSwitchPaymentMethod.check(rbPaymentMethodPaypal.id) + } + } + }) + } + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.PayBack) { + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/PaymentSuccessActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/PaymentSuccessActivity.kt new file mode 100644 index 0000000..8637eea --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/PaymentSuccessActivity.kt @@ -0,0 +1,237 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.content.Intent +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.activity.viewModels +import androidx.core.content.ContextCompat +import coil.load +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.PayResultBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityPaymentSuccessBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.AddTrackerDeviceViewModel +import com.abbidot.tracker.ui.activity.pet.first.FirstSetPetTypeActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.clj.fastble.BleManager + +class PaymentSuccessActivity : + BaseActivity(ActivityPaymentSuccessBinding::inflate) { + + private val mAddTrackerViewModel: AddTrackerDeviceViewModel by viewModels() + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + + private var mPayResult: PayResultBean? = null + private var mMac = "" + + override fun getTopBar() = null + + override fun initData() { + super.initData() + intent.extras?.apply { + mPayResult = Util.getParcelableAdaptive( + intent, ConstantString.Amount, PayResultBean::class.java + ) + } + + mViewBinding.apply { + ivPaymentSuccessAnim.load(R.drawable.pwd_reset_successful_done) + mPayResult?.let { + +// if (it.rechargeType == ConstantInt.Type0) { +// btnPaymentSuccessContinue.setText(R.string.txt_continue) +// } + if (it.rechargeType != ConstantInt.Type2) { + btnPaymentSuccessContinue.isEnabled = false + //刷新设备绑定 + mAddTrackerViewModel.updatePayDeviceState( + this@PaymentSuccessActivity, it.deviceId + ) + } + + tvPaymentSuccessDeviceId.text = it.deviceOutId + tvPaymentSuccessExpiresOnDate.text = Utils.stringToDate( + it.expirationTime, resultFormat = Utils.DATE_FORMAT_PATTERN_EN7 + ) + val times = Utils.getDayToYearMonthDay(it.surplusDays) + ilPaymentSuccessCountDownLayout.tvAddSuccessDeviceExpiresDays.text = times[0] + ilPaymentSuccessCountDownLayout.tvAddSuccessDeviceExpiresHours.text = times[1] + ilPaymentSuccessCountDownLayout.tvAddSuccessDeviceExpiresMin.text = times[2] + } + setOnClickListenerViews( + btnPaymentSuccessContinue, ilPaymentSuccessBluetoothTips.trbBleConnectState + ) + + mMac = MMKVUtil.getString(MMKVKey.FirstDeviceMac) + if (TextUtils.isEmpty(mMac)) { + ilPaymentSuccessBluetoothTips.root.visibility = View.GONE + } else { + connectBle(mMac) + } + } + } + + override fun liveDataObserve() { + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + mViewBinding.ilPaymentSuccessBluetoothTips.trbBleConnectState.let { view -> + val bgColor = if (ble.conState == ConState.CONNECTED) { + setButtonEnabled(mViewBinding.btnPaymentSuccessContinue, ConstantInt.Type1) + view.text = getString(R.string.tracker_manage_set_ble_connect) + ContextCompat.getColor(mContext, R.color.cyan_color1) + } else { + setButtonEnabled(mViewBinding.btnPaymentSuccessContinue, ConstantInt.Type0) + view.text = getString(R.string.tracker_manage_set_ble_unconnect) + ContextCompat.getColor(mContext, R.color.red_color4) + } + view.setBackgroundColor(bgColor) + ViewUtil.instance.bleConStateToast(this, ble.conState, Gravity.CENTER) + } + } + + //搜索蓝牙的设备状态 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + setButtonEnabled(mViewBinding.btnPaymentSuccessContinue, ConstantInt.Type0) + if (conState == ConState.DEVICE_NOT_FOUND) { +// ViewUtil.instance.showDialog( +// mContext, +// R.string.txt_no_search_device, +// object : BaseDialog.OnDialogOkListener { +// override fun onOkClick(dialog: BaseDialog<*>) { +// dialog.dismiss() +// } +// }, +// okTextResId = R.string.txt_sure, +// cancelTextResId = R.string.txt_cancel +// ) + } + mViewBinding.ilPaymentSuccessBluetoothTips.trbBleConnectState.let { view -> + ViewUtil.instance.bleConActionState(this, view, conState) + } + } + + //接收蓝牙返回数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + mPayResult?.apply { + if (mMac == receiveData.mac) { + val data = receiveData.data + setButtonEnabled(mViewBinding.btnPaymentSuccessContinue, ConstantInt.Type1) + parseData(data) + } + } + } + + + mAddTrackerViewModel.mUpdatePayDeviceStateLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mViewBinding.btnPaymentSuccessContinue.isEnabled = true + MMKVUtil.putInt(MMKVKey.isBindDevice, ConstantInt.isBind) + XEventBus.post(EventName.RefreshDevice) + } + + override fun onRequestError(exceptionCode: String?) { + LogUtil.e("支付成功后,更新绑定状态------->请求宠物数据错误onRequestError,再次请求") + if (exceptionCode == mNetworkRequestsFailLimit) { + } else { + mAddTrackerViewModel.updatePayDeviceState( + this@PaymentSuccessActivity, mPayResult!!.deviceId + ) + } + } + }) + } + } + + /** + * 解析数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 0x1A && data1 == 1 && data2 == 0) { + XEventBus.post(EventName.PayBack) + mPayResult?.let { + if (it.rechargeType == ConstantInt.Type0) { + val intent = Intent(mContext, FirstSetPetTypeActivity::class.java) + intent.putExtra(ConstantString.isFirstBind, true) + startActivity(intent) + } + } + finish() + } + } + } + + private fun connectBle(mac: String) { + //是否连接了蓝牙 + if (BleManager.getInstance().isConnected(mac)) { + mViewBinding.ilPaymentSuccessBluetoothTips.trbBleConnectState.let { + it.setBackgroundColor( + ContextCompat.getColor(mContext, R.color.cyan_color1) + ) + it.text = getString(R.string.tracker_manage_set_ble_connect) + } + } else { + setButtonEnabled(mViewBinding.btnPaymentSuccessContinue, ConstantInt.Type0) + mConnectionDeviceViewModel.connectDeviceToMac( + this@PaymentSuccessActivity, mac + ) + } + } + + /** + * 手动连接/断开设备 + */ + private fun connectionBtn(btnString: String) { + mPayResult?.apply { + if (btnString == getString(R.string.tracker_manage_set_ble_unconnect)) { + connectBle(mMac) + } else if (btnString == getString(R.string.tracker_manage_set_ble_connect)) { +// ViewUtil.instance.operateDialogTips(mContext, R.string.txt_disconnect_tips) { +// SRBleUtil.instance.disconnectToMac(mac) +// } + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilPaymentSuccessBluetoothTips.trbBleConnectState -> connectionBtn( + ilPaymentSuccessBluetoothTips.trbBleConnectState.text.toString() + ) + + btnPaymentSuccessContinue -> { + SRBleUtil.instance.getConnectMacDevice(mMac)?.let { + setButtonEnabled( + mViewBinding.btnPaymentSuccessContinue, ConstantInt.Type0 + ) + SRBleUtil.instance.writeData( + it.bleDevice, SRBleCmdUtil.instance.setWorkingStatus() + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/RequestRefundActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/RequestRefundActivity.kt new file mode 100644 index 0000000..0f850a9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/RequestRefundActivity.kt @@ -0,0 +1,113 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.text.TextUtils +import android.view.View +import androidx.activity.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivityRequestRefundBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.vm.SubscriptionManageViewModel + +class RequestRefundActivity : + BaseActivity(ActivityRequestRefundBinding::inflate) { + + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + + //套餐 + private var mSetMealBean: SubscriptionsOrderBean? = null + + override fun getTopBar() = mViewBinding.ilRequestRefundTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_request_refund) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mSetMealBean = Util.getParcelableAdaptive( + intent, ConstantString.SetMeal, SubscriptionsOrderBean::class.java + ) + + mViewBinding.apply { + mSetMealBean?.let { + val orderId = getString(R.string.txt_order_id) + " ${it.orderNum}" + tvRequestRefundOrderId.text = orderId + val planName = getString(R.string.txt_plan) + " ${it.mealName}" + tvRequestRefundPlanName.text = planName + val refundRefund = getString(R.string.txt_refund) + ": " + String.format( + mContext.getString(R.string.txt_money_unit), it.totalAmountWithTax + ) + tvRequestRefundRefund.text = refundRefund + } + + setOnClickListenerViews(btnRequestRefundRecharge) + } + } + + override fun liveDataObserve() { + mSubscriptionViewModel.apply { + mCommitRefundListLiveData.observe(this@RequestRefundActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + if (isCanRefund == ConstantInt.Type0) { + showToast(R.string.txt_successful, true) + } else { + mSetMealBean?.let { meal -> + mSubscriptionViewModel.refund(meal.orderNum) + } + } + } + } + + override fun onRequestError(exceptionCode: String?) { + setButtonEnabled(mViewBinding.btnRequestRefundRecharge) + } + }) + } + + mRefundLiveData.observe(this@RequestRefundActivity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + showToast(R.string.txt_successful, true) + } + }) + } + } + } + + private fun commitRefund() { + val typeMessage = mViewBinding.etRequestRefundTypeMessage.text.toString() + if (TextUtils.isEmpty(typeMessage)) { + showToast(R.string.txt_not_null) + setButtonEnabled(mViewBinding.btnRequestRefundRecharge) + return + } + mSetMealBean?.apply { + mSubscriptionViewModel.commitRefund( + orderNum, + transactionId, + (totalAmountWithTax * 100).toInt(), + subscriptionId, + mealName, + typeMessage, + deviceId, + deviceOutId, + payType + ) + } + } + + override fun onClick(v: View?) { + setButtonEnabled(v!!) + mViewBinding.apply { + when (v) { + btnRequestRefundRecharge -> commitRefund() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionDetailActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionDetailActivity.kt new file mode 100644 index 0000000..3b55265 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionDetailActivity.kt @@ -0,0 +1,84 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.SetMealBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.databinding.ActivitySubscriptionDetailBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.vm.CountDownTimerViewModel + +class SubscriptionDetailActivity : + BaseActivity(ActivitySubscriptionDetailBinding::inflate) { + + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + + private var mSetMealBean: SetMealBean? = null + + override fun getTopBar() = mViewBinding.ilSubscribeDetailTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_subscription_detail) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mSetMealBean = + Util.getParcelableAdaptive(intent, ConstantString.SetMeal, SetMealBean::class.java) + } + + mViewBinding.apply { + mSetMealBean?.let { + tvSubscribeDetailDeviceOutId.text = it.mealId + tvSubscribeDetailPlanType.text = if (it.hotType == ConstantInt.Type0) { + btnSubscribeDetailEnd.visibility = View.VISIBLE + ilSubscribeDetailCountDown.root.visibility = View.GONE + btnSubscribeDetailRecharge.visibility = View.GONE + getString(R.string.txt_auto_renew) + } else { + btnSubscribeDetailRecharge.visibility = View.VISIBLE + ilSubscribeDetailCountDown.root.visibility = View.VISIBLE + btnSubscribeDetailEnd.visibility = View.GONE + mCountDownTimerViewModel.startCountDown( + (it.createdDate.toLong() - System.currentTimeMillis()) / 1000, 60 + ) + it.name + } + tvSubscribeDetailRenewYear.text = + String.format(mContext.getString(R.string.txt_renew_year), it.accountFee) + tvSubscribeDetailRenewsOn.text = + Utils.formatTime(it.createdDate.toLong(), Utils.DATE_FORMAT_PATTERN_EN6) + } + setOnClickListenerViews(btnSubscribeDetailRecharge, btnSubscribeDetailEnd) + } + } + + override fun liveDataObserve() { + mCountDownTimerViewModel.mCountDowningLiveData.observe(this) { + mViewBinding.ilSubscribeDetailCountDown.apply { + val times = Utils.getSecondToDayHourMin(it) + tvAddSuccessDeviceExpiresDays.text = times[0] + tvAddSuccessDeviceExpiresHours.text = times[1] + tvAddSuccessDeviceExpiresMin.text = times[2] + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSubscribeDetailEnd -> finish() + btnSubscribeDetailRecharge -> startActivity( + Intent( + mContext, SubscriptionPlanActivity::class.java + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionHistoryActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionHistoryActivity.kt new file mode 100644 index 0000000..769e89c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionHistoryActivity.kt @@ -0,0 +1,60 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import androidx.activity.viewModels +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.SubscriptionHistoryAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivitySubscriptionHistoryBinding +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.SubscriptionManageViewModel + +class SubscriptionHistoryActivity : + BaseActivity(ActivitySubscriptionHistoryBinding::inflate) { + + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + + private lateinit var mHistoryAdapter: SubscriptionHistoryAdapter + private var mDeviceId = "" + + override fun getTopBar() = mViewBinding.ilSubscriptionHistoryTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_subscription_history) + setLeftBackImage(R.drawable.icon_white_back_svg) + + mHistoryAdapter = SubscriptionHistoryAdapter(this@SubscriptionHistoryActivity, null) + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvSubscriptionHistoryList, mHistoryAdapter, bottom = AppUtils.dpToPx(12) + ) + } + + intent?.extras?.apply { + mDeviceId = getString(ConstantString.DeviceId, "") + } + } + + override fun onResume() { + super.onResume() + mSubscriptionViewModel.getSubscriptionsHistory(mDeviceId) + } + + override fun liveDataObserve() { +// XEventBus.observe(this, EventName.UpdateData) { +// mSubscriptionViewModel.getSubscriptionsHistory(mDeviceId) +// } + mSubscriptionViewModel.mSubscriptionsHistoryLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + mHistoryAdapter.setData(list, true) + } + } + }) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionPlanActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionPlanActivity.kt new file mode 100644 index 0000000..52c1804 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SubscriptionPlanActivity.kt @@ -0,0 +1,140 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.content.Intent +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.SubscriptionPlanAdapter +import com.abbidot.tracker.adapter.SubscriptionPlanDetailAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivitySubscriptionPlanBinding +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.SubscriptionManageViewModel + +/** + * 选择订阅计划 + */ +class SubscriptionPlanActivity : + BaseActivity(ActivitySubscriptionPlanBinding::inflate) { + + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + + private lateinit var mPlanAdapter: SubscriptionPlanAdapter + private lateinit var mPlanDetailAdapter: SubscriptionPlanDetailAdapter + + private var mSelectPlanIndex = 0 + + private var mOrderBean: SubscriptionsOrderBean? = null + + //设备充值类型,0=首次设备充值激活,1=已添加设备再次添加设备充值,2=给当前的设备续费或者升级套餐 + private var mRechargeType = ConstantInt.Type1 + + override fun getTopBar() = mViewBinding.subscribePlanTopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_choose_your_plan) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mOrderBean = Util.getParcelableAdaptive( + intent, ConstantString.LkSetMeal, SubscriptionsOrderBean::class.java + ) + val isFirstBind = getBoolean(ConstantString.isFirstBind, false) + if (isFirstBind) mRechargeType = ConstantInt.Type0 + } + + addListData() + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewGridLayout( + mContext, + rvSubscribePlanList, + mPlanAdapter, + 2, + AppUtils.dpToPx(140), + offset = AppUtils.dpToPx(44) + ) + + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, rvSubscribePlanDetailList, mPlanDetailAdapter, bottom = AppUtils.dpToPx(8) + ) + + setOnClickListenerViews(btnSelectSubscribePlanContinue) + } + + mSubscriptionViewModel.getPackageType() + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.PayBack) { + finish() + } + mSubscriptionViewModel.mPackageTypeLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { list -> + list[0].selected = true + mPlanAdapter.setData(list, true) + mPlanDetailAdapter.setData(list[0].list, true) + } + } + }) + } + } + + private fun addListData() { + mPlanAdapter = SubscriptionPlanAdapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val list = getData() + if (pos > list.size - 1) return + val dataBean = list[pos] + if (dataBean.selected) return + for (i in 0 until list.size) { + val data = list[i] + if (data.selected) { + data.selected = false + notifyItemChanged(i) + break + } + } + dataBean.selected = true + mSelectPlanIndex = pos + notifyItemChanged(pos) + + mPlanDetailAdapter.setData(dataBean.list, true) + } + }) + } + + mPlanDetailAdapter = SubscriptionPlanDetailAdapter(mContext, null) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSelectSubscribePlanContinue -> Intent( + mContext, ChoosePlanActivity::class.java + ).let { + if (mPlanAdapter.getData().isNotEmpty()) { + it.putExtra( + ConstantString.Title, mPlanAdapter.getData()[mSelectPlanIndex].name + ) + it.putExtra(ConstantString.LkSetMeal, mOrderBean) + it.putExtra(ConstantString.RechargeType, mRechargeType) + startActivity(it) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SureSubscriptionPlanActivity.kt b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SureSubscriptionPlanActivity.kt new file mode 100644 index 0000000..c815546 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/activity/subscribe/SureSubscriptionPlanActivity.kt @@ -0,0 +1,350 @@ +package com.abbidot.tracker.ui.activity.subscribe + +import android.content.Intent +import android.graphics.Paint +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.activity.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.SureSubscribeSummaryAdapter +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.PackageBean +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.ActivitySureSubscriptionPlanBinding +import com.abbidot.tracker.ui.activity.WebViewActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.SubscriptionManageViewModel +import kotlin.math.abs + +class SureSubscriptionPlanActivity : + BaseActivity(ActivitySureSubscriptionPlanBinding::inflate) { + + private val mSubscriptionViewModel: SubscriptionManageViewModel by viewModels() + + private var mPackageBean: PackageBean? = null + private var mOrderBean: SubscriptionsOrderBean? = null + + //每年的保险服务费用 + private val mInsuranceMoney = 12.90 + + //保险总费用 + private var mSumInsuranceMoney = 0.0 + + //税率 + private val mTaxRate = 0.0825 + + //付款的总金额(含税) + private var mTotalWithTaxMoney = 0.0 + + //付款的总金额(不含税) + private var mTotalMoney = 0.0 + + //是否是升级套餐 + private var isUpgrade = false + + //设备充值类型,0=首次设备充值激活,1=已添加设备再次添加设备充值,2=给当前的设备续费或者升级套餐 + private var mRechargeType = ConstantInt.Type1 + + //升级套餐还剩下多少差价 + private var mResidualMoney = 0.0 + + private lateinit var mSummaryAdapter: SureSubscribeSummaryAdapter + + override fun getTopBar() = mViewBinding.sureSubscribePlan1TopBar.titleTopBar + + override fun initData() { + super.initData() + setTopBarTitle(R.string.txt_confirm_plan) + setLeftBackImage(R.drawable.icon_white_back_svg) + + intent.extras?.apply { + mPackageBean = + Util.getParcelableAdaptive(intent, ConstantString.SetMeal, PackageBean::class.java) + mOrderBean = Util.getParcelableAdaptive( + intent, ConstantString.LkSetMeal, SubscriptionsOrderBean::class.java + ) + isUpgrade = getBoolean(ConstantString.Upgrade, false) + mRechargeType = getInt(ConstantString.RechargeType, ConstantInt.Type1) + } + + mSummaryAdapter = SureSubscribeSummaryAdapter(this, mutableListOf()) + + mOrderBean?.let { + if (isUpgrade) { + it.isUpdateOrder = ConstantInt.Type1 + //套餐总天数 + val totalDay = if (it.mealUnit == ConstantString.PackageUnitYear) { + it.mealPeriod * 365 + } else { + 30 + } + mResidualMoney = + ((it.totalAmountWithTax - it.tax - it.reactivatePrice) / totalDay) * it.surplusDays + } else { + it.isUpdateOrder = ConstantInt.Type0 + } + } + + mViewBinding.apply { + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext, + ilSubscribePlanSummary.rvSureSubscribePlanSummary, + mSummaryAdapter, + bottom = AppUtils.dpToPx(4) + ) + + mOrderBean?.let { + ilSubscribePlanDetail.tvSureSubscribePlanDeviceId.text = it.deviceOutId + val nowTimestamp = System.currentTimeMillis() + + mPackageBean?.let { p -> + val renewalString = if (p.mealUnit == ConstantString.PackageUnitYear) { + ilSubscribePlanDetail.tvSureSubscribePlanPer.text = + String.format(getString(R.string.txt_year_unit), p.period) + val renewalTimestamp = + Utils.getAfterHowTimestamp(nowTimestamp, p.period * 365L) + val renewalDate = + Utils.formatTime(renewalTimestamp, Utils.DATE_FORMAT_PATTERN_EN7) + String.format( + getString(R.string.txt_renewal_year), + Utils.formatDecimal(p.autoRenewPrice, 2), + renewalDate + ) + } else { + ilSubscribePlanDetail.tvSureSubscribePlanPer.text = + String.format(getString(R.string.txt_month_unit), p.period) + val renewalTimestamp = Utils.getAfterHowTimestamp(nowTimestamp, 30L) + val renewalDate = + Utils.formatTime(renewalTimestamp, Utils.DATE_FORMAT_PATTERN_EN7) + String.format( + getString(R.string.txt_renewal_month), + Utils.formatDecimal(p.autoRenewPrice, 2), + renewalDate + ) + } + ilSubscribePlanDetail.tvSureSubscribePlanUnderused.text = renewalString + + val insuranceMoneyString = + "${p.period} x $${Utils.formatDecimal(mInsuranceMoney, 2)}" + ilSubscribePlanInsurance.tvSureSubscribePlanInsurancePer.text = + insuranceMoneyString + + val packageName = "${p.planName}(${p.planCategory})" + ilSubscribePlanDetail.tvSureSubscribePlanName.text = packageName + val price = if (p.discountedPrice > 0.0) p.discountedPrice else p.planPrice + val priceString = String.format( + getString(R.string.txt_money_unit), Utils.formatDecimal(price, 2) + ) + ilSubscribePlanDetail.tvSureSubscribePlanMoney.text = priceString + + ViewUtil.instance.addMenuBean( + mSummaryAdapter.getData(), + p.planName, + Utils.formatDecimal(price, 2), + colorRedId = R.color.data_black_color + ) + mTotalMoney += price + mSumInsuranceMoney = + Utils.formatDecimal(mInsuranceMoney * p.period, 2).toDouble() + ViewUtil.instance.addMenuBean( + mSummaryAdapter.getData(), + getString(R.string.txt_annual_care_text), + Utils.formatDecimal(mSumInsuranceMoney, 2), + colorRedId = R.color.data_black_color + ) + mTotalMoney += mSumInsuranceMoney + + if (isUpgrade) { + mTotalMoney -= mResidualMoney + ViewUtil.instance.addMenuBean( + mSummaryAdapter.getData(), + getString(R.string.txt_residual_value), + Utils.formatDecimal(mResidualMoney, 2), + colorRedId = R.color.red_color5, + isSwitch = true + ) + } + ViewUtil.instance.addMenuBean( + mSummaryAdapter.getData(), + getString(R.string.txt_sales_tax), + "0", + colorRedId = R.color.data_black_color + ) + } + + //判断套餐是否过期 + if (!TextUtils.isEmpty(it.orderNum) && it.surplusDays == 0L && it.subscriptionStatus == ConstantInt.Close) { + mOrderBean?.reactivation = 1 + mSubscriptionViewModel.getReactivationFee(it.orderNum) + } else { + mOrderBean?.reactivation = 0 + mOrderBean?.reactivatePrice = 0.0 + updateMoney() + } + } + + //添加下划线 + ilSubscribePlanDetail.tvSureSubscribePlanTermsConditions.paint.flags = + Paint.UNDERLINE_TEXT_FLAG + + ilSubscribePlanInsurance.cbSureSubscribeInsureSwitch.setOnCheckedChangeListener { buttonView, isChecked -> + mOrderBean?.let { order -> + if (isUpgrade && order.annualCareType == ConstantInt.Type1) { + ilSubscribePlanInsurance.cbSureSubscribeInsureSwitch.isChecked = !isChecked + showToast(R.string.txt_annual_care_included, gravity = Gravity.CENTER) + return@setOnCheckedChangeListener + } + val list = mSummaryAdapter.getData() + if (isChecked) { + MenuTxtBean( + getString(R.string.txt_annual_care_text), + Utils.formatDecimal(mSumInsuranceMoney, 2) + ).let { + it.colorRedId = R.color.data_black_color + list.add(1, it) + } + mTotalMoney += mSumInsuranceMoney + } else { + list.removeAt(1) + mTotalMoney -= mSumInsuranceMoney + mSummaryAdapter.notifyItemRemoved(1) + } + updateMoney() + } + } + setOnClickListenerViews( + btnSureSubscribePlan1Continue, + ilSubscribePlanDetail.tvSureSubscribePlanTermsConditions, + ilSubscribePlanInsurance.ivSureSubscribePlanInsurance + ) + } + } + + override fun liveDataObserve() { + XEventBus.observe(this, EventName.PayBack) { + if (isUpgrade) { + //升级套餐成功 + MMKVUtil.putInt(MMKVKey.MealType, ConstantInt.PremiumPackage) + } + //通知首页刷新宠物信息,防止没有加载,显示Fragment使用老的套餐数据,升级套餐和套餐过期充值使用 + XEventBus.post(EventName.RefreshPackage) + finish() + } + mSubscriptionViewModel.mReactivationFeeLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mOrderBean?.reactivatePrice = this + mSummaryAdapter.getData().add(2, MenuTxtBean( + getString(R.string.txt_reactivation), Utils.formatDecimal(this, 2) + ).apply { + colorRedId = R.color.data_black_color + }) + mTotalMoney += this + } + updateMoney() + } + }) + } + } + + private fun updateMoney() { + mViewBinding.apply { + val list = mSummaryAdapter.getData() + val taxMoney = Utils.formatDecimal(mTaxRate * mTotalMoney, 2).toDouble() + mOrderBean?.tax = taxMoney + list[list.size - 1].menuValue = taxMoney.toString() + mTotalWithTaxMoney = taxMoney + mTotalMoney + mTotalWithTaxMoney = abs(mTotalWithTaxMoney) + ilSubscribePlanSummary.ilSureSubscribePlanTotalLayout.tvSubscribeSummaryItemMoney.text = + String.format( + getString(R.string.txt_money_unit), Utils.formatDecimal(mTotalWithTaxMoney, 2) + ) + mSummaryAdapter.notifyItemRangeChanged(0, list.size) + + //NestedScrollView缓慢自动滑倒底部 + svSubscribePlanLayout.post { + svSubscribePlanLayout.smoothScrollTo(0, svSubscribePlanLayout.bottom) + } +// svSubscribePlanLayout.post { svSubscribePlanLayout.pageScroll(View.FOCUS_DOWN) } + } + } + + private fun createOrder() { + mOrderBean?.apply { + username = MMKVUtil.getString(MMKVKey.UserName) + annualCareType = + if (mViewBinding.ilSubscribePlanInsurance.cbSureSubscribeInsureSwitch.isChecked) { + annualCarePrice = mSumInsuranceMoney + 1 + } else { + annualCarePrice = 0.0 + 0 + } + //是否使用优惠码 1:是 0:否 + useCoupon = 0 + couponCode = "" + couponPrice = 0.0 + mPackageBean?.let { cPlan -> + mealPrice = cPlan.planPrice + mealDiscountPrice = cPlan.discountedPrice + mealName = cPlan.planName + mealId = cPlan.mealId + mealPeriod = cPlan.period + mealUnit = cPlan.mealUnit + planCategory = cPlan.planCategory +// autoRenewPrice = Utils.formatDecimal(cPlan.autoRenewPrice * mTaxRate, 2).toDouble() + autoRenewPrice = cPlan.autoRenewPrice + mealDesc = + mViewBinding.ilSubscribePlanDetail.tvSureSubscribePlanMoney.text.toString() + mViewBinding.ilSubscribePlanDetail.tvSureSubscribePlanPer.text.toString() + } + totalAmountWithTax = Utils.formatDecimal(mTotalWithTaxMoney, 2).toDouble() + Intent(mContext, PaymentMethodActivity::class.java).let { + it.putExtra(ConstantString.LkSetMeal, this) + it.putExtra(ConstantString.RechargeType, mRechargeType) + startActivity(it) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSureSubscribePlan1Continue -> createOrder() + ilSubscribePlanDetail.tvSureSubscribePlanTermsConditions -> { + val url = if (AppUtils.isChina()) { + "file:///android_asset/subscription_terms_conditions_en.html" + } else { + "file:///android_asset/subscription_terms_conditions_en.html" + } + WebViewActivity.startWebViewActivity( + mActivity, getString(R.string.txt_service_contract), url + ) + } + + ilSubscribePlanInsurance.ivSureSubscribePlanInsurance -> { + val url = if (AppUtils.isChina()) { + "file:///android_asset/annual_protection_plan_en.html" + } else { + "file:///android_asset/annual_protection_plan_en.html" + } + WebViewActivity.startWebViewActivity( + mActivity, getString(R.string.txt_service_contract), url + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/CountryCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/CountryCommon.kt new file mode 100644 index 0000000..4c13170 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/CountryCommon.kt @@ -0,0 +1,89 @@ +package com.abbidot.tracker.ui.common + +import android.content.Context +import android.text.TextUtils +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.TextViewAdapter +import com.abbidot.tracker.bean.CountryCodeBean +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.dialog.CommonListDialog + +/** + *Created by .yzq on 2024/7/10/010. + * @link + * @description: + */ +class CountryCommon(context: Context, listener: BaseRecyclerAdapter.OnItemClickListener) { + + private var mCountryCodeDialog: CommonListDialog? = null + private var mCountryCodeAdapter: TextViewAdapter + private var mCountryCode = "" + private var mCountry = "" + + private var mContext: Context = context + + init { + mCountryCodeAdapter = TextViewAdapter(context, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + if (getData().isNotEmpty()) { + val dataBean = getData()[pos] + mCountryCode = dataBean.value + mCountry = dataBean.name + mCountryCodeDialog?.dismiss() + listener.onItemClick(itemView, pos) + } + } + }) + } + } + + fun getCountryCode() = mCountryCode + fun getCountry() = mCountry + + /** + * 设置国家代码数据 + */ + fun setCountryCodeData(dataList: MutableList) { + val countryCodeList = mutableListOf() + val countryName = mContext.resources.getStringArray(R.array.country_name) + val countryCode = mContext.resources.getIntArray(R.array.country_code) + for (code in dataList) { + for (i in countryCode.indices) { + if (code.countryCode == countryCode[i]) { + val dataBean = DataBean() + dataBean.value = code.countryCode.toString() + dataBean.name = countryName[i] + countryCodeList.add(dataBean) + break + } + } + } + mCountryCodeAdapter.setData(countryCodeList, true) + } + + fun showCountryCodeDialog() { + if (null == mCountryCodeDialog) { + mCountryCodeDialog = + CommonListDialog(mContext, mCountryCodeAdapter, 3, 3, dialogHeightDp = 300) + } + mCountryCodeDialog?.show() + } + + fun codeToCountry(code: String): String { + var country = "" + if (TextUtils.isEmpty(code)) return country + val list = mCountryCodeAdapter.getData() + for (data in list) { + if (data.value == code) { + country = data.name + mCountryCode = data.value + mCountry = data.name + break + } + } + return country + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/MyCropImageContract.kt b/app/src/main/java/com/abbidot/tracker/ui/common/MyCropImageContract.kt new file mode 100644 index 0000000..d05607d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/MyCropImageContract.kt @@ -0,0 +1,41 @@ +package com.abbidot.tracker.ui.common + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Parcelable +import androidx.activity.result.contract.ActivityResultContract +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.ui.activity.MyCropImageActivity +import com.canhub.cropper.CropImage +import com.canhub.cropper.CropImageView +import com.huantansheng.easyphotos.models.album.entity.Photo + +/** + *Created by .yzq on 2022/4/24/024. + * @link + * @description:用于接收跳转裁剪图片的页面 + */ +class MyCropImageContract(aspectRatioX: Int, aspectRatioY: Int) : + ActivityResultContract() { + + private val mAspectRatioX = aspectRatioX + private val mAspectRatioY = aspectRatioY + + override fun createIntent(context: Context, input: Photo): Intent { + return Intent(context, MyCropImageActivity::class.java).apply { + putExtra(ConstantString.Photo, input) + putExtra(ConstantString.Type, mAspectRatioX) + putExtra(ConstantString.Index, mAspectRatioY) + } + } + + override fun parseResult(resultCode: Int, intent: Intent?): CropImageView.CropResult { + val result = intent?.getParcelableExtra( + CropImage.CROP_IMAGE_EXTRA_RESULT + ) as? CropImage.ActivityResult? + + return if (result == null || resultCode == Activity.RESULT_CANCELED) CropImage.CancelledResult + else result + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/PermissionsActivityCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/PermissionsActivityCommon.kt new file mode 100644 index 0000000..d353725 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/PermissionsActivityCommon.kt @@ -0,0 +1,225 @@ +package com.abbidot.tracker.ui.common + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.location.LocationManager +import android.provider.Settings +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.receiver.GPSMonitorReceiver +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager +import com.hjq.permissions.XXPermissions +import com.hjq.permissions.permission.PermissionLists + +/** + *Created by .yzq on 2024/6/12/012. + * @link + * @description: + */ +class PermissionsActivityCommon(context: Context) { + + private val mLocationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + private val mContext = context + private var mGPSListenerReceiver: GPSMonitorReceiver? = null + + /** + * 注册GPS开关广播 + */ + fun registerGpsReceiver() { + if (null == mGPSListenerReceiver) { + mGPSListenerReceiver = GPSMonitorReceiver(mLocationManager) + } + val intentFilter = IntentFilter() + intentFilter.addAction(LocationManager.PROVIDERS_CHANGED_ACTION) + // 注册广播 + mContext.registerReceiver(mGPSListenerReceiver!!, intentFilter) + } + + fun unregisterGpsReceiver() { + mGPSListenerReceiver?.let { + mContext.unregisterReceiver(it) + } + } + + /** + * 添加list数据 + */ + fun addListData(context: Context): MutableList { + val list = mutableListOf() + DataBean().apply { + name = context.getString(R.string.txt_bluetooth_enabled) + value = context.getString(R.string.txt_ble_permissions_tip) + list.add(this) + } + DataBean().apply { + name = context.getString(R.string.txt_location_enabled) + value = context.getString(R.string.txt_location_permissions_tip) + list.add(this) + } + DataBean().apply { + name = context.getString(R.string.txt_receive_notice) + value = context.getString(R.string.txt_notice_permissions_tip) + list.add(this) + } + return list + } + + /** + * 更新选中的状态item + */ + fun updateDataBeanSelectedItem( + adapter: BaseRecyclerAdapter<*>, list: MutableList, position: Int + ) { + for (i in 0 until list.size) { + val data = list[i] + if (i == position) continue + if (data.selected) { + data.selected = false + adapter.notifyItemChanged(i) + break + } + } + list[position].apply { + selected = !selected + if (state == ConstantInt.SpecialType) state = ConstantInt.Type0 + } + adapter.notifyItemChanged(position) + } + + /** + * 更新权限状态 + */ + fun updatePermissionsState(adapter: BaseRecyclerAdapter, errorType: Int) { + val list = adapter.getData() + + + //检测蓝牙权限和蓝牙开关 + if (XXPermissions.isGrantedPermissions( + mContext, mutableListOf( + PermissionLists.getBluetoothScanPermission(), + PermissionLists.getBluetoothConnectPermission() + ) + ) && SRBleUtil.instance.isBlueEnable(mContext) + ) { + list[0].state = ConstantInt.Type1 + list[0].selected = false + adapter.notifyItemChanged(0) + } else { + if (list[0].state == ConstantInt.Type1) { + list[0].state = errorType + adapter.notifyItemChanged(0) + } + } + mutableListOf( + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + //检查定位权限 + if (XXPermissions.isGrantedPermissions( + mContext, mutableListOf( + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + ) && mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + ) { + list[1].state = ConstantInt.Type1 + list[1].selected = false + adapter.notifyItemChanged(1) + } else { + if (list[1].state == ConstantInt.Type1) { + list[1].state = errorType + adapter.notifyItemChanged(1) + } + } + + //检测通知权限 + if (XXPermissions.isGrantedPermissions( + mContext, mutableListOf( + PermissionLists.getPostNotificationsPermission(), + PermissionLists.getNotificationServicePermission() + ) + ) + ) { + list[2].state = ConstantInt.Type1 + list[2].selected = false + adapter.notifyItemChanged(2) + } else { + if (list[2].state == ConstantInt.Type1) { + list[2].state = errorType + adapter.notifyItemChanged(2) + } + } + + //自动展开更多的说明项 + for (i in 0 until list.size) { + val data = list[i] + if (data.state == ConstantInt.Type0) { + data.selected = true + adapter.notifyItemChanged(i) + break + } + } + } + + /** + * 操作蓝牙权限和开关 + */ + fun operateBluetooth() { + Util.checkPermissions( + mContext, + object : Util.RequestPermissionCallback { + override fun onRequestPermissionSuccess() { + if (BleManager.getInstance().isSupportBle) { + SRBleUtil.instance.openBluetooth(mContext) + } + } + }, + "蓝牙权限", + PermissionLists.getBluetoothScanPermission(), + PermissionLists.getBluetoothConnectPermission() + ) + } + + /** + * 操作定位权限和GPS开关 + */ + fun operateGPSLocation() { + Util.checkPermissions( + mContext, + object : Util.RequestPermissionCallback { + override fun onRequestPermissionSuccess() { + if (!mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + mContext.startActivity(intent) + } + } + }, + "定位权限", + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + } + + /** + * 操作通知权限 + */ + fun operateNotifications(adapter: BaseRecyclerAdapter, expansionState: Int) { + Util.checkPermissions( + mContext, + object : Util.RequestPermissionCallback { + override fun onRequestPermissionSuccess() { + updatePermissionsState(adapter, expansionState) + } + }, + "通知权限", + PermissionLists.getPostNotificationsPermission(), + PermissionLists.getNotificationServicePermission() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/PetProfileCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/PetProfileCommon.kt new file mode 100644 index 0000000..a71f764 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/PetProfileCommon.kt @@ -0,0 +1,196 @@ +package com.abbidot.tracker.ui.common + +import android.content.Context +import android.view.View +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.widget.NestedScrollView +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.PetLabelsAdapter +import com.abbidot.tracker.adapter.TextViewAdapter +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.LabelBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.dialog.CommonListDialog +import com.abbidot.tracker.dialog.SelectPetLabelDialog +import com.abbidot.tracker.dialog.ShowCalenderDialog +import com.abbidot.tracker.util.ViewUtil +import com.haibin.calendarview.Calendar + +/** + *Created by .yzq on 2024/6/16/016. + * @link + * @description: + */ +class PetProfileCommon( + context: Context, showSelectLabelsRecyclerView: RecyclerView, petBean: PetBean? +) { + + private val mContext = context + private val mShowSelectLabelsRecyclerView = showSelectLabelsRecyclerView + + private var mSelectLabelList: MutableList = mutableListOf() + private val mPetBean = petBean + + private var mSelectLabelDialog: SelectPetLabelDialog? = null + private var mBirthdayDialog: ShowCalenderDialog? = null + private var mProfileSexDialog: CommonListDialog? = null + + private lateinit var mLabelsAdapter: PetLabelsAdapter + private lateinit var mSexAdapter: TextViewAdapter + private val mSelectLabelsAdapter: PetLabelsAdapter = + PetLabelsAdapter(mContext, mSelectLabelList) + + init { + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext, + mShowSelectLabelsRecyclerView, + mSelectLabelsAdapter, + right = AppUtils.dpToPx(6), + bottom = AppUtils.dpToPx(8) + ) + + mLabelsAdapter = PetLabelsAdapter(mContext, null).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + if (mLabelsAdapter.getData().isNotEmpty()){ + val label = mLabelsAdapter.getData()[pos] + label.check = !label.check + notifyItemChanged(pos) + } + } + }) + } + } + + fun getLabelData(labels: MutableList) { + val labelList = mutableListOf() + for (i in 0 until labels.size) { + LabelBean().run { + name = labels[i] + if (mSelectLabelList.size > 0) { + for (label in mSelectLabelList) { + if (name == label.name) check = true + } + } + labelList.add(this) + } + } + mLabelsAdapter.setData(labelList, true) + } + + /** + * 显示标签弹窗 + */ + fun showLabelDialog(okListener: BaseDialog.OnDialogOkListener) { + if (null == mSelectLabelDialog) { + mSelectLabelDialog = SelectPetLabelDialog(mContext, mLabelsAdapter).apply { + setOkListener(okListener) + } + } + mSelectLabelDialog?.show() + } + + /** + * 显示已选的标签 + */ + fun showSelectPetLabels( + labelTitle: TextView, scrollView: NestedScrollView + ) { + val labelList = mLabelsAdapter.getData() + mSelectLabelList.clear() + mSelectLabelsAdapter.notifyItemRangeChanged(0, mSelectLabelList.size) + for (lab in labelList) { + if (lab.check) { + LabelBean().apply { + name = lab.name + mSelectLabelList.add(this) + } + } + } + mSelectLabelsAdapter.setData(mSelectLabelList, true) + + val textColor = if (mSelectLabelList.size > 0) ContextCompat.getColor( + mContext, R.color.data_black_color + ) + else ContextCompat.getColor(mContext, R.color.block_color2) + labelTitle.setTextColor(textColor) + + scrollView.fullScroll(View.FOCUS_DOWN) + + mPetBean?.apply { + labels = if (mSelectLabelList.size > 0) mSelectLabelList.joinToString(",") { it.name } + else "" + } + } + + /** + * 显示生日弹窗 + */ + fun showBirthdayDialog(showSelectDateView: TextView) { + if (null == mBirthdayDialog) { + val calendar = java.util.Calendar.getInstance() + val cYear = calendar.get(java.util.Calendar.YEAR) + mBirthdayDialog = + ShowCalenderDialog(mContext, object : BaseDialog.OnDialogSelectListener { + override fun onSelectClick(dialog: BaseDialog<*>, any: Any) { + if (any is Calendar) { + val selectBirthday = + Utils.formatTime(any.timeInMillis, Utils.DATE_FORMAT_PATTERN_EN3) + showSelectDateView.text = selectBirthday + mPetBean?.apply { + birthdayDate = + Utils.formatTime(any.timeInMillis, Utils.DATE_FORMAT_PATTERN_CN) + } + } + } + },minYear = cYear-20) + } + mBirthdayDialog?.show() + } + + /** + * 设置性别数据 + */ + fun setSexListData(profileSexTextView: TextView) { + val sexList = mutableListOf() + DataBean().run { + name = mContext.getString(R.string.pet_manage_gender_male) + menuType = ConstantInt.Man + sexList.add(this) + } + DataBean().run { + name = mContext.getString(R.string.pet_manage_gender_female) + menuType = ConstantInt.WoMan + sexList.add(this) + } + mSexAdapter = TextViewAdapter(mContext, sexList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val dataBean = sexList[pos] + mPetBean?.apply { + gender = dataBean.menuType + } + profileSexTextView.text = sexList[pos].name + mProfileSexDialog?.dismiss() + } + }) + } + } + + /** + * 显示性别弹窗 + */ + fun showSexDialog() { + if (null == mProfileSexDialog) { + mProfileSexDialog = CommonListDialog(mContext, mSexAdapter, 3, 3) + } + mProfileSexDialog?.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/UnitSelectDialog.kt b/app/src/main/java/com/abbidot/tracker/ui/common/UnitSelectDialog.kt new file mode 100644 index 0000000..f572589 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/UnitSelectDialog.kt @@ -0,0 +1,52 @@ +package com.abbidot.tracker.ui.common + +import android.content.Context +import android.view.View +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.TextViewAdapter +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.dialog.CommonListDialog + +/** + *Created by .yzq on 2024/7/15/015. + * @link + * @description: + */ +class UnitSelectDialog(context: Context) { + + private val mContext = context + + private var mUnitDialog: CommonListDialog? = null + + /** + * 显示单位选择弹窗 + */ + fun showUnitDialog(listener: BaseDialog.OnDialogSelectListener?) { + if (null == mUnitDialog) { + val heightUnitList = mutableListOf() + DataBean().run { + name = mContext.getString(R.string.txt_imperial) + state = ConstantInt.Type1 + heightUnitList.add(this) + } + DataBean().run { + name = mContext.getString(R.string.txt_metric) + state = ConstantInt.Type2 + heightUnitList.add(this) + } + val unitAdapter = TextViewAdapter(mContext, heightUnitList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + mUnitDialog?.dismiss() + listener?.onSelectClick(mUnitDialog!!, heightUnitList[pos].state) + } + }) + } + mUnitDialog = CommonListDialog(mContext, unitAdapter, 3, 3) + } + mUnitDialog?.show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/EditWifiZoneMapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/EditWifiZoneMapCommon.kt new file mode 100644 index 0000000..a951ba1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/EditWifiZoneMapCommon.kt @@ -0,0 +1,69 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseMapCommon +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.deprecated.ui.fragment.map.baidumap.NavigationBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.EditWiFiZoneGoogleMapFragment + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description: + */ +class EditWifiZoneMapCommon : BaseMapCommon() { + + private var mEditZoneGoogleMapFragment: EditWiFiZoneGoogleMapFragment? = null + private var mBaiduMapFragment: NavigationBaiduMapFragment? = null + + fun getMapFragment( + context: Context, wiFiZoneBean: WiFiZoneBean, mapLoadOk: () -> Unit + ): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mBaiduMapFragment = NavigationBaiduMapFragment.newInstance(context) + mBaiduMapFragment!! + } else { + mEditZoneGoogleMapFragment = + EditWiFiZoneGoogleMapFragment.newInstance(context, wiFiZoneBean, mapLoadOk) + mEditZoneGoogleMapFragment!! + } + } + + /** + * 设置显示wifi的位置 + */ + fun setShowWifiLocation(lat: Double, lon: Double, isDraggable: Boolean) { + mEditZoneGoogleMapFragment?.apply { + moveCameraLocation(lat, lon) + addImageMarker(lat, lon, R.drawable.icon_location_svg, false, isDraggable) + } + } + + /** + * 切换地图类型 + */ + fun switchSatelliteAndNormalMapType() { + switchSatelliteAndNormalMapType(mBaiduMapFragment, mEditZoneGoogleMapFragment) + } + + /** + * 切换用户、宠物和围栏的位置居中显示 + */ + fun switchShowLocation(@ConstantInt type: Int, lat: Double, lon: Double) { + switchShowLocationType( + type, lat, lon, mBaiduMapFragment, mEditZoneGoogleMapFragment + ) + } + + /** + * 设置宠物的经纬度,方便切换显示位置 + */ + fun setPetLatLon(petImageUrl: String, petType: Int, lat: Double, lon: Double) { + if (lat == 0.0 && lon == 0.0) return + mEditZoneGoogleMapFragment?.setPetCurrentLocation(petImageUrl, lat, lon, petType) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/FencesAddEditMapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/FencesAddEditMapCommon.kt new file mode 100644 index 0000000..b1086d8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/FencesAddEditMapCommon.kt @@ -0,0 +1,181 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import android.view.View +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseMapCommon +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.fragment.map.baidumap.FencesAddEditBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.FencesAddEditGoogleMapFragment +import com.abbidot.tracker.util.LonAndLatUtil +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.FencesCircleView +import com.abbidot.tracker.widget.FencesPolygonView +import com.abbidot.tracker.widget.FencesRectView +import com.google.android.gms.maps.model.LatLng + +/** + *Created by .yzq on 2024/4/23/023. + * @link + * @description: + */ +class FencesAddEditMapCommon : BaseMapCommon() { + private var mFencesGoogleMapFragment: FencesAddEditGoogleMapFragment? = null + private var mFencesBaiduMapFragment: FencesAddEditBaiduMapFragment? = null + + fun getMapFragment( + context: Context, + fencesBean: FencesBean, + fencesCircleView: FencesCircleView, + fencesRectView: FencesRectView, + fencesPolygonView: FencesPolygonView, + isEdit: Boolean, + onActivityCallBack: OnActivityCallBack?, + mapLoadOk: () -> Unit + ): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mFencesBaiduMapFragment = FencesAddEditBaiduMapFragment.newInstance( + context, + fencesBean, + fencesCircleView, + fencesRectView, + fencesPolygonView, + isEdit, + onActivityCallBack, + mapLoadOk + ) + mFencesBaiduMapFragment!! + } else { + mFencesGoogleMapFragment = FencesAddEditGoogleMapFragment.newInstance( + context, + fencesBean, + fencesCircleView, + fencesRectView, + fencesPolygonView, + isEdit, + onActivityCallBack, + mapLoadOk + ) + mFencesGoogleMapFragment!! + } + } + + /** + * 经纬度转换地址 + */ + fun getLatLonAddress(geoCoderViewModel: GeoCoderViewModel, fencesBean: FencesBean?) { + if (null != mFencesGoogleMapFragment) { + fencesBean?.apply { + geoCoderViewModel.getLatLonAddress(latitudeCenter, longitudeCenter) + } + } else if (null != mFencesBaiduMapFragment) { + fencesBean?.apply { + val convertLonLat = + LonAndLatUtil.convertFromGCJ02ToWGS84(latitudeCenter, longitudeCenter) + geoCoderViewModel.getLatLonAddress(convertLonLat[0], convertLonLat[1]) + } + } + } + + /** + * 根据预览围栏类型设置地图等级 + * @param padding 距离屏幕边缘的距离,px单位 + */ + fun restoreEditFencesSetMapZoom( + context: Context, fencesBean: FencesBean, padding: Int = AppUtils.dpToPx(80) + ) { + restoreFencesSetMapZoom( + context, fencesBean, mFencesBaiduMapFragment, mFencesGoogleMapFragment, padding + ) + } + + /** + * 上滑,下滑手势,布局绘制,围栏重新设置中心 + */ + fun resetFencesCentre(activity: BaseActivity<*>, view: View, fencesBean: FencesBean) { + fencesBean.let { + mFencesGoogleMapFragment?.apply { + view.postDelayed({ + if (activity.isFinishing) return@postDelayed + resetFencesViewCentre( + LatLng( + it.latitudeCenter, it.longitudeCenter + ) + ) + }, 100) + } + + mFencesBaiduMapFragment?.apply { + view.postDelayed({ + if (activity.isFinishing) return@postDelayed + resetFencesViewCentre( + getBaiduMapLatLng( + it.latitudeCenter, it.longitudeCenter + ) + ) + }, 600) + } + } + } + + fun setMapZoom(zoom: Float) { + if (null != mFencesGoogleMapFragment) { + mFencesGoogleMapFragment!!.setGoogleMapZoom(zoom) + } else if (null != mFencesBaiduMapFragment) { + mFencesBaiduMapFragment!!.setBaiduMapZoom(19f) + } + } + + fun setZoomTo(zoomLevel: Float) { + if (null != mFencesGoogleMapFragment) { + mFencesGoogleMapFragment!!.setZoomTo(zoomLevel) + } else if (null != mFencesBaiduMapFragment) { + } + } + + fun calculateDistanceAndGetPointLatLng( + activity: BaseActivity<*>? = null, delayMillis: Long = 1000 + ) { + activity?.showLoading(true) + if (null != mFencesGoogleMapFragment) { + mFencesGoogleMapFragment?.mapOkAndDistancePointLatLng(delayMillis) + } else if (null != mFencesBaiduMapFragment) { + mFencesBaiduMapFragment?.mapOkAndDistancePointLatLng() + } + } + + /** + * 切换地图类型 + */ + fun switchSatelliteAndNormalMapType() { + switchSatelliteAndNormalMapType(mFencesBaiduMapFragment, mFencesGoogleMapFragment) + } + + /** + * 地图是否加载好了,准备好了 + */ + fun isMapLoadOk() = isMapLoadOk(mFencesBaiduMapFragment, mFencesGoogleMapFragment) + + /** + * 设置宠物当前经纬度和头像 + */ + fun setPetHeadAndCurrentLocation(petHeadUrl: String, petType: Int, lat: Double, lon: Double) { + setPetHeadAndCurrentLocation( + petHeadUrl, petType, lat, lon, mFencesBaiduMapFragment, mFencesGoogleMapFragment + ) + } + + /** + * 切换用户、宠物和围栏的位置居中显示 + */ + fun switchShowLocation(@ConstantInt type: Int, lat: Double, lon: Double) { + switchShowLocationType(type, lat, lon, mFencesBaiduMapFragment, mFencesGoogleMapFragment) + } + + interface OnActivityCallBack { + fun calculationDistance(distances: Array) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/FencesMapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/FencesMapCommon.kt new file mode 100644 index 0000000..72c95ac --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/FencesMapCommon.kt @@ -0,0 +1,308 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import android.graphics.Point +import android.view.View +import androidx.appcompat.widget.AppCompatEditText +import androidx.appcompat.widget.LinearLayoutCompat +import androidx.fragment.app.Fragment +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.vm.ScreenshotsViewModel +import com.abbidot.tracker.deprecated.ui.fragment.map.baidumap.FencesAddBaiduMapFragment +import com.abbidot.tracker.deprecated.ui.fragment.map.googlemap.FencesAddGoogleMapFragment +import com.abbidot.tracker.util.LonAndLatUtil +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.FencesPolygonView +import com.google.android.gms.maps.model.LatLng +import kotlin.math.pow + + +/** + *Created by .yzq on 2022/7/5/005. + * @link + * @description: + */ +class FencesMapCommon { + + private var mFencesAddGoogleMapFragment: FencesAddGoogleMapFragment? = null + private var mFencesAddBaiduMapFragment: FencesAddBaiduMapFragment? = null + + fun getMapFragment( + context: Context, + recyclerView: RecyclerView, + searchResultsViewLayout: LinearLayoutCompat, + addressEditText: AppCompatEditText, + onActivityCallBack: OnActivityCallBack + ): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mFencesAddBaiduMapFragment = FencesAddBaiduMapFragment.newInstance(context) + mFencesAddBaiduMapFragment!!.setOnCalculationDistance(onActivityCallBack) + mFencesAddBaiduMapFragment!!.initRecyclerView(context, recyclerView) + mFencesAddBaiduMapFragment!! + } else { + mFencesAddGoogleMapFragment = FencesAddGoogleMapFragment.newInstance(context) + mFencesAddGoogleMapFragment!!.setOnCalculationDistance(onActivityCallBack) + mFencesAddGoogleMapFragment!!.initMapBox( + context, searchResultsViewLayout, addressEditText + ) + mFencesAddGoogleMapFragment!! + } + } + + /** + * 根据地图显示级别,计算比例尺的一像素对应几米 + */ + fun getCircleRadiusPx(circleRadius: Double): Double { + var px = 0.0 + if (null != mFencesAddGoogleMapFragment) { + val zoom = mFencesAddGoogleMapFragment!!.getGoogleMapZoom() + //一像素对应几米 + val pxDis = 2.0.pow((15.5 - zoom)) + //计算现在半径有多少像素 + px = circleRadius / pxDis + } else if (null != mFencesAddBaiduMapFragment) { + val zoom = mFencesAddBaiduMapFragment!!.getBaiduMapZoom() + //一像素对应几米 + val pxDis = 2.0.pow((18 - zoom).toDouble()) + //计算现在半径有多少像素 + px = circleRadius / pxDis + } + return px + } + + fun setCenterLatLng(latitude: Double, longitude: Double, radius: Double = 0.0) { + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment!!.mCenterLatLng = LatLng(latitude, longitude) + //设置地图缩放级别,让圆形围栏显示完整 + if (radius > 0.0) mFencesAddGoogleMapFragment?.setCircleMapZoom(radius) + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment!!.apply { + //百度地图使用GCJ02坐标 + mCenterLatLng = getBaiduMapLatLng(latitude, longitude) + LogUtil.e("编辑转换中心坐标$mCenterLatLng,gps的$latitude,$longitude") + //设置地图缩放级别,让圆形围栏显示完整 + if (radius > 0.0) mFencesAddBaiduMapFragment?.setCircleMapZoom(radius) + } + } + } + + /** + * 获取开始点(中间点)的经纬度 + */ + fun getCenterLatLng(): DoubleArray { + var dd = DoubleArray(2) + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment!!.mCenterLatLng?.let { + dd[0] = it.latitude + dd[1] = it.longitude + } + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment!!.mCenterLatLng?.let { + //转换原始gps坐标 + dd = LonAndLatUtil.convertFromGCJ02ToWGS84(it.latitude, it.longitude) + } + } + return dd + } + + + fun setRectABCDLatLng( + context: Context, + longitudeA: Double, + latitudeA: Double, + longitudeB: Double, + latitudeB: Double, + longitudeC: Double, + latitudeC: Double, + longitudeD: Double, + latitudeD: Double + ) { + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment?.apply { + mRectALatLng = LatLng(latitudeA, longitudeA) + mRectBLatLng = LatLng(latitudeB, longitudeB) + mRectCLatLng = LatLng(latitudeC, longitudeC) + mRectDLatLng = LatLng(latitudeD, longitudeD) + //设置地图缩放级别,让矩形围栏显示完整 + mFencesAddGoogleMapFragment?.setRectMapZoom( + context, mRectALatLng!!, mRectBLatLng!!, mRectCLatLng!!, mRectDLatLng!! + ) + } + } else if (null != mFencesAddBaiduMapFragment) { + //百度地图使用GCJ02坐标 + mFencesAddBaiduMapFragment?.apply { + mRectALatLng = getBaiduMapLatLng(latitudeA, longitudeA) + mRectBLatLng = getBaiduMapLatLng(latitudeB, longitudeB) + mRectCLatLng = getBaiduMapLatLng(latitudeC, longitudeC) + mRectDLatLng = getBaiduMapLatLng(latitudeD, longitudeD) + LogUtil.e("没有加密的4个点经纬度$latitudeA,$longitudeA,\n$latitudeB.$longitudeB,\n$latitudeC,$longitudeC,\n$latitudeD,$longitudeD") + LogUtil.e("编辑的4个点经纬度$mRectALatLng,\n$mRectBLatLng,\n$mRectCLatLng,\n$mRectDLatLng,\n$mCenterLatLng") + //设置地图缩放级别,让矩形围栏显示完整 + mFencesAddBaiduMapFragment?.setRectMapZoom( + mRectALatLng!!, mRectBLatLng!!, mRectCLatLng!!, mRectDLatLng!! + ) +// mFencesAddBaiduMapFragment?.setRectMapZoom(com.baidu.mapapi.model.LatLng(latitudeA, longitudeA), com.baidu.mapapi.model.LatLng(latitudeB, longitudeB), +// com.baidu.mapapi.model.LatLng(latitudeC, longitudeC), com.baidu.mapapi.model.LatLng(latitudeD, longitudeD)) + } + } + } + + /** + * 经纬度转换为屏幕坐标点 + */ + fun latLngToPotion(wherePoint: Int): Point? { + if (null != mFencesAddGoogleMapFragment) { + return mFencesAddGoogleMapFragment!!.latLngToPotion(wherePoint) + } else if (null != mFencesAddBaiduMapFragment) { + return mFencesAddBaiduMapFragment!!.latLngToPotion(wherePoint) + } + return null + } + + /** + * 获取矩形ABCD的经纬度 + */ + fun getRectABCDLatLng(wherePoint: Int): DoubleArray { + var dd = DoubleArray(2) + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment?.apply { + when (wherePoint) { + FencesPolygonView.POINT_A -> mRectALatLng?.let { + dd[0] = it.latitude + dd[1] = it.longitude + } + + FencesPolygonView.POINT_B -> mRectBLatLng?.let { + dd[0] = it.latitude + dd[1] = it.longitude + } + + FencesPolygonView.POINT_C -> mRectCLatLng?.let { + dd[0] = it.latitude + dd[1] = it.longitude + } + + FencesPolygonView.POINT_D -> mRectDLatLng?.let { + dd[0] = it.latitude + dd[1] = it.longitude + } + } + + } + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment?.apply { + when (wherePoint) { + FencesPolygonView.POINT_A -> mRectALatLng?.let { + //转换原始gps坐标 + dd = LonAndLatUtil.convertFromGCJ02ToWGS84(it.latitude, it.longitude) + } + + FencesPolygonView.POINT_B -> mRectBLatLng?.let { + //转换原始gps坐标 + dd = LonAndLatUtil.convertFromGCJ02ToWGS84(it.latitude, it.longitude) + } + + FencesPolygonView.POINT_C -> mRectCLatLng?.let { + //转换原始gps坐标 + dd = LonAndLatUtil.convertFromGCJ02ToWGS84(it.latitude, it.longitude) + } + + FencesPolygonView.POINT_D -> mRectDLatLng?.let { + //转换原始gps坐标 + dd = LonAndLatUtil.convertFromGCJ02ToWGS84(it.latitude, it.longitude) + } + } + + } + } + return dd + } + + /** + * 刷新显示距离,点击围栏样式刷新 + */ + fun refreshDistance() { + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment!!.mapOkAndDistance() + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment!!.mapOkAndDistance() + } + } + + /** + * 隐藏搜索检索结果页面 + */ + fun setSearchResultsViewGone() { + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment!!.setSearchResultsViewGone() + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment!!.setSearchResultsViewGone() + } + } + + /** + * 搜索地址 + */ + fun searchAddress(keyword: String) { + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment!!.searchAddress(keyword) + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment!!.searchAddress(keyword) + } + } + + /** + * 经纬度转换地址 + */ + fun getLatLonAddress(geoCoderViewModel: GeoCoderViewModel) { + if (null != mFencesAddGoogleMapFragment) { + mFencesAddGoogleMapFragment!!.mCenterLatLng?.let { + geoCoderViewModel.getLatLonAddress(it.latitude, it.longitude) + } + } else if (null != mFencesAddBaiduMapFragment) { + mFencesAddBaiduMapFragment!!.mCenterLatLng?.let { + val convertLonLat = LonAndLatUtil.convertFromGCJ02ToWGS84(it.latitude, it.longitude) + geoCoderViewModel.getLatLonAddress(convertLonLat[0], convertLonLat[1]) + } + } + } + + /** + * 截屏并上传图片 + */ + fun snapshotImage( + activity: BaseActivity<*>, screenshotsViewModel: ScreenshotsViewModel, snapshotView: View + ) { + if (null != mFencesAddGoogleMapFragment) { + if (null == mFencesAddGoogleMapFragment!!.mGoogleMap) { + activity.showToast(com.abbidot.tracker.R.string.txt_load_map) + return + } + screenshotsViewModel.fencesGoogleMapSnapshotImage( + activity, + mFencesAddGoogleMapFragment!!.mGoogleMap!!, + snapshotView, + fileName = "add_fence_${System.currentTimeMillis() / 1000}.png" + ) + } else if (null != mFencesAddBaiduMapFragment) { + if (null == mFencesAddBaiduMapFragment!!.mBaiduMap) { + activity.showToast(com.abbidot.tracker.R.string.txt_load_map) + return + } + screenshotsViewModel.fencesBaiduMapSnapshotImage( + activity, + mFencesAddBaiduMapFragment!!.mBaiduMap!!, + snapshotView, + fileName = "add_fence_${System.currentTimeMillis() / 1000}.png" + ) + } + } + + interface OnActivityCallBack { + fun calculationDistance(distances: Array) + fun mapCompletion() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/HistoryDataMapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/HistoryDataMapCommon.kt new file mode 100644 index 0000000..df51415 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/HistoryDataMapCommon.kt @@ -0,0 +1,129 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseMapCommon +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.ui.fragment.map.baidumap.HistoryDataBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.HistoryDataGoogleMapFragment +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.MapMarkerInfoView + +/** + *Created by .yzq on 2022/7/5/005. + * @link + * @description: + */ +class HistoryDataMapCommon : BaseMapCommon() { + + private var mHistoryDataGoogleMapFragment: HistoryDataGoogleMapFragment? = null + private var mHistoryDataBaiduMapFragment: HistoryDataBaiduMapFragment? = null + + fun getMapFragment( + context: Context, + markerInfoView: MapMarkerInfoView, + geoCoderViewModel: GeoCoderViewModel, + mapLoadOk: () -> Unit + ): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mHistoryDataBaiduMapFragment = + HistoryDataBaiduMapFragment.newInstance(context, geoCoderViewModel, mapLoadOk) + mHistoryDataBaiduMapFragment!! + } else { + mHistoryDataGoogleMapFragment = HistoryDataGoogleMapFragment.newInstance( + context, markerInfoView, geoCoderViewModel, mapLoadOk + ) + mHistoryDataGoogleMapFragment!! + } + } + + /** + * 切换地图类型 + */ + fun switchSatelliteAndNormalMapType() { + switchSatelliteAndNormalMapType(mHistoryDataBaiduMapFragment, mHistoryDataGoogleMapFragment) + } + + suspend fun setPetBean(petBean: PetBean) { + if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.setPetBean(petBean) + } + if (null != mHistoryDataBaiduMapFragment) { + } + } + + /** + * 地图是否加载好了,准备好了 + */ + fun isMapLoadOk() = isMapLoadOk(mHistoryDataBaiduMapFragment, mHistoryDataGoogleMapFragment) + + fun slideChanged(progress: Int) { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.slideChanged(progress) + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.slideChanged(progress) + } + } + + fun slideStopChanged(progress: Int) { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.slideStopChanged(progress) + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.slideStopChanged(progress) + } + } + + fun addSetLine() { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.addSetLine() + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.addSetLine() + } + } + + fun addSetNumber() { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.addSetNumber() + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.addSetNumber() + } + } + + fun clearAllMarker() { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.clearAllMarker() + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.clearAllMarker() + } + } + + fun setLatLngData(data: MutableList) { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.setLatLngData(data) + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.setLatLngData(data) + } + } + + fun showInfoWindow(historyDataBean: HistoryDataBean) { + if (null != mHistoryDataBaiduMapFragment) { + mHistoryDataBaiduMapFragment!!.showMarkerInfoWindow(historyDataBean) + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment!!.showMarkerInfoWindow(historyDataBean) + } + } + + fun setPetUserLatLng() { + if (null != mHistoryDataBaiduMapFragment) { + } else if (null != mHistoryDataGoogleMapFragment) { + mHistoryDataGoogleMapFragment?.apply { + mUserLatLng?.let { + moveCameraLocation(it) + setPetMarkerLatLng(it) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/HomeMapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/HomeMapCommon.kt new file mode 100644 index 0000000..8c51b80 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/HomeMapCommon.kt @@ -0,0 +1,182 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseMapCommon +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.fragment.map.baidumap.HomeMapBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.HomeMapGoogleMapFragment +import com.abbidot.tracker.widget.MapDeviceNetView +import com.abbidot.tracker.widget.MapMarkerInfoView +import com.google.android.gms.maps.model.LatLng +import javax.inject.Inject + +/** + *Created by .yzq on 2022/7/5/005. + * @link + * @description: + */ +class HomeMapCommon @Inject constructor() : BaseMapCommon() { + + private var mHomeMapGoogleMapFragment: HomeMapGoogleMapFragment? = null + private var mHomeMapBaiduMapFragment: HomeMapBaiduMapFragment? = null + + fun getMapFragment( + context: Context, + markerInfoView: MapMarkerInfoView, + mapDeviceNetView: MapDeviceNetView, + mapLoadOk: () -> Unit + ): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mHomeMapBaiduMapFragment = HomeMapBaiduMapFragment.newInstance(context, mapLoadOk) + mHomeMapBaiduMapFragment!! + } else { + mHomeMapGoogleMapFragment = HomeMapGoogleMapFragment.newInstance( + context, markerInfoView, mapDeviceNetView, mapLoadOk + ) + mHomeMapGoogleMapFragment!! + } + } + + fun setMapDeviceBean(mapDeviceBean: MapDeviceBean?) { + if (null != mHomeMapBaiduMapFragment) { + mHomeMapBaiduMapFragment!!.setMapDeviceBean(mapDeviceBean) + } else if (null != mHomeMapGoogleMapFragment) { + mHomeMapGoogleMapFragment!!.setMapDeviceBean(mapDeviceBean) + } + } + + /** + * 开始每隔几秒刷新用户位置 + */ + fun startRefreshUserLocation() { + if (isMapLoadOk()) startRefreshUserLocation( + mHomeMapBaiduMapFragment, mHomeMapGoogleMapFragment + ) + } + + /** + * 获取宠物反编译好的位置信息 + */ + fun getDecPetAddressData(): HistoryDataBean? { + if (null != mHomeMapBaiduMapFragment) { + return mHomeMapBaiduMapFragment!!.mDecPetAddressData + } else if (null != mHomeMapGoogleMapFragment) { + return mHomeMapGoogleMapFragment!!.mDecPetAddressData + } + return null + } + + fun refreshPetCurrentLocation(latitude: Double, longitude: Double, isAnimMoveCamera: Boolean) { + if (null != mHomeMapBaiduMapFragment) { + mHomeMapBaiduMapFragment?.apply { + refreshPetCurrentLocation( + latitude, longitude, isAnimMoveCamera = isAnimMoveCamera + ) + } + } else if (null != mHomeMapGoogleMapFragment) { + MMKVUtil.putDouble(MMKVKey.MapShowDefaultLat, latitude) + MMKVUtil.putDouble(MMKVKey.MapShowDefaultLon, longitude) + mHomeMapGoogleMapFragment?.apply { + refreshPetCurrentLocation( + LatLng(latitude, longitude), isAnimMoveCamera = isAnimMoveCamera + ) + } + } + } + + /** + * 重新设置宠物头像 + */ + fun setPetHeadIcon(imageUrl: String, petType: Int) { + if (null != mHomeMapBaiduMapFragment) { + mHomeMapBaiduMapFragment!!.setPetHeadIconUrl(imageUrl) + } else if (null != mHomeMapGoogleMapFragment) { + mHomeMapGoogleMapFragment!!.setPetHeadIconUrl(imageUrl, petType) + } + } + + /** + * 地图是否加载好了,准备好了 + */ + fun isMapLoadOk() = isMapLoadOk(mHomeMapBaiduMapFragment, mHomeMapGoogleMapFragment) + + /** + * 切换地图类型 + */ + fun switchSatelliteAndNormalMapType() { + switchSatelliteAndNormalMapType(mHomeMapBaiduMapFragment, mHomeMapGoogleMapFragment) + } + + /** + * 切换用户和宠物的位置居中显示 + */ + fun switchShowLocation(@ConstantInt type: Int) { + switchShowLocationType(type, mHomeMapBaiduMapFragment, mHomeMapGoogleMapFragment) + } + + /** + * 画轨迹线 + */ + fun addTrackLine(latitude: Double, longitude: Double) { + if (null != mHomeMapBaiduMapFragment) { + mHomeMapBaiduMapFragment!!.addLine(latitude, longitude) + } else if (null != mHomeMapGoogleMapFragment) { + mHomeMapGoogleMapFragment!!.addLine(LatLng(latitude, longitude)) + } + } + + /** + * 画轨迹线 + */ + fun addTrackLines(trackList: MutableList) { + if (null != mHomeMapBaiduMapFragment) { + } else if (null != mHomeMapGoogleMapFragment) { + mHomeMapGoogleMapFragment!!.addLines(trackList) + } + } + + /** + * 计算用户和宠物的距离 + */ + fun getUserAndPetDistance(): Double { + var distance = 0.0 + if (null != mHomeMapBaiduMapFragment) { + distance = mHomeMapBaiduMapFragment!!.getUserAndPetDistance() + } else if (null != mHomeMapGoogleMapFragment) { + distance = mHomeMapGoogleMapFragment!!.getUserAndPetDistance() + } + return distance + } + + /** + * 开始水波纹动画 + */ + fun startRippleCircleAnim() { + if (null != mHomeMapBaiduMapFragment) { + } else if (null != mHomeMapGoogleMapFragment) { + mHomeMapGoogleMapFragment!!.startRippleCircleAnim() + } + } + + fun setMarkerInfoViewOffset() { + if (null != mHomeMapBaiduMapFragment) { + } else if (null != mHomeMapGoogleMapFragment) { + mHomeMapGoogleMapFragment!!.setMarkerInfoViewOffset() + } + } + +// fun getAddressShowMarkerInfoWindow(historyDataBean: HistoryDataBean) { +// if (null != mHomeMapBaiduMapFragment) { +// mHomeMapBaiduMapFragment!!.showMarkerInfoWindow(historyDataBean) +// } else if (null != mHomeMapGoogleMapFragment) { +// mHomeMapGoogleMapFragment!!.showMarkerInfoWindow(historyDataBean) +// } +// } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/MapLiveCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/MapLiveCommon.kt new file mode 100644 index 0000000..bd5542a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/MapLiveCommon.kt @@ -0,0 +1,47 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.ui.fragment.map.baidumap.MapLiveBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.MapLiveGoogleMapFragment +import com.google.android.gms.maps.model.LatLng + +/** + *Created by .yzq on 2022/7/5/005. + * @link + * @description: + */ +class MapLiveCommon { + + private var mMapLiveGoogleMapFragment: MapLiveGoogleMapFragment? = null + private var mMapLiveBaiduMapFragment: MapLiveBaiduMapFragment? = null + + fun getMapFragment(context: Context, imageUrl: String): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mMapLiveBaiduMapFragment = MapLiveBaiduMapFragment.newInstance(context) + mMapLiveBaiduMapFragment!!.mPetHeadUrl = imageUrl + mMapLiveBaiduMapFragment!! + } else { + mMapLiveGoogleMapFragment = MapLiveGoogleMapFragment.newInstance(context) + mMapLiveGoogleMapFragment!!.mPetHeadUrl = imageUrl + mMapLiveGoogleMapFragment!! + } + } + + fun addLine(latitude: Double, longitude: Double) { + if (null != mMapLiveBaiduMapFragment) { + mMapLiveBaiduMapFragment!!.addLine(latitude, longitude) + } else if (null != mMapLiveGoogleMapFragment) { + mMapLiveGoogleMapFragment!!.addLine(LatLng(latitude, longitude)) + } + } + + fun refreshPetCurrentLocation() { + if (null != mMapLiveBaiduMapFragment) { + mMapLiveBaiduMapFragment!!.refreshPetCurrentLocation() + } else if (null != mMapLiveGoogleMapFragment) { + mMapLiveGoogleMapFragment!!.refreshPetCurrentLocation() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/NavigationMapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/NavigationMapCommon.kt new file mode 100644 index 0000000..602103e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/NavigationMapCommon.kt @@ -0,0 +1,30 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.deprecated.ui.fragment.map.baidumap.NavigationBaiduMapFragment +import com.abbidot.tracker.deprecated.ui.fragment.map.googlemap.NavigationGoogleMapFragment + +/** + *Created by .yzq on 2022/7/5/005. + * @link + * @description: + */ +class NavigationMapCommon { + + private var mNavigationGoogleMapFragment: NavigationGoogleMapFragment? = null + private var mNavigationBaiduMapFragment: NavigationBaiduMapFragment? = null + + fun getMapFragment(context: Context,imageUrl: String): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mNavigationBaiduMapFragment = NavigationBaiduMapFragment.newInstance(context) + mNavigationBaiduMapFragment!!.mPetHeadUrl = imageUrl + mNavigationBaiduMapFragment!! + } else { + mNavigationGoogleMapFragment = NavigationGoogleMapFragment.newInstance(context) + mNavigationGoogleMapFragment!!.mPetHeadUrl = imageUrl + mNavigationGoogleMapFragment!! + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/PreviewFencesZoneCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/PreviewFencesZoneCommon.kt new file mode 100644 index 0000000..e95284a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/PreviewFencesZoneCommon.kt @@ -0,0 +1,83 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseMapCommon +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.fragment.map.baidumap.PreviewFencesBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.PreviewFencesGoogleMapFragment + +/** + *Created by .yzq on 2024/4/23/023. + * @link + * @description: + */ +class PreviewFencesZoneCommon : BaseMapCommon() { + private var mPreviewFencesGoogleMapFragment: PreviewFencesGoogleMapFragment? = null + private var mPreviewFencesBaiduMapFragment: PreviewFencesBaiduMapFragment? = null + + fun getMapFragment( + context: Context, mapLoadOk: () -> Unit + ): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mPreviewFencesBaiduMapFragment = + PreviewFencesBaiduMapFragment.newInstance(context, mapLoadOk) + mPreviewFencesBaiduMapFragment!! + } else { + mPreviewFencesGoogleMapFragment = PreviewFencesGoogleMapFragment.newInstance( + context, mapLoadOk + ) + mPreviewFencesGoogleMapFragment!! + } + } + + /** + * 切换地图类型 + */ + fun switchSatelliteAndNormalMapType() { + switchSatelliteAndNormalMapType( + mPreviewFencesBaiduMapFragment, mPreviewFencesGoogleMapFragment + ) + } + + /** + * 设置宠物位置经纬度 + */ + fun setPetCurrentLocation(petHeadUrl: String, petType: Int, lat: Double, lon: Double) { + setPetHeadAndCurrentLocation( + petHeadUrl, + petType, + lat, + lon, + mPreviewFencesBaiduMapFragment, + mPreviewFencesGoogleMapFragment + ) + } + + /** + * 切换用户、宠物和围栏的位置居中显示 + */ + fun switchShowLocation(@ConstantInt type: Int, lat: Double, lon: Double) { + switchShowLocationType( + type, lat, lon, mPreviewFencesBaiduMapFragment, mPreviewFencesGoogleMapFragment + ) + } + + /** + * 根据预览围栏类型设置地图等级 + * @param padding 距离屏幕边缘的距离,px单位 + */ + fun restorePreviewFencesSetMapZoom( + context: Context, fencesBean: FencesBean, padding: Int = AppUtils.dpToPx(50) + ) { + restoreFencesSetMapZoom( + context, + fencesBean, + mPreviewFencesBaiduMapFragment, + mPreviewFencesGoogleMapFragment, + padding + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/common/map/WifiZone2MapCommon.kt b/app/src/main/java/com/abbidot/tracker/ui/common/map/WifiZone2MapCommon.kt new file mode 100644 index 0000000..dfd111a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/common/map/WifiZone2MapCommon.kt @@ -0,0 +1,42 @@ +package com.abbidot.tracker.ui.common.map + +import android.content.Context +import androidx.fragment.app.Fragment +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.base.BaseMapCommon +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.deprecated.ui.fragment.map.baidumap.NavigationBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.WiFiZone2GoogleMapFragment + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description: + */ +class WifiZone2MapCommon : BaseMapCommon() { + + private var mWiFiZone2GoogleMapFragment: WiFiZone2GoogleMapFragment? = null + private var mBaiduMapFragment: NavigationBaiduMapFragment? = null + + fun getMapFragment(context: Context): Fragment { + return if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + mBaiduMapFragment = NavigationBaiduMapFragment.newInstance(context) + mBaiduMapFragment!! + } else { + mWiFiZone2GoogleMapFragment = WiFiZone2GoogleMapFragment.newInstance(context) + mWiFiZone2GoogleMapFragment!! + } + } + + fun refreshLocation() { + mWiFiZone2GoogleMapFragment?.getLastLocation() + } + + fun getDecWiFiAddressData(): HistoryDataBean? { + if (null != mWiFiZone2GoogleMapFragment) { + if (null !== mWiFiZone2GoogleMapFragment!!.mDecWifiAddressData) return mWiFiZone2GoogleMapFragment!!.mDecWifiAddressData + return null + } + return null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/account/AccountV2Fragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/AccountV2Fragment.kt new file mode 100644 index 0000000..196574e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/AccountV2Fragment.kt @@ -0,0 +1,290 @@ +package com.abbidot.tracker.ui.fragment.account + +import android.content.Context +import android.content.Intent +import android.view.View +import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HomeAccountMenuAdapter +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.FragmentAccountV2Binding +import com.abbidot.tracker.deprecated.ui.activity.vm.NotificationsViewModel +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.account.NotificationV2Activity +import com.abbidot.tracker.ui.activity.account.UserProfileActivity +import com.abbidot.tracker.ui.activity.device.MyTrackerV2Activity +import com.abbidot.tracker.ui.activity.subscribe.MySubscriptionActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + + +/** + * 首页Account + * create an instance of this fragment. + */ +class AccountV2Fragment : + BaseFragment(FragmentAccountV2Binding::inflate) { + + private lateinit var mUserViewModel: UserProfileViewModel + private lateinit var mNotificationsViewModel: NotificationsViewModel + + private lateinit var mMenuAdapter: HomeAccountMenuAdapter + private lateinit var mMenuList: MutableList + private lateinit var mHomeV2Activity: HomeV2Activity + private var mMeasurementUnit = 1 + + companion object { + @JvmStatic + fun newInstance(context: Context) = AccountV2Fragment().apply { + mContext = context + } + } + + override fun initData() { + mHomeV2Activity = activity as HomeV2Activity + mViewBinding.apply { + mHomeV2Activity.edgeToEdgeAdapterBars( + llHomeAccountLayout, WindowInsetsCompat.Type.statusBars() + ) + llHomeAccountTopUser.let { + it.ivTopPetBtnSmall.setImageResource(R.drawable.icon_noti_svg) + it.homeDataPetNameSmall.text = + MMKVUtil.getString(MMKVKey.UserName, getString(R.string.app_name)) + val imageUrl = MMKVUtil.getString(MMKVKey.HeadUrl) + ViewUtil.instance.imageLoadUrl( + it.homeDataPetHeadSmall.appHeadImage, imageUrl, R.drawable.pic_avatar_df + ) + it.homeDataPetNameSmall.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, R.drawable.icon_gray_right_arrow, 0 + ) + } + + addListData() + mMenuAdapter = HomeAccountMenuAdapter(this@AccountV2Fragment, mMenuList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvHomeAccountMenu, mMenuAdapter, top = QMUIDisplayHelper.dpToPx(6) + ) + setOnClickListenerViews( + llHomeAccountMyTracker, + llHomeAccountSubscription, + llHomeAccountTopUser.ivTopPetBtnSmall, + llHomeAccountTopUser.homeDataPetHeadSmall.root, + llHomeAccountTopUser.homeDataPetNameSmall + ) + } + + //切换到账户ActivityV2Fragment 页面,会导致ViewModel生命周期销毁,需要重新创建 + mUserViewModel = ViewModelProvider(this)[UserProfileViewModel::class.java] + mNotificationsViewModel = ViewModelProvider(this)[NotificationsViewModel::class.java] + + mUserViewModel.getUserInfo(mHomeV2Activity) + } + + override fun onResume() { + super.onResume() + mNotificationsViewModel.getMessageList(mHomeV2Activity) + } + + private fun addListData() { + mMenuList = mutableListOf() + ViewUtil.instance.addMenuBean(mMenuList, getString(R.string.txt_help)) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_tracker_guide), + imageResId = R.drawable.icon_cellular_network_svg, + menuType = MultipleEntity.IMG_TEXT + ) +// ViewUtil.instance.addMenuBean( +// mMenuList, +// getString(R.string.txt_features_guide), +// imageResId = R.drawable.icon_cells_svg, +// menuType = MultipleEntity.IMG_TEXT +// ) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_feedback), + imageResId = R.drawable.icon_help_square_svg, + menuType = MultipleEntity.IMG_TEXT + ) + ViewUtil.instance.addMenuBean(mMenuList, getString(R.string.txt_setting)) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_notifications), + imageResId = R.drawable.icon_notification_svg, + menuType = MultipleEntity.IMG_TEXT + ) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_measurement_units), + imageResId = R.drawable.icon_measurement_units_svg, + menuType = MultipleEntity.IMG_TEXT, + value = Util.getMeasureUnitName(mContext!!) + ) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_permissions), + imageResId = R.drawable.icon_permissions_svg, + menuType = MultipleEntity.IMG_TEXT + ) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_advanced_set), + imageResId = R.drawable.icon_advanced_set_svg, + menuType = MultipleEntity.IMG_TEXT + ) + ViewUtil.instance.addMenuBean(mMenuList, getString(R.string.txt_more)) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_setting_about), + imageResId = R.drawable.icon_about_svg, + menuType = MultipleEntity.IMG_TEXT + ) +// ViewUtil.instance.addMenuBean( +// mMenuList, +// getString(R.string.txt_shop), +// imageResId = R.drawable.icon_store_svg, +// menuType = MultipleEntity.IMG_TEXT +// ) +// ViewUtil.instance.addMenuBean( +// mMenuList, +// getString(R.string.txt_setting_clear), +// imageResId = R.drawable.icon_database_restore_svg, +// menuType = MultipleEntity.IMG_TEXT, +// value = "0.00\tKb" +// ) + ViewUtil.instance.addMenuBean( + mMenuList, + getString(R.string.txt_sign_out), + imageResId = R.drawable.icon_logout_svg, + menuType = MultipleEntity.IMG_TEXT + ) + } + + override fun liveDataObserve() { + XEventBus.observe(viewLifecycleOwner, EventName.UpdateUserInfo) { + mUserViewModel.getUserInfo(mHomeV2Activity) + } + XEventBus.observe(viewLifecycleOwner, EventName.RefreshMessage) { + getMessageCount() + } + mUserViewModel.apply { + mUserInfoLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { info -> + MMKVUtil.putString(MMKVKey.Email, info.email) + MMKVUtil.putString(MMKVKey.UserName, info.userName) + MMKVUtil.putString(MMKVKey.HeadUrl, info.imgurl) + MMKVUtil.putString(MMKVKey.CountryCode, info.countryCode) + mViewBinding.llHomeAccountTopUser.let { view -> + view.homeDataPetNameSmall.text = info.userName + ViewUtil.instance.imageLoadUrl( + view.homeDataPetHeadSmall.appHeadImage, + info.imgurl, + R.drawable.pic_avatar_df + ) + } + } + } + }) + } + + mUpdateMeasureUnitLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + MMKVUtil.putInt(MMKVKey.MeasureUnit, mMeasurementUnit) + mMenuAdapter.notifyItemChanged(5) + } + } + }) + } + } + mNotificationsViewModel.mNotificationsLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + lifecycleScope.launch(Dispatchers.IO) { + Util.dealMessage(this@apply) + getMessageCount() + } + } + } + + override fun onRequestError(exceptionCode: String?) { + getMessageCount() + } + }, isRequestErrorTip = false, isShowNoDataTip = false) + } + } + + private fun getMessageCount() { + requireActivity().lifecycleScope.launch(Dispatchers.IO) { + MyDatabase.messageDao().findAll(MyDatabase.MessageTableName)?.apply { + var noReadNum = size + LogUtil.e("消息列表个数:${size}") + for (message in this) { + if (message.isRead) noReadNum-- + } + mHomeV2Activity.runOnUiThread { + mViewBinding.homeAccountMessageCountText.let { + if (noReadNum > 0) it.visibility = View.VISIBLE + else it.visibility = View.GONE + it.text = "$noReadNum" + } + + } + } + } + } + + fun setMeasureUnit(measurementUnit: Int) { + mMeasurementUnit = measurementUnit + mUserViewModel.updateMeasureUnit(mHomeV2Activity, measurementUnit) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + llHomeAccountTopUser.homeDataPetHeadSmall.root, llHomeAccountTopUser.homeDataPetNameSmall -> startActivity( + Intent( + mContext, UserProfileActivity::class.java + ) + ) + + llHomeAccountMyTracker -> startActivity( + Intent( + mContext, MyTrackerV2Activity::class.java + ) + ) + + llHomeAccountSubscription -> startActivity( + Intent( + mContext, MySubscriptionActivity::class.java + ) + ) + + llHomeAccountTopUser.ivTopPetBtnSmall -> startActivity( + Intent( + mContext, NotificationV2Activity::class.java + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/account/CreateAccountV2OneFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/CreateAccountV2OneFragment.kt new file mode 100644 index 0000000..47c5660 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/CreateAccountV2OneFragment.kt @@ -0,0 +1,184 @@ +package com.abbidot.tracker.ui.fragment.account + +import android.content.Context +import android.content.Intent +import android.text.InputType +import android.text.SpannableString +import android.text.SpannableStringBuilder +import android.text.Spanned +import android.text.TextUtils +import android.text.method.LinkMovementMethod +import android.text.style.ClickableSpan +import android.view.Gravity +import android.view.View +import android.widget.TextView +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.network.exception.ErrorCode +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentCreateAccountV2OneBinding +import com.abbidot.tracker.ui.activity.account.CreateAccountV2Activity +import com.abbidot.tracker.ui.activity.account.LoginV2Activity +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + +/** + *Created by .yzq on 2024/8/2/002. + * @link + * @description: + */ +class CreateAccountV2OneFragment : BaseFragment( + FragmentCreateAccountV2OneBinding::inflate +) { + private val mRegisterViewModel: UserProfileViewModel by viewModels() + private lateinit var mCreateAccountV2Activity: CreateAccountV2Activity + + companion object { + @JvmStatic + fun newInstance(context: Context) = CreateAccountV2OneFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + mCreateAccountV2Activity = activity as CreateAccountV2Activity + mViewBinding.apply { + ilCreateAccountV2Email.let { + it.tvInputNameTitle.setText(R.string.txt_email_address) + it.etInputContent.setHint(R.string.login_txt_email) + it.etInputContent.setText(mCreateAccountV2Activity.mUserEmail) + it.etInputContent.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + ViewUtil.instance.setInputViewErrorHideClick( + mContext!!, + it.rlEtInputLayout, + tvEmailErrorTipCreateAccountV2, + it.etInputContent + ) { s -> + s.let { str -> + val hasUpperCase = str.any { c -> + c.isUpperCase() + } + if (hasUpperCase) { + it.etInputContent.setText(str.lowercase()) + it.etInputContent.setSelection(str.length) + } + } + } + } + + val privacyAndTerms = getString(R.string.txt_read_and_accept) + setSpannableClick( + ilCreateAccountV2PolicyTermsCheck.tvLoginAgreePrivacyContent, + privacyAndTerms.substringBeforeLast("Te"), + privacyAndTerms.substringAfterLast("&") + ) + + setOnClickListenerViews(btnCreateAccountV2Continue) + } + } + + private fun setSpannableClick(textView: TextView, str1: String, str2: String) { + try { + textView.apply { + val span1 = SpannableString(str1) + span1.setSpan(object : ClickableSpan() { + override fun onClick(v: View) { + ViewUtil.instance.goPrivacyPolicy(mCreateAccountV2Activity) + } + }, 7, 21, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + val span2 = SpannableString(str2) + span2.setSpan(object : ClickableSpan() { + override fun onClick(v: View) { + ViewUtil.instance.goServiceContract(mCreateAccountV2Activity) + } + }, 1, 6, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + val spannableStringBuilder = SpannableStringBuilder() + spannableStringBuilder.append(span1) + spannableStringBuilder.append(span2) + text = spannableStringBuilder + movementMethod = LinkMovementMethod.getInstance() + } + } catch (e: Exception) { + LogUtil.e("解析同意隐私政策错误${e.message}") + } + } + + override fun liveDataObserve() { + mRegisterViewModel.mGetEmailCodeLiveData.observe(viewLifecycleOwner) { + mViewBinding.btnCreateAccountV2Continue.isEnabled = true + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mCreateAccountV2Activity.switchFragment(1) + } + } + + override fun onRequestError(exceptionCode: String?) { + exceptionCode?.let { code -> + if (code == ErrorCode.USER_HAS_EXIST.toString()) { + ViewUtil.instance.showDialog( + mContext!!, + R.string.txt_account_exists_log_in, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + val intent = Intent(mContext, LoginV2Activity::class.java) + val email = + mViewBinding.ilCreateAccountV2Email.etInputContent.text.toString() + intent.putExtra(MMKVKey.Email, email) + startActivityFinish(mActivity!!, intent) + } + }, + okTextResId = R.string.txt_sure + ) + } + } + } + }) + mCreateAccountV2Activity.isInterceptNetErrorCodeTip = false + } + } + + private fun continueClick() { + mViewBinding.apply { + val email = ilCreateAccountV2Email.etInputContent.text.toString() + if (TextUtils.isEmpty(email) || !Utils.isValidEmail(email)) { + ViewUtil.instance.setInputViewErrorShow( + mContext!!, + ilCreateAccountV2Email.rlEtInputLayout, + tvEmailErrorTipCreateAccountV2 + ) + return + } + + if (!ilCreateAccountV2PolicyTermsCheck.cbLoginAgreePrivacyCheckbox.isChecked) { + showToast(R.string.register_txt_agree, gravity = Gravity.CENTER) + return + } + + mCreateAccountV2Activity.setToEmail(email) + + btnCreateAccountV2Continue.isEnabled = false + mCreateAccountV2Activity.isInterceptNetErrorCodeTip = true + mRegisterViewModel.getEmailCode(mCreateAccountV2Activity, email, 1) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnCreateAccountV2Continue -> { + continueClick() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/account/CreateAccountV2ThreeFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/CreateAccountV2ThreeFragment.kt new file mode 100644 index 0000000..3c3f890 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/CreateAccountV2ThreeFragment.kt @@ -0,0 +1,219 @@ +package com.abbidot.tracker.ui.fragment.account + +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentCreateAccountV2ThreeBinding +import com.abbidot.tracker.ui.activity.account.CreateAccountV2Activity +import com.abbidot.tracker.ui.activity.account.SignupSuccessActivity +import com.abbidot.tracker.ui.common.CountryCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + + +/** + * A simple [Fragment] subclass. + * Use the [CreateAccountV2ThreeFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class CreateAccountV2ThreeFragment : BaseFragment( + FragmentCreateAccountV2ThreeBinding::inflate +) { + + private val mUserViewModel: UserProfileViewModel by viewModels() + + private lateinit var mCreateAccountV2Activity: CreateAccountV2Activity + + private lateinit var mCountryCommon: CountryCommon + + private var isShowPassword = false + + companion object { + @JvmStatic + fun newInstance(context: Context) = CreateAccountV2ThreeFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + mCreateAccountV2Activity = activity as CreateAccountV2Activity + mViewBinding.apply { + + ilCreateAccountV2FullName.let { + it.tvInputNameTitle.setText(R.string.register_txt_name) + it.etInputContent.setHint(R.string.acccount_name) + ViewUtil.instance.setInputViewErrorHideClick( + mContext!!, + it.rlEtInputLayout, + tvCreateAccountV2FullNameError, + it.etInputContent + ) + } + + ilCreateAccountV2Country.let { + it.tvInputNameTitle.setText(R.string.txt_country) + ViewUtil.instance.setEditTextDropSelectState(it.etInputContent) + it.ivInputRepresentImage.visibility = View.VISIBLE + ViewUtil.instance.setInputViewErrorHideClick( + mContext!!, + it.rlEtInputLayout, + tvCreateAccountV2SelectCountryError, + it.etInputContent + ) + } + + ilCreateAccountV2Password.let { + it.tvInputNameTitle.setText(R.string.login_txt_password) + it.etInputContent.setHint(R.string.login_txt_password) + ViewUtil.instance.setShowAndHidePassword( + it.etInputContent, it.ivInputRepresentImage + ) + it.etInputContent.addTextChangedListener { str -> + it.rlEtInputLayout.background = ContextCompat.getDrawable( + mContext!!, R.drawable.shape8_gray_border_white_bg + ) + tvCreateAccountV2PasswordError.setTextColor( + ContextCompat.getColor( + mContext!!, R.color.blue_color1 + ) + ) + str?.apply { + if (length >= 6) { + tvCreateAccountV2PasswordError.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.icon_input_check_svg, 0, 0, 0 + ) + } else { + tvCreateAccountV2PasswordError.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, 0, 0 + ) + } + } + } + } + + mCountryCommon = + CountryCommon(mContext!!, object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + ilCreateAccountV2Country.etInputContent.setText(mCountryCommon.getCountry()) + } + }) + + setOnClickListenerViews( + ilCreateAccountV2Country.rlEtInputLayout, + ilCreateAccountV2Country.ivInputRepresentImage, + ilCreateAccountV2Password.ivInputRepresentImage, + btnCreateAccountV2Signup + ) + } + + mUserViewModel.getCountryCodes(mCreateAccountV2Activity) + } + + override fun liveDataObserve() { + mUserViewModel.apply { + mCountryCodeLiveData.observe(mCreateAccountV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mCountryCommon.setCountryCodeData(this) + } + } + }) + } + mRegisterV2LiveData.observe(mCreateAccountV2Activity) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + Util.dealLoginSuccessData(mContext!!, this) + val intent = Intent(mContext, SignupSuccessActivity::class.java) + mCreateAccountV2Activity.startActivityFinish(intent) + } + } + }) + } + } + } + + private fun signupClick() { + mViewBinding.apply { + val name = ilCreateAccountV2FullName.etInputContent.text.toString() +// val country = ilCreateAccountV2Country.etInputContent.text.toString() + val country = "1212" + val email = mCreateAccountV2Activity.mUserEmail + var password = ilCreateAccountV2Password.etInputContent.text.toString() + var isSure = true + if (TextUtils.isEmpty(name)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext!!, + ilCreateAccountV2FullName.rlEtInputLayout, + tvCreateAccountV2FullNameError + ) + } + if (TextUtils.isEmpty(country)) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext!!, + ilCreateAccountV2Country.rlEtInputLayout, + tvCreateAccountV2SelectCountryError + ) + } +// if (TextUtils.isEmpty(email) || !Utils.isValidEmail(email)) { +// isSure = false +// ViewUtil.instance.setInputViewErrorShow( +// mContext!!, ilCreateAccountEmailAddress.rlEtInputLayout, tvEmailErrorTip +// ) +// } + if (TextUtils.isEmpty(password) || password.length < 6) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext!!, + ilCreateAccountV2Password.rlEtInputLayout, + tvCreateAccountV2PasswordError + ) + } + if (!isSure) return + +// if (!ilPolicyTermsCheck.cbLoginAgreePrivacyCheckbox.isChecked) { +// showToast(R.string.register_txt_agree) +// return +// } + + password = Utils.get32Md5Value(password) + mUserViewModel.registerV2( + mCreateAccountV2Activity, email, password, name, mCountryCommon.getCountryCode() + ) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilCreateAccountV2Country.rlEtInputLayout, ilCreateAccountV2Country.ivInputRepresentImage -> mCountryCommon.showCountryCodeDialog() + + ilCreateAccountV2Password.ivInputRepresentImage -> { + isShowPassword = !isShowPassword + ViewUtil.instance.showAndHidePasswordClick( + ilCreateAccountV2Password.etInputContent, + ilCreateAccountV2Password.ivInputRepresentImage, + isShowPassword + ) + } + + btnCreateAccountV2Signup -> signupClick() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/account/EmailValidFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/EmailValidFragment.kt new file mode 100644 index 0000000..54fa429 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/EmailValidFragment.kt @@ -0,0 +1,207 @@ +package com.abbidot.tracker.ui.fragment.account + +import android.content.Context +import android.graphics.Paint +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import android.view.View.OnClickListener +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentBaseEmailValidBinding +import com.abbidot.tracker.vm.CountDownTimerViewModel +import com.abbidot.tracker.vm.UserProfileViewModel +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo + + +/** + * A simple [Fragment] subclass. + * Use the [EmailValidFragment.newInstance] factory method to + * create an instance of this fragment. + */ +open class EmailValidFragment : BaseFragment( + FragmentBaseEmailValidBinding::inflate +) { + + private val mCountDownTimerViewModel: CountDownTimerViewModel by viewModels() + private val mRegisterViewModel: UserProfileViewModel by viewModels() + + private val mResendCodeTime = 5 * 60 + 1 + private var mEmail = "" + private var mBtnString = "" + private var mEmailCodeType = 0 + + private lateinit var mOnOkClickListener: OnClickListener + + companion object { + @JvmStatic + fun newInstance( + context: Context, + emailCodeType: Int, + btnString: String = "", + okClickListener: OnClickListener + ) = EmailValidFragment().apply { + mContext = context + mBtnString = btnString + mEmailCodeType = emailCodeType + mOnOkClickListener = okClickListener + } + } + + fun setEmail(email: String) { + mEmail = email + //返回上一个页面修改邮箱,再次回到需要更新邮箱显示 + if (isResumed) formatEmail() + } + + override fun initData() { + super.initData() + + formatEmail() + + mViewBinding.apply { + + if (!TextUtils.isEmpty(mBtnString)) { + btnForgotPasswordV2TwoContinue.text = mBtnString + } + + //添加下划线 + tvForgotPasswordResendCodeBtn.paintFlags = Paint.UNDERLINE_TEXT_FLAG + + vetForgotPasswordV2EditText.setEditClickListener { + vetForgotPasswordV2EditText.setBoxBackground( + ContextCompat.getDrawable( + mContext!!, R.drawable.shape8_gray_border_white_bg + ) + ) + } + setOnClickListenerViews(btnForgotPasswordV2TwoContinue, tvForgotPasswordResendCodeBtn) + } + + mCountDownTimerViewModel.startCountDown(mResendCodeTime.toLong()) + } + + private fun formatEmail() { + val emailString = String.format( + getString(R.string.txt_otp_verification_tips), mEmail + ) + //找出邮箱并加上下划线 +// val index = 38 +// val spannableString = SpannableString(emailString) +// spannableString.setSpan( +// UnderlineSpan(), index, index + mEmail.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE +// ) + mViewBinding.tvForgotPasswordV2EmailDec.text = emailString + } + + override fun liveDataObserve() { + mCountDownTimerViewModel.apply { + mCountDowningLiveData.observe(viewLifecycleOwner) { +// val t = it / 60f +// val str = if (t > 0 && t < 1) 1 +// else if (t >= 1) t.toInt() +// else 0 + mViewBinding.tvForgotPasswordV2TwoValidMin.let { v -> + v.text = String.format(getString(R.string.txt_code_valid_min), "$it") + v.visibility = if (it == 0L) View.INVISIBLE else View.VISIBLE + } + } + + mCountDownEndLiveData.observe(viewLifecycleOwner) { + mViewBinding.tvForgotPasswordV2CountdownTime.visibility = View.GONE + mViewBinding.tvForgotPasswordV2TimeRemaining.visibility = View.GONE + mViewBinding.tvForgotPasswordV2TimeSec.visibility = View.GONE + setResendCodeBtnEnabled(true) + } + } + + mRegisterViewModel.apply { + mGetEmailCodeLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + showToast(R.string.txt_code_success, gravity = Gravity.CENTER) +// mViewBinding.tvForgotPasswordV2CountdownTime.visibility = View.VISIBLE +// mViewBinding.tvForgotPasswordV2TimeRemaining.visibility = View.VISIBLE +// mViewBinding.tvForgotPasswordV2TimeSec.visibility = View.VISIBLE + mCountDownTimerViewModel.startCountDown(mResendCodeTime.toLong()) + } + } + + override fun onRequestError(exceptionCode: String?) { + setResendCodeBtnEnabled(true) + } + }) + } + + mValidateCodeLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mOnOkClickListener.onClick(view) + } + } + }) + } + } + } + + private fun setResendCodeBtnEnabled(enabled: Boolean) { + mViewBinding.tvForgotPasswordResendCodeBtn.apply { + isEnabled = enabled + if (enabled) { + setTextColor( + ContextCompat.getColor( + mContext!!, R.color.black + ) + ) + } else { + setTextColor( + ContextCompat.getColor( + mContext!!, R.color.gray_color8 + ) + ) + } + } + } + + private fun continueVerifyCode() { + mViewBinding.vetForgotPasswordV2EditText.apply { + if (TextUtils.isEmpty(content) || content.length < 4) { + YoYo.with(Techniques.Shake).onStart { + setBoxBackground( + ContextCompat.getDrawable( + mContext!!, R.drawable.shape8_red_border_white_bg + ) + ) + }.duration(50).playOn(this) + return + } + + mRegisterViewModel.validateCode( + this@EmailValidFragment, mEmail, content, mEmailCodeType + ) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnForgotPasswordV2TwoContinue -> continueVerifyCode() + + tvForgotPasswordResendCodeBtn -> { + setResendCodeBtnEnabled(false) + mRegisterViewModel.getEmailCode( + activity as BaseActivity<*>, mEmail, mEmailCodeType + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/account/ForgotPasswordV2OneFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/ForgotPasswordV2OneFragment.kt new file mode 100644 index 0000000..7addbb0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/ForgotPasswordV2OneFragment.kt @@ -0,0 +1,110 @@ +package com.abbidot.tracker.ui.fragment.account + +import android.content.Context +import android.text.InputType +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentForgotPasswordV2OneBinding +import com.abbidot.tracker.ui.activity.account.AddEmailValidActivity +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.UserProfileViewModel + + +/** + * A simple [Fragment] subclass. + * Use the [ForgotPasswordV2OneFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class ForgotPasswordV2OneFragment( +) : BaseFragment( + FragmentForgotPasswordV2OneBinding::inflate +) { + + private val mRegisterViewModel: UserProfileViewModel by viewModels() + + //当前Fragment所在的下标 + private var mCurrentFragmentPotion = 0 + + private lateinit var mForgotPasswordV2Activity: AddEmailValidActivity + + private var mEmailCodeType = 0 + private var mTitle = "" + + companion object { + @JvmStatic + fun newInstance( + context: Context, title: String, emailCodeType: Int, cFragmentPotion: Int = 0 + ) = ForgotPasswordV2OneFragment().apply { + mContext = context + mCurrentFragmentPotion = cFragmentPotion + mEmailCodeType = emailCodeType + mTitle = title + } + } + + override fun initData() { + super.initData() + mForgotPasswordV2Activity = activity as AddEmailValidActivity + mViewBinding.apply { + tvChangePwdTitle.text = mTitle + ilChangePwdEmail.let { + it.tvInputNameTitle.setText(R.string.txt_email_address) + it.etInputContent.setHint(R.string.login_txt_email) + it.etInputContent.setText(mForgotPasswordV2Activity.mUserEmail) + it.etInputContent.inputType = InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS + ViewUtil.instance.setInputViewErrorHideClick( + mContext!!, it.rlEtInputLayout, tvEmailErrorTipChangePwd, it.etInputContent + ) + } + + setOnClickListenerViews(btnForgotPasswordV2OneContinue) + } + } + + override fun liveDataObserve() { + mRegisterViewModel.mGetEmailCodeLiveData.observe(viewLifecycleOwner) { + mViewBinding.btnForgotPasswordV2OneContinue.isEnabled = true + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + //跳转下一个页面 + mForgotPasswordV2Activity.switchFragment(mCurrentFragmentPotion + 1) + } + } + }) + } + } + + private fun continueClick() { + mViewBinding.apply { + val email = ilChangePwdEmail.etInputContent.text.toString() + if (TextUtils.isEmpty(email) || !Utils.isValidEmail(email)) { + ViewUtil.instance.setInputViewErrorShow( + mContext!!, ilChangePwdEmail.rlEtInputLayout, tvEmailErrorTipChangePwd + ) + return + } + + mForgotPasswordV2Activity.setToEmail(email) + + btnForgotPasswordV2OneContinue.isEnabled = false + mRegisterViewModel.getEmailCode(mForgotPasswordV2Activity, email, mEmailCodeType) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnForgotPasswordV2OneContinue -> { + continueClick() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/account/ForgotPasswordV2ThereFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/ForgotPasswordV2ThereFragment.kt new file mode 100644 index 0000000..7b66dad --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/account/ForgotPasswordV2ThereFragment.kt @@ -0,0 +1,195 @@ +package com.abbidot.tracker.ui.fragment.account + +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentForgotPasswordV2ThereBinding +import com.abbidot.tracker.deprecated.ui.activity.vm.ChangePasswordViewModel +import com.abbidot.tracker.ui.activity.account.AddEmailValidActivity +import com.abbidot.tracker.ui.activity.account.ResetPasswordSuccessActivity +import com.abbidot.tracker.util.ViewUtil + + +/** + * A simple [Fragment] subclass. + * Use the [ForgotPasswordV2ThereFragment.newInstance] factory method to + * create an instance of this fragment. + */ +class ForgotPasswordV2ThereFragment : + BaseFragment(FragmentForgotPasswordV2ThereBinding::inflate) { + + private val mChangePasswordViewModel: ChangePasswordViewModel by viewModels() + + private var isShowPassword = false + private var isShowConfirmPassword = false + private lateinit var mForgotPasswordV2Activity: AddEmailValidActivity + + companion object { + @JvmStatic + fun newInstance(context: Context) = ForgotPasswordV2ThereFragment().apply { + mContext = context + } + } + + override fun initData() { + super.initData() + mForgotPasswordV2Activity = activity as AddEmailValidActivity + mViewBinding.apply { + + ilResetPasswordPwd.let { + it.tvInputNameTitle.setText(R.string.login_txt_password) + it.etInputContent.setHint(R.string.login_txt_password) + ViewUtil.instance.setShowAndHidePassword( + it.etInputContent, it.ivInputRepresentImage + ) + it.etInputContent.addTextChangedListener { str -> + val surePassword = ilResetPasswordPwdSure.etInputContent.text.toString() + it.rlEtInputLayout.background = ContextCompat.getDrawable( + mContext!!, R.drawable.shape8_gray_border_white_bg + ) + if (!TextUtils.isEmpty(surePassword)) { + tvResetPasswordPwdErrorTip.setTextColor( + ContextCompat.getColor( + mContext!!, R.color.blue_color1 + ) + ) + } + str?.apply { + if (length < 6 || surePassword.length < 6) { + tvResetPasswordPwdErrorTip.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, 0, 0 + ) + } else { + tvResetPasswordPwdErrorTip.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.icon_input_check_svg, 0, 0, 0 + ) + } + } + } + } + + ilResetPasswordPwdSure.let { + it.tvInputNameTitle.setText(R.string.txt_confirm_password) + it.etInputContent.setHint(R.string.login_txt_password) + ViewUtil.instance.setShowAndHidePassword( + it.etInputContent, it.ivInputRepresentImage + ) + it.etInputContent.addTextChangedListener { str -> + val password = ilResetPasswordPwd.etInputContent.text.toString() + it.rlEtInputLayout.background = ContextCompat.getDrawable( + mContext!!, R.drawable.shape8_gray_border_white_bg + ) + if (!TextUtils.isEmpty(password)) { + tvResetPasswordPwdErrorTip.setTextColor( + ContextCompat.getColor( + mContext!!, R.color.blue_color1 + ) + ) + } + str?.apply { + if (length < 6 || password.length < 6) { + tvResetPasswordPwdErrorTip.setCompoundDrawablesWithIntrinsicBounds( + 0, 0, 0, 0 + ) + } else { + tvResetPasswordPwdErrorTip.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.icon_input_check_svg, 0, 0, 0 + ) + } + } + } + } + + setOnClickListenerViews( + btnResetNowPwd, + ilResetPasswordPwd.ivInputRepresentImage, + ilResetPasswordPwdSure.ivInputRepresentImage + ) + } + } + + override fun liveDataObserve() { + mChangePasswordViewModel.mResetPasswordLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mForgotPasswordV2Activity.startActivityFinish( + Intent( + mContext, ResetPasswordSuccessActivity::class.java + ) + ) + } + } + }) + } + } + + private fun resetPwd() { + mViewBinding.apply { + val password = ilResetPasswordPwd.etInputContent.text.toString() + val surePassword = ilResetPasswordPwdSure.etInputContent.text.toString() + var isSure = true + + if (TextUtils.isEmpty(password) || password.length < 6) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext!!, ilResetPasswordPwd.rlEtInputLayout, tvResetPasswordPwdErrorTip + ) + } + if (TextUtils.isEmpty(surePassword) || surePassword.length < 6) { + isSure = false + ViewUtil.instance.setInputViewErrorShow( + mContext!!, ilResetPasswordPwdSure.rlEtInputLayout, tvResetPasswordPwdErrorTip + ) + } + + if (!isSure) return + + if (password != surePassword) { + showToast(R.string.txt_password_no_same, gravity = Gravity.CENTER) + return + } + + val pwd = Utils.get32Md5Value(password) + mChangePasswordViewModel.resetPassword( + mForgotPasswordV2Activity, mForgotPasswordV2Activity.mUserEmail, pwd + ) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnResetNowPwd -> resetPwd() + + ilResetPasswordPwd.ivInputRepresentImage -> { + isShowPassword = !isShowPassword + ViewUtil.instance.showAndHidePasswordClick( + ilResetPasswordPwd.etInputContent, + ilResetPasswordPwd.ivInputRepresentImage, + isShowPassword + ) + } + + ilResetPasswordPwdSure.ivInputRepresentImage -> { + isShowConfirmPassword = !isShowConfirmPassword + ViewUtil.instance.showAndHidePasswordClick( + ilResetPasswordPwdSure.etInputContent, + ilResetPasswordPwdSure.ivInputRepresentImage, + isShowConfirmPassword + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/data/ActivityV2Fragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/data/ActivityV2Fragment.kt new file mode 100644 index 0000000..854b1dc --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/data/ActivityV2Fragment.kt @@ -0,0 +1,309 @@ +package com.abbidot.tracker.ui.fragment.data + +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import android.view.View +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.view.WindowInsetsCompat +import androidx.lifecycle.ViewModelProvider +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.HomeDataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentActivityV2Binding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.data.MoreActivityActivity +import com.abbidot.tracker.ui.activity.data.MoreSleepActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.DataViewModel +import com.qmuiteam.qmui.widget.pullRefreshLayout.QMUIPullRefreshLayout + + +/** + * 首页Activity + * create an instance of this fragment. + */ +class ActivityV2Fragment : + BaseFragment(FragmentActivityV2Binding::inflate) { + + private lateinit var mDataViewModel: DataViewModel + + private lateinit var mHomeV2Activity: HomeV2Activity + + //当前这个页面显示的宠物下标 + private var mCurrentShowPetPos = -1 + + private var mToday = "2024-06-28" + + companion object { + @JvmStatic + fun newInstance(context: Context) = ActivityV2Fragment().apply { + mContext = context + } + } + + override fun initData() { + mHomeV2Activity = activity as HomeV2Activity + + mToday = Utils.getCurrentTime(Utils.DATE_FORMAT_PATTERN_CN) + mViewBinding.apply { + + mHomeV2Activity.edgeToEdgeAdapterBars( + svHomeActivityScroll, WindowInsetsCompat.Type.statusBars() + ) + + ilSportActiveLayout.let { + it.root.background = + ContextCompat.getDrawable(mContext!!, R.drawable.shape8_gray_yellow_bg) + it.ivSportTimeType.setImageResource(R.drawable.icon_active_time) + it.tvSportTimeTitle.text = getText(R.string.data_active_other_time) + } + + //下拉刷新 + homeActivityV2PullRefresh.setOnPullListener(object : + QMUIPullRefreshLayout.OnPullListener { + override fun onMoveTarget(offset: Int) { + } + + override fun onMoveRefreshView(offset: Int) { + } + + override fun onRefresh() { + mHomeV2Activity.getPet()?.apply { + mDataViewModel.getHomeDeviceData(deviceId, mToday, petId) + } + } + }) + + setOnClickListenerViews( + ilHomeActivityTopPet.ivTopPetBtnSmall, + btnHomeMoreActivity, + btnHomeMoreSleep, + ilHomeActivityTopPet.homeDataPetNameSmall, + ilHomeActivityTopPet.homeDataPetHeadSmall.root + ) + + if (AppUtils.isDebug()) { + ilHomeActivityTopPet.homeDataPetHeadSmall.root.setOnLongClickListener { + mHomeV2Activity.goToDebugActivity() + true + } + } + +// val activityList = mutableListOf(28, 54, 69, 110, 88, 39, 78, 12, 68, 98, 46, 93) +// homeActiveTimeBarView.setData(activityList) +// homeActiveSleepView.setData(3f, 5f) + } + + //切换到账户AccountV2Fragment 页面,会导致ViewModel生命周期销毁,需要重新创建 + mDataViewModel = ViewModelProvider(this)[DataViewModel::class.java] + + initHomeData() + } + + + private fun initHomeData() { + val goal = MMKVUtil.getInt(MMKVKey.ActivityGoal) + mViewBinding.apply { + homeActiveTimeProgress.progress = 0 + tvHomeActivityV2GoalPercent.text = + String.format(getString(R.string.txt_goal_percent), "0") + tvHomeActivityV2Goal.text = + String.format(getString(R.string.txt_min_unit_lower), "0/$goal") + ilSportDistanceLayout.let { + it.tvSportDataContent.text = "0" + it.tvSportDataUnit.text = Util.getMetricUnits(mContext!!, ConstantInt.Type4) + } + ilSportCaloriesLayout.let { + it.ivSportDataType.setImageResource(R.drawable.icon_calories) + it.tvSportDataTitle.text = getString(R.string.data_active_calories) + it.tvSportDataContent.text = "0" + it.tvSportDataUnit.text = getString(R.string.txt_calorie_unit) + } + ilSportActiveLayout.tvSportTimeContent.text = "00:00:00" + ilSportSleepLayout.tvSportTimeContent.text = "00:00:00" + homeActiveTimeBarView.setData(mutableListOf()) + homeActiveSleepView.setData(0f, 0f) + } + } + + override fun onResume() { + super.onResume() + + if (mCurrentShowPetPos == mHomeV2Activity.mSelectPetPosition) { + mHomeV2Activity.getPet()?.apply { + mDataViewModel.getHomeDeviceData(deviceId, mToday, petId) + } + } else { + //其他页面是否选择了宠物 + showPetNameAndHead(mHomeV2Activity.mSelectPetPosition) + } + } + + override fun liveDataObserve() { + mDataViewModel.mHomeDataLiveData.observe(viewLifecycleOwner) { + initHomeData() + mViewBinding.let { v -> + if (v.homeActivityV2PullRefresh.isRefreshing) { + v.homeActivityV2PullRefresh.finishRefresh() + } + } + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + setHomeData(this) + } + } + }) + } + } + + private fun setHomeData(homeDataBean: HomeDataBean) { + mViewBinding.apply { + var goal = ConstantInt.DefaultSportGoal + var activeTime = 0 + + val updateTime = if (homeDataBean.updateTime == 0L) { + Utils.formatTime(System.currentTimeMillis(), Utils.DATE_FORMAT_PATTERN_EN6) + } else { + Utils.formatTime(homeDataBean.updateTime * 1000, Utils.DATE_FORMAT_PATTERN_EN6) + } + tvHomeActivityV2UpdateTime.text = + String.format(getString(R.string.txt_update_time), updateTime) + + homeDataBean.activeTime?.let { + ViewUtil.instance.viewNumberValueAnimator( + ilSportDistanceLayout.tvSportDataContent, + 0, + Util.metricConvertUnits(it.distance.toFloat(), ConstantInt.Type4, 2) + ) + ViewUtil.instance.viewNumberValueAnimator( + ilSportCaloriesLayout.tvSportDataContent, 0, it.calories + ) + if (it.goalTime >= ConstantInt.MinSportGoal) goal = it.goalTime + + activeTime = it.activeTime + timeConversionValueAnimator(ilSportActiveLayout.tvSportTimeContent, activeTime) + val activeMin = activeTime / 60 + tvHomeActivityV2Goal.text = + String.format(getString(R.string.txt_min_unit_lower), "$activeMin/$goal") + var goalPercent = activeMin / goal.toFloat() + if (goalPercent > 1) { + goalPercent = 1f + } + goalPercent *= 100 + ViewUtil.instance.viewNumberValueAnimator( + tvHomeActivityV2GoalPercent, + 0, + goalPercent.toInt(), + getString(R.string.txt_goal_percent) + ) + ViewUtil.instance.viewNumberValueAnimator( + homeActiveTimeProgress, 0, goalPercent.toInt() + ) + + if (!TextUtils.isEmpty(it.activeValue)) { + val activeTimeList = mutableListOf() + val activeList = it.activeValue.split(",") + for (i in activeList.indices step 2) { + val time = activeList[i].toInt() + activeList[i + 1].toInt() + activeTimeList.add(time / 60) + } + homeActiveTimeBarView.setData(activeTimeList) + } + } + + homeDataBean.sleepTime?.let { + timeConversionValueAnimator( + ilSportSleepLayout.tvSportTimeContent, it.sleepTime + ) + homeActiveSleepView.setData(activeTime.toFloat(), it.sleepTime.toFloat()) + } + } + } + + /** + * 时间动画显示 + */ + private fun timeConversionValueAnimator(textView: TextView, time: Int) { + if (time == 0) return + ViewUtil.instance.setValueAnimator(0, time, 1500) { + val value = it.animatedValue as Int + textView.text = Utils.timeConversion(value) + } + } + + override fun onDetach() { + super.onDetach() + mCurrentShowPetPos = -1 + } + + /** + * 设置需要更新的标识 + */ + fun setNeedUpdateTag() { + mCurrentShowPetPos = -1 + } + + /** + * 显示选择的宠物 + */ + fun showPetNameAndHead(position: Int) { + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + if (mHomeV2Activity.mPetList.size == 0) { + return + } + mCurrentShowPetPos = position + + ViewUtil.instance.selectPetDialogShow( + mContext!!, + mHomeV2Activity.mPetList, + position, + mViewBinding.ilHomeActivityTopPet.homeDataPetNameSmall, + mViewBinding.ilHomeActivityTopPet.homeDataPetHeadSmall.appHeadImage + ) + + mHomeV2Activity.getPet()?.apply { + mDataViewModel.getHomeDeviceData(deviceId, mToday, petId) + } + } + + private fun goActivityNeedPet(cls: Class<*>) { + mHomeV2Activity.getPet()?.apply { + Intent(mContext, cls).let { + it.putExtra(ConstantString.Pet, this) + startActivity(it) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilHomeActivityTopPet.ivTopPetBtnSmall -> mHomeV2Activity.getPet()?.apply { + ViewUtil.instance.viewAlphaAndRotationObjectAnimator(v) + mDataViewModel.getHomeDeviceData(deviceId, mToday, petId) + } + + btnHomeMoreActivity -> goActivityNeedPet(MoreActivityActivity::class.java) + + btnHomeMoreSleep -> goActivityNeedPet(MoreSleepActivity::class.java) + + ilHomeActivityTopPet.homeDataPetNameSmall, ilHomeActivityTopPet.homeDataPetHeadSmall.root -> mHomeV2Activity.selectPetDialog() + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/data/RouteV2Fragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/data/RouteV2Fragment.kt new file mode 100644 index 0000000..57add7a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/data/RouteV2Fragment.kt @@ -0,0 +1,455 @@ +package com.abbidot.tracker.ui.fragment.data + +import android.content.Context +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentRouteV2Binding +import com.abbidot.tracker.dialog.ShowCalenderAndTimeDialog +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.common.map.HistoryDataMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.DataDetailViewModel +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.TypefaceTextView +import com.abbidot.tracker.widget.VerticalTopToBottomSeekBar +import kotlinx.coroutines.launch + + +/** + * 首页轨迹 + * create an instance of this fragment. + */ +class RouteV2Fragment : BaseFragment(FragmentRouteV2Binding::inflate) { + + private val mDataDetailViewModel: DataDetailViewModel by viewModels() + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + private lateinit var mHomeV2Activity: HomeV2Activity + + private lateinit var mFragment: Fragment + + private var mFromCalenderDialog: ShowCalenderAndTimeDialog? = null + private var mToCalenderDialog: ShowCalenderAndTimeDialog? = null + + //当前这个页面显示的宠物下标 + private var mCurrentShowPetPos = -1 + + //最多只能选择多少天的历史记录 + private val mOnlySelectDay = 15L + + //当前的时间戳 + private var mCurrentTimestamp = 0L + private var mFromTimestamp = 0L + private var mToTimestamp = 0L + + //方便保存地理反编译信息 + private var mHistoryDataList = mutableListOf() + private val mHistoryDataMapCommon = HistoryDataMapCommon() + + //是否选过自定义日期,没有则再次回到这个页面,刷新取24小时的记录 + private var isSelectCustomDate = false + private var isFirst = true + private var mProgress = -1 + + companion object { + @JvmStatic + fun newInstance(context: Context) = RouteV2Fragment().apply { + mContext = context + } + } + + override fun initData() { + mHomeV2Activity = activity as HomeV2Activity + mViewBinding.apply { + mHomeV2Activity.edgeToEdgeAdapterBars( + root, WindowInsetsCompat.Type.statusBars() + ) + mFragment = mHistoryDataMapCommon.getMapFragment( + mContext!!, miHomeRouteAddressView, mGeoCoderViewModel + ) { + mapLoadOk() + } + + ilHomeRoutePetHead.ivTopPetBtnSmall.visibility = View.GONE + + setOnClickListenerViews( + llHomeRouteCalendarFrom, + llHomeRouteCalendarTo, +// ilHomeRoutePetHead.ivTopPetBtnSmall, + ilHomeRoutePetHead.homeDataPetHeadSmall.root, + ilHomeRoutePetHead.homeDataPetNameSmall, + ivHomeRouteMapTypeBtn + ) + + val type = MMKVUtil.getInt(MMKVKey.LineSelectPosition, ConstantInt.Line) + if (type == ConstantInt.Line) { + mViewBinding.rbHistoryRouteMapLine.isChecked = true + } else { + mViewBinding.rbHistoryRouteMapNumber.isChecked = true + } + + if (AppUtils.isDebug()) { + ilHomeRoutePetHead.homeDataPetHeadSmall.root.setOnLongClickListener { + mHomeV2Activity.goToDebugActivity() + true + } + } + } + + //解决报错java.lang.IllegalArgumentException + //No view found for id 0x7f090254 (com.abbidot.tracker:id/fc_home_route_fragment) for fragment HomeMapGoogleMapFragment + //https://www.jianshu.com/p/9235092f407a + childFragmentManager.commit { + add(R.id.fc_home_route_fragment, mFragment) + } +// (mActivity as FragmentActivity).supportFragmentManager.commit { +// add(R.id.fc_home_route_fragment, mFragment) +// } + + initEven() + initState() + } + + /** + * 一开始获取24小时内的数据 + */ + private fun get24HourTime() { + mCurrentTimestamp = System.currentTimeMillis() + mViewBinding.apply { + mFromTimestamp = Utils.getBeforeHowTimestamp(mCurrentTimestamp, 1) + mToTimestamp = mCurrentTimestamp + tvHomeRouteCalendarTo.text = + Utils.formatTime(mToTimestamp, Utils.DATE_FORMAT_PATTERN_EN1) + tvHomeRouteCalendarFrom.text = + Utils.formatTime(mFromTimestamp, Utils.DATE_FORMAT_PATTERN_EN1) + } + } + + private fun initState() { + mHistoryDataMapCommon.clearAllMarker() + setSeekBarMax(0) + } + + private fun initEven() { + mViewBinding.vsbMapRouteLine.setOnSeekBarChangeProgressListener(object : + VerticalTopToBottomSeekBar.OnSeekBarChangeListener { + override fun onProgressChanged(progress: Int) { + if (mProgress == progress) { + return + } + mProgress = progress + mViewBinding.miHomeRouteAddressView.visibility = View.GONE + mHistoryDataMapCommon.slideChanged(progress) + + val historyDataBean = mHistoryDataList[progress] + val address = historyDataBean.address + if (!TextUtils.isEmpty(address)) { +// mHistoryDataMapCommon.showInfoWindow(historyDataBean) + } + } + + override fun onStopChanged(progress: Int) { +// val historyDataBean = mHistoryDataList[progress] + +// val address = historyDataBean.address + mHistoryDataMapCommon.slideStopChanged(progress) +// if (TextUtils.isEmpty(address)) { +// mHistoryDataMapCommon.slideStopChanged(progress) +// } else { +// mHistoryDataMapCommon.showInfoWindow(historyDataBean) +// } + } + }) + + mViewBinding.rgHistoryRouteMapTrackType.setOnCheckedChangeListener { _, checkedId -> + mViewBinding.vsbMapRouteLine.progress = mViewBinding.vsbMapRouteLine.max + when (checkedId) { + mViewBinding.rbHistoryRouteMapLine.id -> { + MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Line) + mHistoryDataMapCommon.addSetLine() + if (mHistoryDataList.size > 0) { + mHistoryDataMapCommon.slideStopChanged(mHistoryDataList.size - 1) + } else { + showToast(R.string.no_data, gravity = Gravity.CENTER) + } + } + + mViewBinding.rbHistoryRouteMapNumber.id -> { + MMKVUtil.putInt(MMKVKey.LineSelectPosition, ConstantInt.Point) + mHistoryDataMapCommon.addSetNumber() + if (mHistoryDataList.size > 0) { + mHistoryDataMapCommon.slideStopChanged(mHistoryDataList.size - 1) + } else { + showToast(R.string.no_data, gravity = Gravity.CENTER) + } + } + } + } + } + + override fun onResume() { + super.onResume() + if (mCurrentShowPetPos == mHomeV2Activity.mSelectPetPosition) { + if (!isSelectCustomDate) { + if (mHistoryDataMapCommon.isMapLoadOk()) { + get24HourTime() + getHistoryDay(mViewBinding.tvHomeRouteCalendarFrom) + } + } + } else { + //其他页面是否选择了宠物 + if (!isSelectCustomDate) get24HourTime() + showPetNameAndHead(mHomeV2Activity.mSelectPetPosition) + } + } + + override fun liveDataObserve() { + mDataDetailViewModel.mHistoryDetailLive.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull()!! + setLatLngData(data) + } + }, isShowNoDataTip = false) + } + + //地理反编译成功返回 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(this) { + val position = mViewBinding.vsbMapRouteLine.progress + if (mHistoryDataList.size <= position) return@observe + mHistoryDataList[position].apply { + //详细位置 + address = it + +// val timeString = String.format( +// getString(R.string.map_current_update_time), +// DateUtils.getRelativeTimeSpanString(timeStamp * 1000) +// ) + val timeString = Utils.formatTime(timeStamp * 1000, Utils.DATE_FORMAT_PATTERN_EN6) + //更新位置时间 + dayTime = timeString + + var delay = 500L + //第一次延时,等待地图动画执行完成 + if (isFirst) { + isFirst = false + delay = 1500 + } + mViewBinding.root.postDelayed( + { +// mHistoryDataMapCommon.showInfoWindow(this) + mViewBinding.miHomeRouteAddressView.let { mi -> + mi.visibility = View.VISIBLE + mi.setShowText(timeString, it) + } + }, delay + ) + } + } + } + + /** + * 地图加载好了 + */ + private fun mapLoadOk() { + if (mCurrentShowPetPos == -1) return + showPetNameAndHead(mHomeV2Activity.mSelectPetPosition) + } + + /** + * 设置需要更新的标识 + */ + fun setNeedUpdateTag() { + mCurrentShowPetPos = -1 + } + + /** + * 显示选择的宠物 + */ + fun showPetNameAndHead(position: Int) { + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + if (mHomeV2Activity.mPetList.size == 0) { + return + } + mCurrentShowPetPos = position + + ViewUtil.instance.selectPetDialogShow( + mContext!!, + mHomeV2Activity.mPetList, + position, + mViewBinding.ilHomeRoutePetHead.homeDataPetNameSmall, + mViewBinding.ilHomeRoutePetHead.homeDataPetHeadSmall.appHeadImage + ) + + if (mHistoryDataMapCommon.isMapLoadOk()) { + getHistoryDay(mViewBinding.tvHomeRouteCalendarFrom) + } + } + + private fun setLatLngData(data: MutableList) { + viewLifecycleOwner.lifecycleScope.launch { + mHistoryDataList = data + mHomeV2Activity.getPet(false)?.let { + mHistoryDataMapCommon.setPetBean(it) + } + + if (isResumed) { + mHomeV2Activity.getPet()?.let { pet -> + if (Util.checkPackageLimit(mHomeV2Activity, pet.deviceId)) { + mHistoryDataMapCommon.setPetUserLatLng() + return@launch + } + } + } + if (data.size == 0) { + mViewBinding.vsbMapRouteLine.visibility = View.GONE + mHistoryDataMapCommon.setPetUserLatLng() + return@launch + } + + mViewBinding.vsbMapRouteLine.visibility = View.VISIBLE + val max = data.size - 1 + setSeekBarMax(max) + + mHistoryDataMapCommon.setLatLngData(data) + } + } + + private fun setSeekBarMax(max: Int) { + mViewBinding.vsbMapRouteLine.let { + it.max = max + //定位中心显示最后一个经纬度 + it.progress = max + } + } + + /** + * cSelectView 当前设置日期的view + */ + private fun getHistoryDay(cSelectView: TypefaceTextView) { + mViewBinding.apply { + mFromTimestamp = Utils.stringToTimestamp( + tvHomeRouteCalendarFrom.text.toString(), Utils.DATE_FORMAT_PATTERN_EN1 + ) + mToTimestamp = Utils.stringToTimestamp( + tvHomeRouteCalendarTo.text.toString(), Utils.DATE_FORMAT_PATTERN_EN1 + ) + if (mToTimestamp - mFromTimestamp < 0) { + checkCorrectDate(cSelectView) + } else if (mToTimestamp - mFromTimestamp > mOnlySelectDay * 24 * 60 * 60 * 1000) { + checkCorrectDate(cSelectView) + } + + mHomeV2Activity.getPet()?.apply { + initState() + mViewBinding.miHomeRouteAddressView.visibility = View.GONE + mDataDetailViewModel.getHistoryByDay( + mHomeV2Activity, deviceId, mFromTimestamp / 1000, mToTimestamp / 1000 + ) + } + } + } + + /** + * 检测符合的日期 + */ + private fun checkCorrectDate(cSelectView: TypefaceTextView) { + mViewBinding.apply { + if (cSelectView == tvHomeRouteCalendarFrom) { + mToTimestamp = Utils.getAfterHowTimestamp(mFromTimestamp, mOnlySelectDay) + if (mToTimestamp > mCurrentTimestamp) { + mToTimestamp = mCurrentTimestamp + } + tvHomeRouteCalendarTo.text = Utils.formatTime( + mToTimestamp, Utils.DATE_FORMAT_PATTERN_EN1 + ) + mToCalenderDialog?.setSelectDate(mToTimestamp) + } else { + //结束日期小于开始日期,就默认选择结束日期前一天 + mFromTimestamp = + if (mFromTimestamp > mToTimestamp) Utils.getBeforeHowTimestamp(mToTimestamp, 1L) + else Utils.getBeforeHowTimestamp(mToTimestamp, mOnlySelectDay) + tvHomeRouteCalendarFrom.text = Utils.formatTime( + mFromTimestamp, Utils.DATE_FORMAT_PATTERN_EN1 + ) + mFromCalenderDialog?.setSelectDate(mFromTimestamp) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { +// ilHomeRoutePetHead.ivTopPetBtnSmall -> startActivity( +// Intent( +// mContext, NotificationV2Activity::class.java +// ) +// ) + ilHomeRoutePetHead.homeDataPetNameSmall, ilHomeRoutePetHead.homeDataPetHeadSmall.root -> mHomeV2Activity.selectPetDialog() + + llHomeRouteCalendarFrom -> { + mHomeV2Activity.getPet()?.let { + if (Util.checkPackageLimit(mHomeV2Activity, it.deviceId)) return@apply + } + + if (null == mFromCalenderDialog) { + mFromCalenderDialog = ShowCalenderAndTimeDialog( + mContext!!, + tvHomeRouteCalendarFrom, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + isSelectCustomDate = true + getHistoryDay(tvHomeRouteCalendarFrom) + } + }, + calenderShowTimestamp = mFromTimestamp + ) + } + mFromCalenderDialog?.show() + } + + llHomeRouteCalendarTo -> { + mHomeV2Activity.getPet()?.let { + if (Util.checkPackageLimit(mHomeV2Activity, it.deviceId)) return@apply + } + if (null == mToCalenderDialog) { + mToCalenderDialog = ShowCalenderAndTimeDialog( + mContext!!, + tvHomeRouteCalendarTo, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + isSelectCustomDate = true + getHistoryDay(tvHomeRouteCalendarTo) + } + }, + calenderShowTimestamp = mToTimestamp + ) + } + mToCalenderDialog?.show() + } + + ivHomeRouteMapTypeBtn -> mHistoryDataMapCommon.switchSatelliteAndNormalMapType() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/HomeFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/HomeFragment.kt new file mode 100644 index 0000000..a6d02f9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/HomeFragment.kt @@ -0,0 +1,32 @@ +package com.abbidot.tracker.ui.fragment.demo + +import android.content.Intent +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.databinding.FragmentHomeBinding +import com.abbidot.tracker.deprecated.ui.activity.DetailActivity +import com.abbidot.tracker.deprecated.ui.fragment.vm.HomeViewModel +import com.abbidot.tracker.dialog.DialogUtils + +/** + * + */ +class HomeFragment : BaseFragment(FragmentHomeBinding::inflate) { + // +// companion object { +// @JvmStatic +// fun newInstance(context: Context) = HomeFragment().apply { +// mContext = context +// } +// } + override fun initData() { + super.initData() + mViewBinding.test.setOnClickListener { + DialogUtils.showMessageNegativeDialog(mActivity!!, "确定进去吗", { dialog, _ -> + startActivity(Intent(context, DetailActivity::class.java)) + dialog.dismiss() + }, null) + } + + HomeViewModel().getData() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/MineFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/MineFragment.kt new file mode 100644 index 0000000..59c937b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/MineFragment.kt @@ -0,0 +1,114 @@ +package com.abbidot.tracker.ui.fragment.demo + +import android.content.Context +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.databinding.FragmentMineBinding +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.widget.pullLayout.QMUIPullLayout + + +class MineFragment : BaseFragment(FragmentMineBinding::inflate) { + private lateinit var mAdapter: BaseRecyclerAdapter + private lateinit var list: MutableList + + companion object { + @JvmStatic + fun newInstance(context: Context) = MineFragment().apply { + mContext = context + } + } + + override fun initData() { + list = mutableListOf() + mAdapter = object : BaseRecyclerAdapter(mContext!!, list) { + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_empty_view + + override fun getItemLayoutId(viewType: Int) = R.layout.layout_list_item + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: String) { + holder!!.setText(R.id.sdagfdsgg, item) + } + } + + ViewUtil.instance.setRecyclerViewDecorationLinearLayoutManager(mContext!!, + mViewBinding.minePullLoad.pullRefreshAndLoadMoreRv, mAdapter) + + mViewBinding.minePullLoad.pullRefreshAndLoadMoreLayout.setActionListener { pullAction -> + mViewBinding.minePullLoad.pullRefreshAndLoadMoreLayout.postDelayed({ + if (pullAction.pullEdge == QMUIPullLayout.PULL_EDGE_TOP) { + onRefreshData() + } else if (pullAction.pullEdge == QMUIPullLayout.PULL_EDGE_BOTTOM) { + onLoadMore() + } + mViewBinding.minePullLoad.pullRefreshAndLoadMoreLayout.finishActionRun(pullAction) + }, 1500) + } + + mAdapter.setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + showLoading(true) + mViewBinding.minePullLoad.pullRefreshAndLoadMoreRv.postDelayed(Runnable { + onRefreshData() + showLoading(false) + }, 1500) + + } + }) + showToast(R.string.txt_net_error) + } + + private fun onLoadMore() { + val id = System.currentTimeMillis() + list = mAdapter.getData() + val data: MutableList = list.toMutableList() + for (i in 0..9) { + data.add("onLoadMore-$id-$i") + } + mAdapter.setData(data) + + val diffResult = DiffUtil.calculateDiff(object : BaseDiffCallBack(list, data) { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition] == data[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition] == data[newItemPosition] + + }, true) + + diffResult.dispatchUpdatesTo(mAdapter) + } + + private fun onRefreshData() { + val data: MutableList = ArrayList() + val id = System.currentTimeMillis() + for (i in 0..20) { + data.add("onRefreshData-$id-$i") + } + list = mAdapter.getData() + mAdapter.setData(data) + if (list.size == 0) { + //防止空布局报错 + mAdapter.notifyDataSetChanged() + return + } + + val diffResult = DiffUtil.calculateDiff(object : BaseDiffCallBack(list, data) { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition] == data[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition] == data[newItemPosition] + + }, true) + + diffResult.dispatchUpdatesTo(mAdapter) + + //刷新完成重新定位到第一个数据 +// mViewBinding.recordPullLayout.pullRefreshRv.scrollToPosition(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/RecordFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/RecordFragment.kt new file mode 100644 index 0000000..6b687fd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/demo/RecordFragment.kt @@ -0,0 +1,86 @@ +package com.abbidot.tracker.ui.fragment.demo + +import android.content.Intent +import androidx.recyclerview.widget.DiffUtil +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.list.RecyclerViewHolder +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseDiffCallBack +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.databinding.FragmentRecordBinding +import com.abbidot.tracker.deprecated.ui.activity.SwipeActionActivity +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.widget.pullRefreshLayout.QMUIPullRefreshLayout + +class RecordFragment : BaseFragment(FragmentRecordBinding::inflate) { + private lateinit var mAdapter: BaseRecyclerAdapter + private lateinit var list: MutableList + +// companion object { +// @JvmStatic +// fun newInstance(context: Context) = RecordFragment().apply { +// mContext = context +// } +// } + + override fun initData() { + list = mutableListOf() + mAdapter = object : BaseRecyclerAdapter(mContext!!, list) { + override fun getEmptyLayoutId(viewType: Int) = R.layout.layout_empty_view + + override fun getItemLayoutId(viewType: Int) = R.layout.layout_list_item + + override fun bindData(holder: RecyclerViewHolder?, position: Int, item: String) { + holder!!.setText(R.id.sdagfdsgg, item) + } + } + + ViewUtil.instance.setRecyclerViewDecorationLinearLayoutManager(mContext!!, + mViewBinding.recordPullLayout.pullRefreshRv, mAdapter) + + mViewBinding.recordPullLayout.onlyPullToRefresh.setOnPullListener(object : + QMUIPullRefreshLayout.OnPullListener { + override fun onMoveTarget(offset: Int) { + } + + override fun onMoveRefreshView(offset: Int) { + } + + override fun onRefresh() { + mViewBinding.recordPullLayout.onlyPullToRefresh.postDelayed(Runnable { + onDataLoaded() + mViewBinding.recordPullLayout.onlyPullToRefresh.finishRefresh() + }, 1000) + } + }) + + mViewBinding.swipeBtn.setOnClickListener { + startActivity(Intent(mContext, SwipeActionActivity::class.java)) + } + + onDataLoaded() + } + + private fun onDataLoaded() { + val data: MutableList = ArrayList( + listOf("Helps", "Maintain", "Liver", "Health", "Function", "Supports", "Healthy", + "Fat", "Metabolism", "Nuturally", "Bracket", "Refrigerator", "Bathtub", + "Wardrobe", "Comb", "Apron", "Carpet", "Bolster", "Pillow", "Cushion")) + data.shuffle() + list = mAdapter.getData() + mAdapter.setData(data) + + val diffResult = DiffUtil.calculateDiff(object : BaseDiffCallBack(list, data) { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition] == data[newItemPosition] + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + list[oldItemPosition] == data[newItemPosition] + + }, true) + + diffResult.dispatchUpdatesTo(mAdapter) + //刷新完成重新定位到第一个数据 + mViewBinding.recordPullLayout.pullRefreshRv.scrollToPosition(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/device/HomeTrackFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/device/HomeTrackFragment.kt new file mode 100644 index 0000000..031f6b7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/device/HomeTrackFragment.kt @@ -0,0 +1,923 @@ +package com.abbidot.tracker.ui.fragment.device + +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import android.view.Gravity +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.FencesZoneAdapter +import com.abbidot.tracker.adapter.HomePetTrackMenuAdapter +import com.abbidot.tracker.adapter.HomePetTrackStateAdapter +import com.abbidot.tracker.adapter.WiFiZoneAdapter +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.BleReportDataBean +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.FirmwareBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.databinding.FragmentHomeTrackBinding +import com.abbidot.tracker.dialog.DFUNewDialog +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.device.fences.VirtualFencesActivity +import com.abbidot.tracker.ui.activity.device.wifi.PowerSavingSetActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.ConnectionDeviceViewModel +import com.abbidot.tracker.vm.DeviceDFUViewModel +import com.abbidot.tracker.vm.DownLoadFileViewModel +import com.abbidot.tracker.vm.TrackerInfoViewModel +import com.abbidot.tracker.vm.TrackerSetViewModel +import com.clj.fastble.BleManager +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import no.nordicsemi.android.dfu.DfuServiceListenerHelper + + +/** + * 首页Track切换页面 + * create an instance of this fragment. + */ +class HomeTrackFragment : + BaseFragment(FragmentHomeTrackBinding::inflate) { + + private val mTrackerInfoViewModel: TrackerInfoViewModel by viewModels() + private val mTrackerSetViewModel: TrackerSetViewModel by viewModels() + private val mConnectionDeviceViewModel: ConnectionDeviceViewModel by viewModels() + private val mDeviceDFUViewModel: DeviceDFUViewModel by viewModels() + private val mDownLoadFileViewModel: DownLoadFileViewModel by viewModels() + + private lateinit var mHomeV2Activity: HomeV2Activity + + private lateinit var mHomePetTrackStateAdapter: HomePetTrackStateAdapter + private lateinit var mTrackStateList: MutableList + + private lateinit var mFencesAdapter: FencesZoneAdapter + private lateinit var mFencesList: MutableList + + private lateinit var mZoneAdapter: WiFiZoneAdapter + private lateinit var mZoneList: MutableList + + private lateinit var mTrackMenuAdapter: HomePetTrackMenuAdapter + private lateinit var mTrackMenuList: MutableList + + private var mDFUNewDialogDialog: DFUNewDialog? = null +// private var mTrackerDFUStateDialog: TrackerDFUStateDialog? = null +// private lateinit var mDFUStateAdapter: DFUStateAdapter + + private var mMapDeviceBean: MapDeviceBean? = null + private var mBleTrackDeviceBean: BleTrackDeviceBean? = null + + //是否是dfu的返回的 + private var isDFUCallback = false + private var mFirmwareBean: FirmwareBean? = null + + companion object { + @JvmStatic + fun newInstance(context: Context) = HomeTrackFragment().apply { + mContext = context + } + } + + override fun initData() { + mHomeV2Activity = activity as HomeV2Activity + mViewBinding.apply { + root.setBackgroundResource(R.color.transparent) + addListData() + mHomePetTrackStateAdapter = + HomePetTrackStateAdapter(mContext!!, mTrackStateList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + when (pos) { + 0 -> { + val value = mHomePetTrackStateAdapter.getData()[pos].menuValue + if (value == getString(R.string.txt_offline)) showToast( + R.string.txt_no_signal_tip, gravity = Gravity.CENTER + ) + } + + 1 -> { + val value = mHomePetTrackStateAdapter.getData()[pos].menuValue + if (value == getString(R.string.txt_no_signal)) showToast( + R.string.txt_no_signal_tip, gravity = Gravity.CENTER + ) + } + + 4 -> connectionBtn(getData()[pos].menuValue) + 5 -> setLedSwitch() + } + } + }) + } + ViewUtil.instance.setRecyclerViewGridLayout( + mContext!!, + rvHomePetTrackState, + mHomePetTrackStateAdapter, + 3, + itemWidth = AppUtils.dpToPx(110), + top = QMUIDisplayHelper.dpToPx(8), + offset = AppUtils.dpToPx(16) + ) + mFencesAdapter = FencesZoneAdapter(mContext!!, mFencesList, false).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + mHomeV2Activity.getPet()?.apply { + goPetDataActivity(VirtualFencesActivity::class.java) + } + } + }) + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) { + mHomeV2Activity.getPet()?.let { pet -> + if (Util.checkPackageLimit(mHomeV2Activity, pet.deviceId)) { + return + } + } + goPetDataActivity(VirtualFencesActivity::class.java) + } + } + }) + } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, + rvHomePetTrackVirtualFences, + mFencesAdapter, + top = QMUIDisplayHelper.dpToPx(18) + ) + + mZoneAdapter = WiFiZoneAdapter(mContext!!, mZoneList, false).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + goPetDataActivity(PowerSavingSetActivity::class.java) + } + }) + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) { + goPetDataActivity(PowerSavingSetActivity::class.java) + } + } + }) + } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvHomePetTrackWifiZone, mZoneAdapter, top = QMUIDisplayHelper.dpToPx(18) + ) + + mTrackMenuAdapter = HomePetTrackMenuAdapter(this@HomeTrackFragment, mTrackMenuList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvHomePetTrackMenu, mTrackMenuAdapter + ) + + setOnClickListenerViews(btnHomePetTrackWifiZoneManage, btnHomePetTrackVirtualFencesEdit) + +// mHomeV2Activity.getPet()?.apply { +// mTrackerManageSetViewModel.getPetTrackerInfo(mHomeV2Activity, deviceId) +// } + } + + getPetTrackerInfoData() + } + + override fun liveDataObserve() { + //接收蓝牙连接状态 + XEventBus.observe(this, EventName.ConnectDeviceState) { ble: BleTrackDeviceBean -> + mHomeV2Activity.getPet()?.apply { + if (ble.mac == macID) { + updateConState(ble) + } + } + } + //接收开蓝牙操作,然后搜索连接设备 + XEventBus.observe(this, EventName.ActionConDeviceState) { conState: Int -> + if (conState == ConState.CONNECTING || conState == ConState.CONNECTING_MAC) { +// mConnectionDeviceViewModel.connectingFailCountDown() + mTrackStateList[4].apply { + menuValue = getString(R.string.txt_connecting) + colorRedId = R.color.gray + isSwitch = true + mHomePetTrackStateAdapter.notifyItemChanged(4) + } + } else { + mTrackStateList[4].apply { + menuValue = getString(R.string.txt_unconnect_no) + isSwitch = false + colorRedId = R.color.orange_color3 + mHomePetTrackStateAdapter.notifyItemChanged(4) + } + } + } + //接收发送指令后设备返回的数据 + XEventBus.observe(this, EventName.DeviceReceiveData) { receiveData: ReceiveDeviceData -> + getBleTrackDeviceBean(false)?.let { + if (it.mac == receiveData.mac) { + val data = receiveData.data + parseData(data) + } + } + } + //蓝牙上报的数据 + XEventBus.observe(this, EventName.BleReportData) { reportData: BleReportDataBean -> + mHomeV2Activity.getPet(false)?.apply { + if (macID == reportData.mac) updateBleReportData(reportData) + } + } + + mTrackerInfoViewModel.mTrackerInfoLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + mMapDeviceBean = data +// if (BleManager.getInstance().isConnected(deviceMacId)) { +// LogUtil.e("蓝牙连接,不使用接口数据更新状态") +// } else { +// } + setStateData(data) + setZoneData(data) + } + } + }) + shareDeviceState() + } + + //下载固件 + mDownLoadFileViewModel.mDownloadProgressLiveData.observe(this) { + it?.apply { +// mTrackerDFUStateDialog?.addData(this) + if (progress == 100) { + LogUtil.e("固件下载完成") + mDeviceDFUViewModel.startDFU( + mContext!!, mBleTrackDeviceBean!!.bleDevice!!, filePath + ) + } + } + } + + mDeviceDFUViewModel.apply { + //接收固件版本返回 + mFirmwareInfoLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mFirmwareBean = this + mFirmwareBean?.mac = mBleTrackDeviceBean?.mac!! + + val localVersion = mTrackMenuList[3].menuValue + if (mDeviceDFUViewModel.checkVersionUpdate(localVersion, version)) { + mTrackMenuList[3].isSwitch = true + mTrackMenuAdapter.notifyItemChanged(3) + } + } + } + }) + } + //接收dfu结果 + mDfuStateLiveData.observe(viewLifecycleOwner) { + it?.apply { +// mTrackerDFUStateDialog?.addData(this) + if (progress in 0..100) mDFUNewDialogDialog?.setDFUProgress(progress) + if (progress == mDeviceDFUViewModel.mDFUSuccessCode) { + mDFUNewDialogDialog?.setDFUProgress(100) + isDFUCallback = true + mBleTrackDeviceBean?.let { ble -> + mConnectionDeviceViewModel.connectDeviceToMac( + mHomeV2Activity, ble.mac, false + ) + } + } else if (progress == mDeviceDFUViewModel.mDFUFailCode) { +// mTrackerDFUStateDialog?.dismiss() + mDFUNewDialogDialog?.setDFUFail { + mFirmwareBean?.apply { + mDownLoadFileViewModel.downLoadFile( + mContext!!, fileName, firmwareUrl + ) + } + } + } + } + } + } + + mTrackerSetViewModel.apply { + mTurnoffLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + mTrackMenuList[4].menuValue = ConstantInt.Close.toString() + mTrackMenuAdapter.notifyItemChanged(4) + bleTurnOff() + } + }) + } + mLedSwitchLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + + } + }) + } + } + } + + fun getPetTrackerInfoData() { + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + mHomeV2Activity.getPet()?.apply { +// mViewBinding.ivHomePetTrackPetHead.load(imgurl) + mTrackerInfoViewModel.getPetTrackerInfo(mHomeV2Activity, deviceId) + + mTrackMenuList[1].menuValue = "" + mTrackMenuList[2].menuValue = "" + + getBleTrackDeviceBean(false)?.let { ble -> + if (BleManager.getInstance().isConnected(macID)) { + //获取固件版本号 + if (null == mFirmwareBean) { + SRBleUtil.instance.writeData( + ble.bleDevice, SRBleCmdUtil.instance.getFirmwareVersion() + ) + } else { + //只有新的设备,回到这个界面才去检查dfu + if (mFirmwareBean?.mac != ble.mac) { + mTrackMenuList[3].menuValue = "" + mTrackMenuList[3].isSwitch = false + SRBleUtil.instance.writeData( + ble.bleDevice, SRBleCmdUtil.instance.getFirmwareVersion() + ) + } + } + } else { + mFirmwareBean = null + mTrackMenuList[3].menuValue = "" + mTrackMenuList[3].isSwitch = false + } + } + if (null == mBleTrackDeviceBean) { + mFirmwareBean = null + mTrackMenuList[3].menuValue = "" + mTrackMenuList[3].isSwitch = false + } + mTrackMenuAdapter.notifyItemRangeChanged(1, 3) + } + } + + /** + * 显示分享的设备状态 + */ + private fun shareDeviceState() { + val share = MMKVUtil.getInt(MMKVKey.Shared) + if (share == ConstantInt.NoShare) { + mZoneAdapter.showNoDataAddButton(true) + mFencesAdapter.showNoDataAddButton(true) + } else { + mZoneAdapter.showNoDataAddButton(false) + mFencesAdapter.showNoDataAddButton(false) + } + } + + private fun addListData() { + mTrackStateList = mutableListOf() + mFencesList = mutableListOf() + mZoneList = mutableListOf() + + ViewUtil.instance.addMenuBean( + mTrackStateList, + getString(R.string.tracker_manage_set_network), + getString(R.string.txt_offline), + imageResId = R.drawable.icon_map_signal + ) + ViewUtil.instance.addMenuBean( + mTrackStateList, + getString(R.string.tracker_manage_set_gps), + getString(R.string.txt_weak_signal), + imageResId = R.drawable.icon_map_gps + ) + ViewUtil.instance.addMenuBean( + mTrackStateList, + getString(R.string.txt_battery), + "N/A", + imageResId = -1, + menuType = MultipleEntity.IMG_TEXT_IMG + ) + ViewUtil.instance.addMenuBean( + mTrackStateList, + getString(R.string.txt_low_zone), + getString(R.string.txt_outside), + imageResId = R.drawable.icon_map_zone + ) + ViewUtil.instance.addMenuBean( + mTrackStateList, + getString(R.string.txt_bluetooth), + getString(R.string.txt_unconnect_no), + menuType = MultipleEntity.TEXT_TEXT_IMG + ) + + ViewUtil.instance.addMenuBean( + mTrackStateList, + getString(R.string.txt_light), + getString(R.string.tracker_manage_set_led_off), + imageResId = R.drawable.icon_light_svg + ) + + mTrackMenuList = mutableListOf() + ViewUtil.instance.addMenuBean(mTrackMenuList, getString(R.string.tracker_manage_led)) + ViewUtil.instance.addMenuBean( + mTrackMenuList, getString(R.string.tracker_manage_set_duration) + ) + ViewUtil.instance.addMenuBean(mTrackMenuList, getString(R.string.txt_live_duration)) + ViewUtil.instance.addMenuBean( + mTrackMenuList, + getString(R.string.txt_firmware_upgrade), + menuType = MultipleEntity.IMG_TEXT_IMG + ) + ViewUtil.instance.addMenuBean( + mTrackMenuList, + getString(R.string.txt_power_off), + menuType = MultipleEntity.IMG_TEXT//, menuType = MultipleEntity.TEXT_NAME + ) + } + + /** + * 更新蓝牙上报的数据 + */ + private fun updateBleReportData(bleReportDataBean: BleReportDataBean) { + bleReportDataBean.apply { + if (haveDeviceStateData) { + if (null == mMapDeviceBean) mMapDeviceBean = MapDeviceBean() + mMapDeviceBean!!.let { + Util.bleReportDataToMapDevice(it, bleReportDataBean) + setStateData(it) + } + } + } + } + + /** + * 设置相关状态数据 + */ + private fun setStateData(mapDeviceBean: MapDeviceBean) { + mapDeviceBean.apply { + val startTime = "${getString(R.string.txt_start_time)}:${ + startTime.split(" ")[0].replace( + "-", "." + ) + }" + val eTime = Utils.stringToDate( + endTime, Utils.DATE_FORMAT_PATTERN_CN, Utils.DATE_FORMAT_PATTERN_EN7 + ) + val endTime = "${getString(R.string.txt_expires_on1)}$eTime" + val id = "ID:$deviceOutId" + mViewBinding.let { + it.tvHomePetTrackPetStartTime.text = startTime + it.tvHomePetTrackPetEndTime.text = endTime + it.tvHomePetTrackId.text = id + } + + //是否超时上报 + val isTimeoutReport = Util.isTimeoutReport(updateTime) + mTrackStateList[0].let { + it.menuValue = + if (isTimeoutReport || lteSignal == ConstantInt.NoSignal || powerSwitch == ConstantInt.Type0 || powerSwitch == ConstantInt.Type2) { + it.colorRedId = R.color.orange_color3 + getString(R.string.txt_offline) + } else { + it.colorRedId = R.color.blue_color1 + getString(R.string.txt_online) + } + } + mTrackStateList[1].let { + it.colorRedId = R.color.orange_color3 + it.menuValue = + if (isTimeoutReport || gpsSignal == ConstantInt.NoSignal || powerSwitch == ConstantInt.Type0 || powerSwitch == ConstantInt.Type2 || inWifiZone == ConstantInt.Type1) { + getString(R.string.txt_no_signal) + } else if (gpsSignal > ConstantInt.WeakSignal) { + it.colorRedId = R.color.blue_color1 + getString(R.string.txt_strong_signal) + } else { + getString(R.string.txt_weak_signal) + } + } + mTrackStateList[2].let { + it.menuValue = + if (isTimeoutReport || lteSignal == ConstantInt.NoSignal || powerSwitch == ConstantInt.Type0 || powerSwitch == ConstantInt.Type2) { + it.colorRedId = R.color.blue_color1 +// it.imageId = -1 +// "N/A" + "${batteryLevel}%" + } else { + it.colorRedId = + if (batteryLevel > ConstantInt.LowBattery20) R.color.blue_color1 + else R.color.orange_color3 + "${batteryLevel}%" + } + it.imageId = Utils.calculatePowerLevel(batteryLevel) + } + mTrackStateList[3].let { + it.colorRedId = R.color.orange_color3 + it.menuValue = when (inWifiZone) { + ConstantInt.Open -> { + it.colorRedId = R.color.blue_color1 + getString(R.string.txt_inside) + } + + ConstantInt.Type2 -> getString(R.string.txt_not_set) + else -> getString(R.string.txt_outside) + } + } + mTrackStateList[4].let { + if (it.menuValue == getString(R.string.txt_connecting)) return + it.colorRedId = R.color.orange_color3 + it.menuValue = if (BleManager.getInstance().isConnected(deviceMacId)) { + it.colorRedId = R.color.blue_color1 + getString(R.string.txt_connected) + } else { + getString(R.string.txt_unconnect_no) + } + } + mTrackStateList[5].let { + it.colorRedId = R.color.orange_color3 + it.menuValue = if (ledSwitch == ConstantInt.Open) { + it.colorRedId = R.color.blue_color1 + getString(R.string.tracker_manage_set_led_on) + } else { + getString(R.string.tracker_manage_set_led_off) + } + } + + mTrackMenuList.let { + it[1].menuValue = + (gnssInterval / 60).toString() + getString(R.string.data_active_unit_min) + it[2].menuValue = + (liveTime / 60).toString() + getString(R.string.data_active_unit_min) + it[4].menuValue = powerSwitch.toString() + } + } + + mTrackMenuAdapter.notifyItemRangeChanged(1, mTrackMenuList.size) + mHomePetTrackStateAdapter.notifyItemRangeChanged(0, mTrackStateList.size) + } + + private fun setZoneData(mapDeviceBean: MapDeviceBean) { + mFencesList.clear() + mZoneList.clear() + mFencesAdapter.setData(null) + mZoneAdapter.setData(null) + mapDeviceBean.fences?.apply { + if (size > 0) { + if (size > 3) mFencesList.addAll(subList(0, 3).toList()) + else mFencesList.addAll(this.toList()) + mFencesAdapter.setData(mFencesList, true) + mViewBinding.btnHomePetTrackVirtualFencesEdit.visibility = View.VISIBLE + } else { + mViewBinding.btnHomePetTrackVirtualFencesEdit.visibility = View.GONE + } + } + mapDeviceBean.wifiZones?.apply { + if (size > 0) { + mZoneList.addAll(this) + mZoneAdapter.setData(mZoneList, true) + mViewBinding.btnHomePetTrackWifiZoneManage.visibility = View.VISIBLE + } else { + mViewBinding.btnHomePetTrackWifiZoneManage.visibility = View.GONE + } + } + } + + /** + * 设置固件信息 + */ + private fun setFirmwareData(firmware: FirmwareBean?) { + if (null == firmware) { + showToast(R.string.txt_latest_version) + return + } + firmware.apply { + val remark = if (TextUtils.isEmpty(remark)) { + "" + } else { + "\n${getString(R.string.txt_release_notes)}:${remark}" + } + val message = + "${getString(R.string.txt_firmware_name)}:$fileName\n${getString(R.string.txt_firmware_version)}:$version\n${ + getString(R.string.txt_firmware_size) + }:$firmwareSize$remark" +// if (null == mTrackerDFUStateDialog) { +// mDFUStateAdapter = DFUStateAdapter(mContext!!, null) +// mTrackerDFUStateDialog = TrackerDFUStateDialog(mContext!!, mDFUStateAdapter) +// } + val localVersion = mTrackMenuList[3].menuValue + if (mDeviceDFUViewModel.checkVersionUpdate(localVersion, version)) { +// mTrackerDFUStateDialog?.apply { +// show() +// setContent(message) +// startSureDFU { +// if (TextUtils.isEmpty(firmwareUrl)) { +// showToast(R.string.txt_no_down_url) +// } else { +// mDownLoadFileViewModel.downLoadFile(mContext, fileName, firmwareUrl) +// } +// } +// } + if (null == mDFUNewDialogDialog) mDFUNewDialogDialog = DFUNewDialog(mContext!!) + mDFUNewDialogDialog?.apply { + show() + startDfuState(version) { + if (TextUtils.isEmpty(firmwareUrl)) { + showToast(R.string.txt_no_down_url) + } else { + mDownLoadFileViewModel.downLoadFile( + mContext, fileName, firmwareUrl + ) + } + } + } + } else { + showToast(R.string.txt_latest_version) + } + } + } + + /** + * 更新设备连接状态 + */ + private fun updateConState(trackBleDevice: BleTrackDeviceBean) { + mBleTrackDeviceBean = trackBleDevice + + if (trackBleDevice.conState == ConState.CONNECTED) { + if (isDFUCallback) { + showToast(R.string.txt_upgrade_success) + } + mHomePetTrackStateAdapter.notifyItemChanged(4) + mTrackStateList[4].apply { + menuValue = getString(R.string.txt_connected) + isSwitch = false + colorRedId = R.color.blue_color1 + mHomePetTrackStateAdapter.notifyItemChanged(4) + } + //获取固件版本号 + SRBleUtil.instance.writeData( + trackBleDevice.bleDevice, SRBleCmdUtil.instance.getFirmwareVersion() + ) + } else { + if (isDFUCallback) { + showToast(R.string.txt_connect_fail) + } + mTrackStateList[4].apply { + menuValue = getString(R.string.txt_unconnect_no) + isSwitch = false + colorRedId = R.color.orange_color3 + mHomePetTrackStateAdapter.notifyItemChanged(4) + } + } + //dfu升级成功 重新连接返回 + if (isDFUCallback) { +// mTrackerDFUStateDialog?.dismiss() + isDFUCallback = false + } + } + + /** + * 解析数据 + */ + private fun parseData(data: ByteArray?) { + if (null != data && data.isNotEmpty()) { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + if (data0 == 9) { + if (data2 == 0) { + val strVersion = String(data.sliceArray(3..11)) + LogUtil.e("设备固件版本号:$strVersion") + mTrackMenuList[3].menuValue = strVersion + mTrackMenuList[3].isSwitch = false + mTrackMenuAdapter.notifyItemChanged(3) + mDeviceDFUViewModel.getFirmware(mHomeV2Activity, false) + mBleTrackDeviceBean?.apply { + SRBleUtil.instance.writeData( + bleDevice, SRBleCmdUtil.instance.ledState(SRBleCmdUtil.CMD_READ, 0) + ) + } + } + } else if (data0 == 3 && data1 == 0 && data2 == 0) { + val data3 = SRBleCmdUtil.instance.byteToInt(data[3]) + if (data3 in 1..3) setLedState(ConstantInt.Open) + else setLedState(ConstantInt.Close) + } else if (data0 == 4) { + if (data1 == 1 && data2 == 0) { + mTrackMenuList[1].menuValue = "" + mTrackMenuList[2].menuValue = "" + mTrackMenuList[3].menuValue = "" + //取消红点显示 + mTrackMenuList[3].isSwitch = false + mTrackMenuList[4].menuValue = ConstantInt.Close.toString() + mTrackMenuAdapter.notifyItemRangeChanged(1, 4) + mBleTrackDeviceBean?.apply { + SRBleUtil.instance.disconnectToMac(mac) + } + } + } + } + } + + fun goPetDataActivity(cls: Class<*>) { + mHomeV2Activity.getPet()?.apply { + val intent = Intent(mContext, cls) + intent.putExtra(ConstantString.Pet, this) + startActivity(intent) + } + } + + fun goMapDeviceDataActivity(cls: Class<*>) { + mMapDeviceBean?.apply { + val intent = Intent(mContext, cls) + intent.putExtra(ConstantString.MapDeviceBean, this) + startActivity(intent) + } + } + + /** + * 获取当前宠物的连接设备 + */ + private fun getBleTrackDeviceBean(isShowBleUnConTip: Boolean = true): BleTrackDeviceBean? { + mHomeV2Activity.getPet()?.apply { + if (null == mBleTrackDeviceBean) { + mBleTrackDeviceBean = SRBleUtil.instance.getConnectMacDevice(macID) + } else { + //判断是否切换了宠物 + mBleTrackDeviceBean?.let { + if (it.mac != macID) mBleTrackDeviceBean = + SRBleUtil.instance.getConnectMacDevice(macID) + } + } + } + + if (isShowBleUnConTip && null == mBleTrackDeviceBean) { + showToast(R.string.tracker_manage_set_ble_unconnect) + } + return mBleTrackDeviceBean + } + + /** + * 固件开始检查更新 + */ + fun firmwareCheckUpdate() { + getBleTrackDeviceBean()?.let { + if (BleManager.getInstance().isConnected(it.bleDevice)) { + setFirmwareData(mFirmwareBean) + } else { + showToast(R.string.tracker_manage_set_ble_unconnect) + } + } + } + + /** + * 手动连接/断开设备 + */ + private fun connectionBtn(btnString: String) { + if (btnString == getString(R.string.txt_unconnect_no)) { + Util.checkBluetoothPermissionsEnabled(mContext!!, { + mHomeV2Activity.getPet()?.apply { + mConnectionDeviceViewModel.connectDeviceToMac(mHomeV2Activity, macID, false) + } + }) + } else if (btnString == getString(R.string.txt_connected)) { + operateDeviceTips(R.string.txt_disconnect_tips, 1) + } + } + + /** + * 设备关机/断开连接设备 + */ + fun operateDeviceTips(messageID: Int, type: Int) { + if (type == ConstantInt.Type0) { + getBleTrackDeviceBean()?.apply { + if (!BleManager.getInstance().isConnected(mac)) { + showToast(R.string.tracker_manage_set_ble_unconnect) + return@apply + } + } + if (null == mBleTrackDeviceBean) return + } + ViewUtil.instance.showDialog( + mContext!!, messageID, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + when (type) { + 0 -> mMapDeviceBean?.let { +// mTrackerSetViewModel.turnOff(mHomeV2Activity, it.deviceServerId) + bleTurnOff() + } + + 1 -> mBleTrackDeviceBean?.let { + mConnectionDeviceViewModel.disconnect(it.bleDevice) + } + } + } + }, okTextResId = R.string.start_txt_yes + ) + } + + /** + * 远程设置led开关 + */ + private fun setLedSwitch() { + mTrackStateList[5].apply { + val ledSwitch = if (menuValue == getString(R.string.tracker_manage_set_led_on)) { + ConstantInt.Close + } else { + ConstantInt.Open + } + //蓝牙控制 + getBleTrackDeviceBean(false)?.let { + SRBleUtil.instance.setBleLedSwitch(mContext!!, it.bleDevice, ledSwitch) + } + //远程控制 + mMapDeviceBean?.let { + mTrackerSetViewModel.setLedSwitch(null, it.deviceServerId, ledSwitch) + } + + setLedState(ledSwitch) + } + } + + /** + * 设置LED的状态 + */ + private fun setLedState(ledSwitch: Int) { + mTrackStateList[5].apply { + colorRedId = R.color.orange_color3 + menuValue = if (ledSwitch == ConstantInt.Open) { + colorRedId = R.color.blue_color1 + getString(R.string.tracker_manage_set_led_on) + } else { + getString(R.string.tracker_manage_set_led_off) + } + mHomePetTrackStateAdapter.notifyItemChanged(5) + } + } + + private fun bleTurnOff() { + //判断是否有蓝牙连接 + mBleTrackDeviceBean?.apply { + if (BleManager.getInstance().isConnected(bleDevice)) { + val cmd = SRBleCmdUtil.instance.shutdownDevice() + SRBleUtil.instance.writeData(bleDevice, cmd) + } + } + } + + override fun onResume() { + super.onResume() + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + DfuServiceListenerHelper.registerProgressListener( + mContext!!, mDeviceDFUViewModel.mDfuProgressListener + ) + } + + override fun onPause() { + super.onPause() + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + DfuServiceListenerHelper.unregisterProgressListener( + mContext!!, mDeviceDFUViewModel.mDfuProgressListener + ) + } + + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnHomePetTrackWifiZoneManage -> goPetDataActivity(PowerSavingSetActivity::class.java) + btnHomePetTrackVirtualFencesEdit -> goPetDataActivity(VirtualFencesActivity::class.java) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV2Fragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV2Fragment.kt new file mode 100644 index 0000000..6f06641 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/MapV2Fragment.kt @@ -0,0 +1,624 @@ +package com.abbidot.tracker.ui.fragment.map + +import android.content.Context +import android.content.Intent +import android.location.LocationManager +import android.provider.Settings +import android.view.View +import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.commit +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HomeMapDeviceMsgAdapter +import com.abbidot.tracker.adapter.HomeMapDeviceStateAdapter +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.BleReportDataBean +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.FragmentMapV2Binding +import com.abbidot.tracker.dialog.CommonDialog1 +import com.abbidot.tracker.dialog.SelectMapTypeDialog +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.device.MyTrackerV2Activity +import com.abbidot.tracker.ui.activity.map.LiveActivityV2 +import com.abbidot.tracker.ui.common.map.HomeMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.vm.FamilyViewModel +import com.abbidot.tracker.vm.FencesMapViewModel +import com.abbidot.tracker.vm.MapViewModel +import com.abbidot.tracker.widget.MapDeviceNetView +import com.clj.fastble.BleManager +import com.hjq.permissions.XXPermissions +import com.hjq.permissions.permission.PermissionLists +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import javax.inject.Inject + + +/** + * 首页map + * create an instance of this fragment. + */ +@AndroidEntryPoint +class MapV2Fragment : BaseFragment(FragmentMapV2Binding::inflate) { + + private val mFencesMapViewModel: FencesMapViewModel by viewModels() + private val mMapViewModel: MapViewModel by viewModels() + private val mFamilyViewModel: FamilyViewModel by viewModels() + + private lateinit var mLocationManager: LocationManager + + private lateinit var mHomeV2Activity: HomeV2Activity + + private var mSelectMapTypeDialog: SelectMapTypeDialog? = null + + //当前这个页面显示的宠物下标 + private var mCurrentShowPetPos = -1 + + @Inject + lateinit var mHomeMapCommon: HomeMapCommon + private lateinit var mFragment: Fragment + private var mMapDeviceBean: MapDeviceBean? = null + + private lateinit var mDeviceStateList: MutableList + private lateinit var mDeviceStateAdapter: HomeMapDeviceStateAdapter + private lateinit var mDeviceMsgList: MutableList + private lateinit var mDeviceMsgAdapter: HomeMapDeviceMsgAdapter + + //启动动画移动地图摄像机 + private var isAnimMoveCamera = true + private var mShowCenterLocationType = ConstantInt.PetLocationType + + //是否显示围栏 + private var isShowFence = true + + //地图类型,标准和卫星地图 + private var mMapType = ConstantInt.Type0 + + companion object { + @JvmStatic + fun newInstance(context: Context) = MapV2Fragment().apply { + mContext = context + } + } + + override fun initData() { + mLocationManager = mContext!!.getSystemService(Context.LOCATION_SERVICE) as LocationManager + mHomeV2Activity = activity as HomeV2Activity + mCurrentShowPetPos = mHomeV2Activity.mSelectPetPosition + + isShowFence = Util.getShowFenceSp() + + mViewBinding.apply { + mHomeV2Activity.edgeToEdgeAdapterBars( + rlHomeMapTopLayout, WindowInsetsCompat.Type.statusBars() + ) + mFragment = mHomeMapCommon.getMapFragment( + mContext!!, miHomeMapAddressView, dnHomeMapNetView + ) { mapLoadOk() } + + dnHomeMapNetView.setShowNetType(MapDeviceNetView.WIFI_NET_TYPE) + llHomeMapTopPet.ivTopPetBtnSmall.setImageResource(R.drawable.icon_map_type) + + ViewUtil.instance.setMapSwitchLocationButtonImage( + homeMapRefreshBtn, mShowCenterLocationType + ) + + mDeviceStateList = mutableListOf() + mDeviceMsgList = mutableListOf() + mMapViewModel.addDeviceDefaultStateData(mContext!!, mDeviceStateList) + mDeviceStateAdapter = HomeMapDeviceStateAdapter(mContext!!, mDeviceStateList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvHomeMapDeviceState, mDeviceStateAdapter, bottom = 10 + ) + + mDeviceMsgAdapter = HomeMapDeviceMsgAdapter(mContext!!, mDeviceMsgList) + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvHomeMapDeviceMsg, mDeviceMsgAdapter, bottom = 10 + ) + + setOnClickListenerViews( + homeMapRefreshBtn, + homeMapBluetoothBtn, + homeMapLiveBtn, + llHomeMapTopPet.ivTopPetBtnSmall, + llHomeMapTopPet.homeDataPetNameSmall, + llHomeMapTopPet.homeDataPetHeadSmall.root + ) + + if (AppUtils.isDebug()) { + llHomeMapTopPet.homeDataPetHeadSmall.appHeadImage.setOnLongClickListener { + mHomeV2Activity.goToDebugActivity() + true + } + } + } + + //解决报错java.lang.IllegalArgumentException + //No view found for id 0x7f090254 (com.abbidot.tracker:id/fc_home_map_fragment) for fragment HomeMapGoogleMapFragment + //https://www.jianshu.com/p/9235092f407a + childFragmentManager.commit { + add(R.id.fc_home_map_fragment, mFragment) + } +// (mActivity as FragmentActivity).supportFragmentManager.commit { +// add(R.id.fc_home_map_fragment, mFragment) +// } + + locationPermissionsTip(1) + } + + override fun onResume() { + super.onResume() + //其他页面是否选择了宠物 + if (mCurrentShowPetPos != mHomeV2Activity.mSelectPetPosition) { + showPetNameAndHead(mHomeV2Activity.mSelectPetPosition) + } + mViewBinding.apply { + homeMapLiveBtn.isEnabled = true + homeMapBluetoothBtn.isEnabled = true + } + + val showFence = Util.getShowFenceSp() + //检测直播页面有没有修改围栏显示 + if (isShowFence != showFence) { + if (null == mSelectMapTypeDialog) { + setFencesShow(showFence) + } else { + mSelectMapTypeDialog?.setFencesSwitch(showFence) + } + } + } + + /** + * 地图加载好了 + */ + private fun mapLoadOk() { + LogUtil.e("MapV2Fragment 地图加载好了${mHomeV2Activity.mPetList}") +// mHomeV2Activity.mDataViewModel.getHomeBindPetList(mHomeV2Activity) + mHomeV2Activity.apply { + if (mPetList.size > 0) showPetNameAndHead(mSelectPetPosition) + mFamilyViewModel.getDeviceInviteInfo(this) + } + } + + override fun liveDataObserve() { + //获取设备邀请信息 + mFamilyViewModel.mInviteInfoLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + if (deleteArray.isNotEmpty()) showDeleteInviteDialog(deleteArray) + if (inviteArray.isNotEmpty()) showInviteDialog(inviteArray) + } + } + }) + } + //获取当前宠物位置和围栏信息 + mMapViewModel.mMapDeviceLiveData.observe(this) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + val data = it.getOrNull() + data?.apply { + mMapDeviceBean = data + setMapData(data) + mMapViewModel.setDeviceStateAndWarningData( + mContext!!, + mHomeV2Activity.getPet(), + data, + mDeviceStateList, + mDeviceStateAdapter, + mDeviceMsgList, + mDeviceMsgAdapter, + mViewBinding.rvHomeMapDeviceMsg + ) + } + } + }, showLoading = false, isRequestErrorTip = false) + } + + //删除围栏通知 + XEventBus.observe(this, EventName.DeleteFences) { + updateMapDeviceStatus() + } + //添加编辑围栏通知 + XEventBus.observe(this, EventName.RefreshFences) { + updateMapDeviceStatus() + } + //直播自动结束提示 + XEventBus.observe(this, EventName.LiveAutoEnd) { + ViewUtil.instance.showDialog( + mContext!!, R.string.txt_live_ended, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + } + }, okTextResId = R.string.txt_sure, cancelTextResId = R.string.txt_cancel + ) + } + //直播超时 + XEventBus.observe(this, EventName.LiveOpenTimeOut) { + ViewUtil.instance.showDialog( + mContext!!, + R.string.txt_time_out_try_again, + object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + locationPermissionsTip() + } + }, + okTextResId = R.string.txt_sure + ) + } + //蓝牙上报的数据 + XEventBus.observe(this, EventName.BleReportData) { reportData: BleReportDataBean -> + mHomeV2Activity.getPet(false)?.apply { + if (macID == reportData.mac) updateBleReportData(reportData) + } + } + //接收蓝牙连接状态 + XEventBus.observe( + this, EventName.ConnectDeviceState + ) { trackBleDevice: BleTrackDeviceBean -> + mHomeV2Activity.getPet(false)?.apply { + if (trackBleDevice.mac == macID) { + //蓝牙断开就重新获取服务器数据,蓝牙数据上报断开 + if (trackBleDevice.conState == ConState.DISCONNECTED) { + updateMapDeviceStatus() + } + } + } + } + } + + /** + * 更新蓝牙上报的数据 + */ + private fun updateBleReportData(bleReportDataBean: BleReportDataBean) { + bleReportDataBean.apply { + if (haveDeviceStateData) { + mMapViewModel.stopGetData() + if (null == mMapDeviceBean) mMapDeviceBean = MapDeviceBean() + mMapDeviceBean?.let { + Util.bleReportDataToMapDevice(it, bleReportDataBean) + if (haveLocationData) { + locationList?.let { list -> + val location = list[list.size - 1] + it.longitude = location.longitude + it.latitude = location.latitude + } + mHomeMapCommon.setMapDeviceBean(mMapDeviceBean) + mHomeMapCommon.refreshPetCurrentLocation( + it.latitude, it.longitude, isAnimMoveCamera + ) + mHomeMapCommon.startRefreshUserLocation() + } + } + + mViewBinding.homeMapLiveBtn.visibility = View.VISIBLE + + mMapViewModel.setDeviceStateAndWarningData( + mContext!!, + mHomeV2Activity.getPet(), + mMapDeviceBean!!, + mDeviceStateList, + mDeviceStateAdapter, + mDeviceMsgList, + mDeviceMsgAdapter, + mViewBinding.rvHomeMapDeviceMsg + ) + } + } + } + + /** + * 显示被删除邀请的弹窗 + */ + private fun showDeleteInviteDialog(deleteArray: MutableList) { + lifecycleScope.launch(Dispatchers.IO) { + val myDeviceDb = MyDatabase.deviceDao().findAll(MyDatabase.DeviceTableName) + myDeviceDb?.apply { + for (deviceDb in myDeviceDb) { + for (item in deleteArray) { + if (deviceDb.deviceId == item.deviceId) { + MyDatabase.deviceDao().delete(deviceDb) + LogUtil.e("删除了邀请的设备") + } + } + } + } + val newDeviceDb = MyDatabase.deviceDao().findAll(MyDatabase.DeviceTableName) + newDeviceDb?.apply { + LogUtil.e("删去了邀请,还剩${newDeviceDb.size}下个设备") + } + } + CommonDialog1( + mContext!!, + getString(R.string.txt_setting_notification), + getString(R.string.txt_view_access), + false, + okClickListener = object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + if (mHomeV2Activity.mPetList.size == 0) { + mHomeV2Activity.noSharePet() + } else { + mHomeV2Activity.onChangeClick(0) + } + } + }).show() + } + + /** + * 显示被邀请通知弹窗 + */ + private fun showInviteDialog(inviteArray: MutableList) { + for (item in inviteArray) { + val content = String.format( + getString(R.string.txt_invites_you_check), item.userName, item.petName + ) + CommonDialog1( + mContext!!, + getString(R.string.txt_setting_notification), + content, + false, + okClickListener = object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + startActivity( + mHomeV2Activity, Intent( + mHomeV2Activity, MyTrackerV2Activity::class.java + ) + ) + } + }).show() + } + } + + private val mPermissionsTips = + "ABBIDOT APP collects location data,The route and distance between the current location and the device can be calculated." + + /** + * 弹窗说明需要位置权限做什么,上架市场需要 + */ + private fun locationPermissionsTip(type: Int = 0) { + if (XXPermissions.isGrantedPermissions( + mContext!!, mutableListOf( + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + ) + ) { + if (type == 0) checkPermissions(type) + } else { + ViewUtil.instance.showDialog( + mContext!!, mPermissionsTips, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + if (type == 0) checkPermissions(type) + else Util.checkLocationPermissionsGpsEnabled(mContext!!, {}) + } + }, okTextResId = R.string.txt_accept, cancelTextResId = R.string.txt_deny + ) + } + } + + /** + * 更新首页设备状态数据 + */ + private fun updateMapDeviceStatus( + isNeedCountDown: Boolean = true, useBleLocation: Boolean = false + ) { + mShowCenterLocationType = ConstantInt.PetLocationType + ViewUtil.instance.setMapSwitchLocationButtonImage( + mViewBinding.homeMapRefreshBtn, mShowCenterLocationType + ) + if (mHomeV2Activity.mPetList.size <= mCurrentShowPetPos) return + mHomeV2Activity.getPet()?.apply { + //蓝牙连接上,就直接用蓝牙上报的位置刷新 + if (useBleLocation && BleManager.getInstance().isConnected(macID)) { + mMapDeviceBean?.let { + mHomeMapCommon.refreshPetCurrentLocation( + it.latitude, it.longitude, isAnimMoveCamera + ) + mHomeMapCommon.startRefreshUserLocation() + } + } else { + mMapViewModel.getMapDeviceStatus(deviceId, isNeedCountDown) + } + } + } + + /** + * 显示地图类型选择弹窗 + */ + private fun showMapTypeDialog() { + if (null == mSelectMapTypeDialog) mSelectMapTypeDialog = ViewUtil.instance.getMapTypeDialog( + mContext!!, object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + mMapType = Util.getMapTypeSp() + if (pos == mMapType) { + return + } + mHomeMapCommon.switchSatelliteAndNormalMapType() + } + }) { v, isChecked -> + if (v.id == R.id.cb_dialog_map_fences_switch) { + setFencesShow(isChecked) + } + } + else { + mSelectMapTypeDialog!!.mapTypeSpToUpdate() + } + mSelectMapTypeDialog?.show() + } + + /** + * 设置围栏显示 + */ + private fun setFencesShow(isShow: Boolean) { + isShowFence = isShow + MMKVUtil.putBoolean(MMKVKey.ShowFence, isShow) + if (isShow) { + mMapDeviceBean?.fences?.let { fences -> + mFencesMapViewModel.setFencesData(mContext!!, fences, mFragment) + } + } else { + mFencesMapViewModel.setFencesData(mContext!!, null, mFragment) + } + } + + /** + * 设置需要更新的标识 + */ + fun setNeedUpdateTag() { + mCurrentShowPetPos = -1 + } + + /** + * 显示选择宠物 + */ + fun showPetNameAndHead(position: Int) { + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + if (mHomeV2Activity.mPetList.size == 0) { + return + } + mCurrentShowPetPos = position + + ViewUtil.instance.selectPetDialogShow( + mContext!!, + mHomeV2Activity.mPetList, + position, + mViewBinding.llHomeMapTopPet.homeDataPetNameSmall, + mViewBinding.llHomeMapTopPet.homeDataPetHeadSmall.appHeadImage + ) + + if (mHomeMapCommon.isMapLoadOk()) { + showLoading(true) + val pet = mHomeV2Activity.mPetList[position] + //重新设置地图宠物头像 + mHomeMapCommon.setPetHeadIcon(pet.imgurl, pet.petType) + + //获取首页设备信息状态 + updateMapDeviceStatus() + } + } + + + /** + * 设置地图相关数据 + */ + private fun setMapData(mapDeviceBean: MapDeviceBean) { + mapDeviceBean.apply { + //分钟后无上报、没有lte信号或在wifi中隐藏直播按钮 + mViewBinding.homeMapLiveBtn.visibility = + if (Util.isTimeoutReport(updateTime) || powerSwitch == ConstantInt.Type0 || powerSwitch == ConstantInt.Type2 || inWifiZone == ConstantInt.Type1) View.GONE + else View.VISIBLE + + mHomeMapCommon.setMapDeviceBean(this) + //设置循环查询时间间隔 30秒 + mMapViewModel.updateMillisInFuture(0.5f) + + if (isShowFence) { + fences?.let { fences -> + mFencesMapViewModel.setFencesData(mContext!!, fences, mFragment) + } + } + + mShowCenterLocationType = ConstantInt.PetLocationType + ViewUtil.instance.setMapSwitchLocationButtonImage( + mViewBinding.homeMapRefreshBtn, mShowCenterLocationType + ) + mHomeMapCommon.refreshPetCurrentLocation(latitude, longitude, isAnimMoveCamera) + mHomeMapCommon.startRefreshUserLocation() + + isAnimMoveCamera = false + } + } + + /** + * 检查获取定位,蓝牙权限 + */ + private fun checkPermissions(type: Int) { + Util.checkBluetoothPermissionsEnabled(mContext!!, { + if (type == 0) { + if (!mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + startActivity(intent) + } else if (mHomeV2Activity.isRequestPetData && mHomeV2Activity.mPetList.size == 0) { + showToast(R.string.no_bind_pet) + } else { + mHomeV2Activity.getPet()?.apply { + mViewBinding.homeMapLiveBtn.isEnabled = false + val intent = Intent(mContext, LiveActivityV2::class.java) + intent.putExtra(ConstantString.JumpActivity, true) + intent.putExtra(ConstantString.Pet, this) + startActivity(intent) + } + } + } else if (type == 1) { + if (!SRBleUtil.instance.isBlueEnable(mContext!!)) { + SRBleUtil.instance.openBluetooth(mContext!!) + } else if (mHomeV2Activity.isRequestPetData && mHomeV2Activity.mPetList.size == 0) { + showToast(R.string.no_bind_pet) + } else { + mHomeV2Activity.getPet()?.apply { + mViewBinding.homeMapBluetoothBtn.isEnabled = false + val intent = Intent(mContext, LiveActivityV2::class.java) + intent.putExtra(ConstantString.JumpActivity, false) + intent.putExtra(ConstantString.Pet, this) + startActivity(intent) + } + } + } + }) + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + homeMapRefreshBtn -> { + if (mHomeV2Activity.isRequestPetData && mHomeV2Activity.mPetList.size == 0) { + showToast(R.string.no_bind_pet) + return + } + if (mShowCenterLocationType == ConstantInt.PetLocationType) { + mShowCenterLocationType = ConstantInt.UserLocationType + mHomeMapCommon.switchShowLocation(mShowCenterLocationType) + + ViewUtil.instance.setMapSwitchLocationButtonImage( + homeMapRefreshBtn, mShowCenterLocationType + ) + } else { + updateMapDeviceStatus(useBleLocation = true) + } + } + + homeMapLiveBtn -> locationPermissionsTip() + llHomeMapTopPet.ivTopPetBtnSmall -> showMapTypeDialog() + homeMapBluetoothBtn -> checkPermissions(1) + llHomeMapTopPet.homeDataPetNameSmall, llHomeMapTopPet.homeDataPetHeadSmall.root -> mHomeV2Activity.selectPetDialog() + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/BaseBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/BaseBaiduMapFragment.kt new file mode 100644 index 0000000..fb97a14 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/BaseBaiduMapFragment.kt @@ -0,0 +1,845 @@ +package com.abbidot.tracker.ui.fragment.map.baidumap + +import android.content.Context +import android.graphics.Bitmap +import android.hardware.Sensor +import android.hardware.SensorEvent +import android.hardware.SensorEventListener +import android.hardware.SensorManager +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.databinding.FragmentBaseBaiduMapBinding +import com.abbidot.tracker.ui.fragment.map.googlemap.GoogleBitmapHelper +import com.abbidot.tracker.util.ImageUtil +import com.abbidot.tracker.util.LonAndLatUtil +import com.abbidot.tracker.widget.TypefaceTextView +import com.baidu.location.BDAbstractLocationListener +import com.baidu.location.BDLocation +import com.baidu.location.LocationClient +import com.baidu.location.LocationClientOption +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.map.BitmapDescriptorFactory +import com.baidu.mapapi.map.Circle +import com.baidu.mapapi.map.CircleDottedStrokeType +import com.baidu.mapapi.map.CircleOptions +import com.baidu.mapapi.map.InfoWindow +import com.baidu.mapapi.map.MapStatusUpdateFactory +import com.baidu.mapapi.map.MarkerOptions +import com.baidu.mapapi.map.MyLocationConfiguration +import com.baidu.mapapi.map.MyLocationData +import com.baidu.mapapi.map.Overlay +import com.baidu.mapapi.map.PolygonOptions +import com.baidu.mapapi.map.PolylineDottedLineType +import com.baidu.mapapi.map.PolylineOptions +import com.baidu.mapapi.map.Stroke +import com.baidu.mapapi.map.TextureMapView +import com.baidu.mapapi.model.LatLng +import com.baidu.mapapi.utils.DistanceUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlinx.coroutines.launch +import kotlin.math.abs + + +/** + * 百度地图 + */ +abstract class BaseBaiduMapFragment : + BaseFragment(FragmentBaseBaiduMapBinding::inflate) { + + private lateinit var mMapView: TextureMapView + var mBaiduMap: BaiduMap? = null + + private var mMapviewBundleKey = this.javaClass.simpleName + + //地图是否加载准备好了 + var isMapLoadOk = false + + //地图放大 + private var mBaiduMapZoom = 19f + + //自定显示宠物头像地点信息图层 + protected var mMarker: Overlay? = null + + //宠物大头针设置自定义头像 + private var mPetHeadIconBitmap: Bitmap? = null + + //宠物头像地址 + var mPetHeadUrl = "" + + //头像为空,用默认类型的头像 + private var mPetType = ConstantInt.DogPetType + + //坐标类型,定位SDK默认输出GCJ02坐标,地图SDK默认输出BD09ll坐标。火星GCJ02坐标https://lbsyun.baidu.com/faq/api?title=androidsdk/guide/create-map/location + private val mCoordType = "GCJ02" + private var mLocationClient: LocationClient? = null + private var mLocationListener: BDAbstractLocationListener? = null + + private var mSensorManager: SensorManager? = null + private var mSensor: Sensor? = null + private var mSensorEventListener: SensorEventListener? = null + private var mLastX = 0f + + //用户位置经纬度 + var mUserLatLng: LatLng? = null + + //宠物位置经纬度 + var mPetLatLng: LatLng? = null + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + mViewBinding = this.inflater(inflater, container, false) + LogUtil.e("${mMapviewBundleKey}===BaiduMapFragment--->onCreateView---$container") + +// var mapViewBundle: Bundle? = null +// if (null != savedInstanceState) { +// mapViewBundle = savedInstanceState.getBundle(mMapviewBundleKey) +// } + mMapView = mViewBinding.baiduMapView + //不显示缩放按钮控件 + mMapView.showZoomControls(false) + mBaiduMap = mMapView.map +// mMapView.onCreate(mContext, mapViewBundle) + return mViewBinding.root + } + + override fun initData() { + super.initData() + mBaiduMap?.apply { + //开启地图的定位图层 + isMyLocationEnabled = true + val locationConfiguration = + MyLocationConfiguration(MyLocationConfiguration.LocationMode.NORMAL, true, null) + //自定义精度圈边框颜色 + setMyLocationConfiguration(locationConfiguration) + + //设置是否允许俯视手势 + uiSettings.isOverlookingGesturesEnabled = false + //设置是否允许抛出惯性手势 + uiSettings.setFlingEnable(false) + + setOnMapLoadedCallback { + LogUtil.e("百度地图加载好了") + isMapLoadOk = true + + onMapLoadOk(this) + } + } + } + +// override fun onSaveInstanceState(outState: Bundle) { +// super.onSaveInstanceState(outState) +// +// var mapViewBundle = outState.getBundle(mMapviewBundleKey) +// if (null == mapViewBundle) { +// mapViewBundle = Bundle() +// outState.putBundle(mMapviewBundleKey, mapViewBundle) +// } +// mMapView.onSaveInstanceState(mapViewBundle) +// } + + override fun onResume() { + super.onResume() + //在activity执行onResume时执行mMapView. onResume (),实现地图生命周期管理 + mMapView.onResume() + } + + override fun onPause() { + super.onPause() + //在activity执行onPause时执行mMapView. onPause (),实现地图生命周期管理 + mMapView.onPause() + } + + + /** + * 卫星标准地图模式切换 + */ + fun switchSatelliteAndNormalMapType() { + if (mBaiduMap!!.mapType == BaiduMap.MAP_TYPE_NORMAL) { + setMapType(BaiduMap.MAP_TYPE_SATELLITE) + } else { + setMapType(BaiduMap.MAP_TYPE_NORMAL) + } + } + + /** + * 设置地图样式 + */ + private fun setMapType(type: Int) { + if (null != mBaiduMap) { + mBaiduMap!!.mapType = type + } + } + + /** + * 设置地图显示级别 + */ + fun setBaiduMapZoom(zoom: Float) { + mBaiduMapZoom = zoom + } + + /** + * 获取地图显示级别 + */ + fun getBaiduMapZoom() = mBaiduMapZoom + + /** + * 切换用户和宠物的位置居中显示,移动摄像头中心 + */ + fun switchShowLocation(@ConstantInt type: Int) { + val latLng = if (type == ConstantInt.PetLocationType) { + mPetLatLng + } else mUserLatLng + + latLng?.apply { + moveCameraLocation(latLng) + } + } + + /** + * 设置大头针宠物头像 + */ + fun setPetHeadIconUrl(petHeadUrl: String) { + mPetHeadUrl = petHeadUrl + mPetHeadIconBitmap?.recycle() + mPetHeadIconBitmap = null + } + + /** + * @param needConvertGCJ02 是否需要转换GCJ02坐标 + */ + fun getBaiduMapLatLng( + lat: Double, lon: Double, needConvertGCJ02: Boolean = true + ): LatLng { + val latLng = if (needConvertGCJ02) { + val convertLonLat = LonAndLatUtil.convertFromWGS84ToGCJ02(lat, lon) + LatLng(convertLonLat[0], convertLonLat[1]) + } else { + LatLng(lat, lon) + } + return latLng + } + + /** + * 设置宠物头像地图Marker + */ + private suspend fun setPetHeadMarker( + lat: Double, + lon: Double, + needConvertGCJ02: Boolean = true, + headBgResId: Int = R.drawable.pic_map_gps_avatar + ) { + if (null == mPetHeadIconBitmap) { + //设置头像 + mPetHeadIconBitmap = + GoogleBitmapHelper.headToBitmap(mContext!!, headBgResId, mPetHeadUrl, mPetType) + } + mMarker?.remove() + + val latLng = getBaiduMapLatLng(lat, lon, needConvertGCJ02) + + mPetLatLng = latLng + + //构建MarkerOption,用于在地图上添加Marker + val option = + MarkerOptions().position(latLng).animateType(MarkerOptions.MarkerAnimateType.grow) + .icon(BitmapDescriptorFactory.fromBitmap(mPetHeadIconBitmap)) + //在地图上添加Marker,并显示 + mMarker = mBaiduMap!!.addOverlay(option) + } + + /** + * 地图添加图片 + */ + private fun addImageMarker( + lat: Double, lon: Double, imageTypeResId: Int, needConvertGCJ02: Boolean = true + ) { + mBaiduMap?.apply { + val latLng = getBaiduMapLatLng(lat, lon, needConvertGCJ02) + + val bitmap = ImageUtil.getBitmapFromDrawableAndSvg(mContext!!, imageTypeResId) + //构建MarkerOption,用于在地图上添加Marker + val option = + MarkerOptions().position(latLng).animateType(MarkerOptions.MarkerAnimateType.grow) + .icon(BitmapDescriptorFactory.fromBitmap(bitmap)).apply { + anchor(anchorX, anchorY / 2) + } + addOverlay(option) + bitmap?.recycle() + } + } + + /** + * 当前宠物位置刷新 + * @param needConvertGCJ02 是否需要转换GCJ02坐标 + * @param isAnimMoveCamera 移动使用动画 + */ + fun refreshPetCurrentLocation( + lat: Double, + lon: Double, + needMoveCamera: Boolean = true, + isAnimMoveCamera: Boolean = true, + needConvertGCJ02: Boolean = true, + headBgResId: Int = R.drawable.pic_map_gps_avatar + ) { + mBaiduMap?.let { + lifecycleScope.launch(lifecycleScope.coroutineContext) { + + setPetHeadMarker(lat, lon, needConvertGCJ02, headBgResId) + + //是否需要移动到地图中心点 + if (needMoveCamera) { + //animateCamera使用动画 + moveCameraLocation(lat, lon, isAnimMoveCamera = isAnimMoveCamera) + } + + //刷新当前位置ok + onRefreshPetCurrentLocationOk(mBaiduMap!!) + } + } + } + + fun refreshPetCurrentLocation( + latLng: LatLng, + needMoveCamera: Boolean = true, + isAnimMoveCamera: Boolean = true, + headBgResId: Int = R.drawable.pic_map_gps_avatar + ) { + refreshPetCurrentLocation( + latLng.latitude, latLng.longitude, needMoveCamera, isAnimMoveCamera, false, headBgResId + ) + } + + /** + * 设置宠物当前位置经纬度 + */ + fun setPetCurrentLocation( + petHeadUrl: String, lat: Double, lon: Double, needConvertGCJ02: Boolean = true + ) { + lifecycleScope.launch(lifecycleScope.coroutineContext) { + setPetHeadIconUrl(petHeadUrl) + setPetHeadMarker(lat, lon, needConvertGCJ02) + } + } + + /** + * 改变地图手势的中心点(地图的中心点) + * @param needConvertGCJ02 是否需要转换GCJ02坐标 + */ + fun moveCameraLocation( + lat: Double, lon: Double, needConvertGCJ02: Boolean = true, isAnimMoveCamera: Boolean = true + ) { + val latLng = getBaiduMapLatLng(lat, lon, needConvertGCJ02) + + if (isAnimMoveCamera) { + //animateCamera使用动画,以动画方式更新地图状态,默认动画耗时 300 ms + mBaiduMap!!.animateMapStatus(MapStatusUpdateFactory.newLatLng(latLng), 2) + } else { + mBaiduMap!!.setMapStatus(MapStatusUpdateFactory.newLatLng(latLng)) + } + //直接缩放至缩放级别 + mBaiduMap!!.setMapStatus(MapStatusUpdateFactory.zoomTo(mBaiduMapZoom)) + } + + /** + * 改变地图手势的中心点(地图的中心点) + * @param latLng 已经是转换好的坐标 + */ + fun moveCameraLocation(latLng: LatLng, isAnimMoveCamera: Boolean = true) { + moveCameraLocation(latLng.latitude, latLng.longitude, false, isAnimMoveCamera) + } + + /** + * 显示信息窗(弹窗覆盖物InfoWindow) + */ + fun showMarkerInfoWindow(historyDataBean: HistoryDataBean) { + historyDataBean.apply { + val convertLatLng = getBaiduMapLatLng(latitude, longitude) + val view = LayoutInflater.from(mContext).inflate(R.layout.marker_info_contents, null) + val addressTextView = view.findViewById(R.id.map_pet_current_location) + val timeTextView = + view.findViewById(R.id.map_pet_location_update_time) + addressTextView.text = address + timeTextView.text = dayTime + val mInfoWindow = InfoWindow( + view, convertLatLng, -(mPetHeadIconBitmap?.width!! + 30) + ) + mBaiduMap?.showInfoWindow(mInfoWindow) + } + } + + + //在地图上画圆形、矩形和多边形围栏集合 + private var mMapFencesList: MutableList? = null + +// fun initFenceList() { +// if (null == mCircleAndRectFenceList) mCircleAndRectFenceList = mutableListOf() +// } + + /** + * Adds a [Circle] around the provided + * 地图上画一个圆形围栏 + * @param isDashedPattern 是否是虚线 + */ + fun addCircleFence( + centerLat: Double, + centerLon: Double, + radius: Double = 700.0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + if (null == mBaiduMap) return + //转换坐标 + val convertLatLng = getBaiduMapLatLng(centerLat, centerLon) + //构造CircleOptions对象 + val circleOptions = CircleOptions().center(convertLatLng).radius(radius.toInt()) + //是否显示虚线 + .dottedStroke(isDashedPattern) + .dottedStrokeType(CircleDottedStrokeType.DOTTED_LINE_SQUARE) + //填充颜色 + .fillColor(fillColor) + //边框宽和边框颜色 + .stroke(Stroke(AppUtils.dpToPx(1), strokeColor)) + + //在地图上显示圆形围栏 + val circleFence = mBaiduMap!!.addOverlay(circleOptions) + + if (null == mMapFencesList) mMapFencesList = mutableListOf() + mMapFencesList?.add(circleFence) + } + + /** + * 地图上画多边形 + * @param isDashedPattern 是否是虚线 + */ + private fun mapDrawPolygon( + points: MutableList, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + if (null == mBaiduMap) return + + //构造PolygonOptions + val polygonOptions = PolygonOptions().points(points) + //是否显示虚线 + .dottedStroke(isDashedPattern) + .dottedStrokeType(PolylineDottedLineType.DOTTED_LINE_SQUARE) + //填充颜色 + .fillColor(fillColor) + //边框宽和边框颜色 + .stroke(Stroke(AppUtils.dpToPx(1), strokeColor)) + + //在地图上显示矩形围栏 + val rectFence = mBaiduMap!!.addOverlay(polygonOptions) + + if (null == mMapFencesList) mMapFencesList = mutableListOf() + mMapFencesList?.add(rectFence) + } + + /** + * 地图上画一个矩形围栏 + * @param isDashedPattern 是否是虚线 + */ + fun addRectFence( + fencesBean: FencesBean, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + fencesBean.apply { + val points = mutableListOf() + setRectFencePointData(points, latitudeA, longitudeA) + setRectFencePointData(points, latitudeB, longitudeB) + setRectFencePointData(points, latitudeC, longitudeC) + setRectFencePointData(points, latitudeD, longitudeD) + + mapDrawPolygon(points, strokeColor, fillColor, isDashedPattern) + } + } + + /** + * 地图上画一个多边形围栏 + */ + fun addPolygonFence( + fencesBean: FencesBean, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + fencesBean.apply { + val points = mutableListOf() + setRectFencePointData(points, latitudeA, longitudeA) + setRectFencePointData(points, latitudeB, longitudeB) + setRectFencePointData(points, latitudeC, longitudeC) + setRectFencePointData(points, latitudeD, longitudeD) + setRectFencePointData(points, latitudeE, longitudeE) + setRectFencePointData(points, latitudeF, longitudeF) + mapDrawPolygon(points, strokeColor, fillColor, isDashedPattern) + } + } + + /** + * + * 地图上画一个圆形虚线围栏 + */ + fun addCircleDashedAndImageFence( + centerLat: Double, + centerLon: Double, + radius: Double = 700.0, + imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4) + ) { + if (imageTypeResId > 0) addImageMarker(centerLat, centerLon, imageTypeResId) + addCircleFence(centerLat, centerLon, radius, strokeColor, fillColor, true) + } + + /** + * + * 地图上画一个矩形虚线有中心图片围栏 + */ + fun addRectDashedAndImageFence( + fencesBean: FencesBean, imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + ) { + if (imageTypeResId > 0) addImageMarker( + fencesBean.latitudeCenter, fencesBean.longitudeCenter, imageTypeResId + ) + addRectFence(fencesBean, strokeColor, fillColor, true) + } + + fun addRectDashedAndImageEditFence( + fencesBean: FencesBean, imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + ) { + fencesBean.apply { + addRectDashedAndImageFence(this, imageTypeResId, strokeColor, fillColor) + addImageMarker(latitudeA, longitudeA, R.drawable.icon_fence_dot_svg) + addImageMarker(latitudeB, longitudeB, R.drawable.icon_fence_dot_svg) + addImageMarker(latitudeC, longitudeC, R.drawable.icon_fence_dot_svg) + addImageMarker(latitudeD, longitudeD, R.drawable.icon_fence_dot_svg) + } + } + + /** + * + * 地图上画一个多边形虚线有中心图片围栏 + */ + fun addPolygonDashedAndImageFence( + fencesBean: FencesBean, imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + ) { + if (imageTypeResId > 0) addImageMarker( + fencesBean.latitudeCenter, fencesBean.longitudeCenter, imageTypeResId + ) + addPolygonFence(fencesBean, strokeColor, fillColor, true) + } + + /** + * + * 地图上画一个正在预览的矩形虚线围栏 + */ + fun addPolygonDashedAndImageEditFence( + fencesBean: FencesBean, imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + ) { + fencesBean.apply { + addPolygonDashedAndImageFence(this, imageTypeResId, strokeColor, fillColor) + addImageMarker(latitudeA, longitudeA, R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(latitudeB, longitudeB, R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(latitudeC, longitudeC, R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(latitudeD, longitudeD, R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(latitudeE, longitudeE, R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(latitudeF, longitudeF, R.drawable.icon_fence_polygon_operate_svg) + } + } + + /** + * 设置矩形围栏点的数据 + */ + private fun setRectFencePointData(points: MutableList, lat: Double, lon: Double) { + //转换坐标 + val convertLonLat = getBaiduMapLatLng(lat, lon) + points.add(convertLonLat) + } + + /** + * 清除地图有多个围栏圈 + */ + fun clearFenceList() { +// if (null == mCircleAndRectFenceList) throw NullPointerException( +// "mCircleAndRectFenceList空指针,请在onMapLoadOk重写方法中实例化mCircleAndRectFenceList,执行方法initFenceList()" +// ) + mMapFencesList?.forEach { + it.remove() + } + mMapFencesList?.clear() + } + + private var mBlackPolyline: Overlay? = null + private var mYellowPolyline: Overlay? = null + + /** + * 画线,传入已经转换好的经纬度坐标 + * @param needBlackPolyline 是否需要黑线 + */ + fun addPolyline( + baiduMap: BaiduMap, + latLngList: MutableList, + needBlackPolyline: Boolean = true, + startCapImageRes: Int = 0 + ) { + //至少需要2个点 + if (latLngList.size < 2) { + return + } + + if (needBlackPolyline) { + val blackOverlayOptions = PolylineOptions().width(QMUIDisplayHelper.dpToPx(4)) + .color(ContextCompat.getColor(mContext!!, R.color.txt_button)).points(latLngList) + mBlackPolyline?.remove() + //在地图上绘制折线 + mBlackPolyline = baiduMap.addOverlay(blackOverlayOptions) + } + + //设置折线的属性 + val yellowOverlayOptions = PolylineOptions().width(QMUIDisplayHelper.dpToPx(3)) + .color(ContextCompat.getColor(mContext!!, R.color.select_color2)).points(latLngList) + mYellowPolyline?.remove() + mYellowPolyline = baiduMap.addOverlay(yellowOverlayOptions) + //添加起始点图片 + if (startCapImageRes > 0) { + //构建MarkerOption,用于在地图上添加Marker + val option = MarkerOptions().position(latLngList[0]) + .animateType(MarkerOptions.MarkerAnimateType.grow) + .icon(BitmapDescriptorFactory.fromResource(startCapImageRes)) + //在地图上添加Marker,并显示 + baiduMap.addOverlay(option) + } + } + + /** + * 清空所有图层 + */ + open fun clearAllMarker() { + mBaiduMap?.clear() + clearFenceList() + } + + /** + * 开始定位,请求用户当前的位置 + */ + fun startLocation(locationPeriod: Int = 1, needStopLocation: Boolean = true) { + initSensorEvent(mContext!!) + //定位初始化 + mLocationClient = LocationClient(mContext) + //通过LocationClientOption设置LocationClient相关参数 + val option = LocationClientOption() + // 打开gps + option.isOpenGps = true + // 设置坐标类型 + option.setCoorType(mCoordType) + //设置定位刷新间隔 + option.setScanSpan(locationPeriod * 1000) + //设置locationClientOption + mLocationClient!!.locOption = option + //注册LocationListener监听器 + mLocationListener = object : BDAbstractLocationListener() { + override fun onReceiveLocation(location: BDLocation?) { + //mapView 销毁后不在处理新接收的位置 + if (null == location) { + return + } + val locData = MyLocationData.Builder().accuracy(location.radius) + // 此处设置开发者获取到的方向信息,顺时针0-360 + .direction(location.direction).latitude(location.latitude) + .longitude(location.longitude).build() + mBaiduMap!!.setMyLocationData(locData) + if (needStopLocation) stopLocation() + location.city + mUserLatLng = LatLng(location.latitude, location.longitude) + locationSuccess(location) + } + } + mLocationClient!!.registerLocationListener(mLocationListener) + //开启定位 + mLocationClient!!.start() + } + + /** + * 设置方向感应器,百度地图使用 + */ + private fun initSensorEvent(context: Context) { + // 获得传感器管理器 + mSensorManager = context.getSystemService(Context.SENSOR_SERVICE) as SensorManager + if (mSensorManager != null) { + //获得方向传感器 + mSensor = mSensorManager?.getDefaultSensor(Sensor.TYPE_ORIENTATION) + } + // 注册 + if (mSensor != null) { + mSensorEventListener = object : SensorEventListener { + override fun onSensorChanged(event: SensorEvent?) { + // 接受方向感应器的类型 + if (event?.sensor?.type == Sensor.TYPE_ORIENTATION) { + val x = event.values[SensorManager.DATA_X] + if (abs(x - mLastX) > 1.0) { + mLastX = x + mBaiduMap?.apply { + setLocationData(locationData, x) + } + } + } + } + + override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) { + } + } + mSensorManager?.registerListener( + mSensorEventListener, mSensor, SensorManager.SENSOR_DELAY_UI + ) + } + } + + /** + * 设置定位的方向信息 + */ + private fun setLocationData(locData: MyLocationData?, direction: Float) { + if (null == locData || null == mBaiduMap) return + val myLocData = MyLocationData.Builder().accuracy(locData.accuracy) + // 此处设置开发者获取到的方向信息,顺时针0-360 + .direction(direction).latitude(locData.latitude).longitude(locData.longitude).build() + mBaiduMap!!.setMyLocationData(myLocData) + } + + /** + * 停止定位 + */ + private fun stopLocation() { + mLocationClient?.apply { + unRegisterLocationListener(mLocationListener) + stop() + } + } + + /** + * 根据轨迹获取地图缩放比例 + */ + fun getDistanceZoom(max: LatLng, min: LatLng): Float { + var jl = DistanceUtil.getDistance(max, min) + if (jl < 1) { + jl = 50.0 + } + LogUtil.e("最大和最小间经纬度直线距离 = $jl") +// var i = 0 +// while (i < scale.size - 2) { +// if (scale[i] < jl && scale[i + 1] >= jl) { +// break +// } +// i++ +// } + return getDistanceZoom(jl) + } + + /** + * 根据显示距离获取缩放地图等级 + */ + fun getDistanceZoom(distance: Double): Float { + val scaleOld = intArrayOf( + 1, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 25000, + 50000, + 100000, + 200000, + 500000, + 1000000, + 2000000, + 5000000 + ) + val scaleNew = intArrayOf( + 5, + 10, + 20, + 50, + 100, + 200, + 500, + 1000, + 2000, + 5000, + 10000, + 20000, + 25000, + 50000, + 100000, + 200000, + 500000, + 1000000 + ) + val scale = scaleNew + var jl = distance + if (jl < 5) { + jl = 50.0 + } + var i = 0 + while (i < scale.size - 2) { + if (scale[i] < jl && scale[i + 1] >= jl) { + break + } + i++ + } + LogUtil.e("根据显示距离获取缩放地图等级 = $jl,zoom=${21f - i}") + return 21f - i + } + + /** + * 定位成功返回 + */ + open fun locationSuccess(location: BDLocation?) {} + + /** + * 地图加载完毕 + */ + abstract fun onMapLoadOk(baiduMap: BaiduMap) + + /** + * 宠物位置刷新完成 + */ + open fun onRefreshPetCurrentLocationOk(baiduMap: BaiduMap) {} + + override fun onDestroy() { + super.onDestroy() + mSensorManager?.unregisterListener(mSensorEventListener) + mPetHeadIconBitmap?.recycle() + stopLocation() + //在activity执行onDestroy时执行mMapView.onDestroy(),实现地图生命周期管理 + mMapView.onDestroy() + } +} diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/FencesAddEditBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/FencesAddEditBaiduMapFragment.kt new file mode 100644 index 0000000..1f7a8c1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/FencesAddEditBaiduMapFragment.kt @@ -0,0 +1,495 @@ +package com.abbidot.tracker.ui.fragment.map.baidumap + +import android.content.Context +import android.graphics.Point +import android.view.MotionEvent +import android.view.View +import androidx.core.view.isVisible +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.common.map.FencesAddEditMapCommon +import com.abbidot.tracker.util.LonAndLatUtil +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.widget.FencesCircleView +import com.abbidot.tracker.widget.FencesPolygonView +import com.abbidot.tracker.widget.FencesRectView +import com.baidu.location.BDLocation +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.map.MapStatus +import com.baidu.mapapi.model.LatLng +import kotlin.math.pow + +/** + *Created by .yzq on 2024/5/15/015. + * @link + * @description: + */ +class FencesAddEditBaiduMapFragment : BaseBaiduMapFragment() { + + private lateinit var mFencesBean: FencesBean + private var isEditFences = false + private lateinit var mFencesCircleView: FencesCircleView + private lateinit var mFencesRectView: FencesRectView + private lateinit var mFencesPolygonView: FencesPolygonView + private var mOnActivityCallBack: FencesAddEditMapCommon.OnActivityCallBack? = null + + private lateinit var mMapLoadOk: () -> Unit + + companion object { + @JvmStatic + fun newInstance( + context: Context, + fencesBean: FencesBean, + fencesCircleView: FencesCircleView, + fencesRectView: FencesRectView, + fencesPolygonView: FencesPolygonView, + isEdit: Boolean, + onActivityCallBack: FencesAddEditMapCommon.OnActivityCallBack?, + mapLoadOk: () -> Unit + ) = FencesAddEditBaiduMapFragment().apply { + mContext = context + mFencesBean = fencesBean + isEditFences = isEdit + mFencesCircleView = fencesCircleView + mFencesRectView = fencesRectView + mFencesPolygonView = fencesPolygonView + mOnActivityCallBack = onActivityCallBack + mMapLoadOk = mapLoadOk + } + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { + + mBaiduMap?.apply { + + setOnMapLongClickListener { + resetFencesViewCentre(it) + mapOkAndDistancePointLatLng(10) + } + + setOnMapGestureListener(object : BaiduMap.onMapGestureListener { + override fun onMapScroll(p0: Point?, p1: Point?, p2: MapStatus?): Boolean { + //平移 + mFencesBean.let { + resetFencesViewCentre( + getBaiduMapLatLng( + it.latitudeCenter, it.longitudeCenter + ) + ) + } + return false + } + + override fun onMapDoubleTouch(p0: Point?, p1: MapStatus?): Boolean { + //双击放大 + LogUtil.e("onMapDoubleTouch") + return false + } + + override fun onMapTwoClick(p0: Point?, p1: Point?, p2: MapStatus?): Boolean { + //双指点击缩小 + LogUtil.e("onMapTwoClick") + return false + } + + override fun onMapKneading(p0: Point?, p1: Point?, p2: MapStatus?): Boolean { + LogUtil.e("onMapKneading") + //监听地图放大缩小操作 + if (getBaiduMapZoom() != mapStatus.zoom) { + setBaiduMapZoom(mapStatus.zoom) + resetFencesViewSize() + + mFencesBean.let { + resetFencesViewCentre( + getBaiduMapLatLng( + it.latitudeCenter, it.longitudeCenter + ) + ) + } + } + return false + } + + override fun onMapOverLooking(p0: Point?, p1: Point?, p2: MapStatus?): Boolean { + LogUtil.e("onMapOverLooking") + return false + } + + override fun onMapFling( + p0: MotionEvent?, p1: Float, p2: Float, p3: MapStatus? + ): Boolean { + //惯性抛出手势 + LogUtil.e("onMapFling1") + return false + } + + override fun onMapStatusChangeFinish(p0: MapStatus?) { + LogUtil.e("onMapStatusChangeFinish") + } + }) + } + + startLocation() + + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + override fun locationSuccess(location: BDLocation?) { + if (isEditFences) { + mFencesBean.apply { + moveCameraLocation( + latitudeCenter, longitudeCenter + ) + } + + //延时执行,防止围栏没画好/地图摄像头移动中 + mFencesCircleView.postDelayed({ + restoreEditFences() + }, 500) + + return + } + + + location?.apply { + //定位返回的经纬度已经是转换好的GCJ02坐标,需要转换为gps原生坐标 + val convertLonLat = LonAndLatUtil.convertFromGCJ02ToWGS84(latitude, longitude) + mFencesBean.latitudeCenter = convertLonLat[0] + mFencesBean.longitudeCenter = convertLonLat[1] + moveCameraLocation(latitude, longitude, false) + + mapOkAndDistancePointLatLng() + } + } + + /** + * 地图准备好计算距离 + */ + fun mapOkAndDistancePointLatLng(delayMillis: Long = 500) { + //延时计算距离,防止围栏没画好/地图摄像头移动中 + mFencesCircleView.postDelayed({ + val arrayOfDistance = getLatLngAndCalDistance() + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + }, delayMillis) + } + + /** + * 还原围栏 + */ + private fun restoreEditFences() { + var arrayOfDistance = arrayOf("0") + mFencesBean.apply { + when (mFencesBean.fenceShapeType) { + ConstantInt.CircleShapeType -> { + val radiusPx = getCircleRadiusPx(radius.toDouble()) + mFencesCircleView.let { + it.visibility = View.VISIBLE + it.setCircleRadiusPx(radiusPx.toFloat()) + arrayOfDistance = arrayOf("$radius") + } + } + + ConstantInt.RectangleShapeType -> { + restoreRectFences() + arrayOfDistance = arrayOf("$longDistance", "$shortDistance") + } + + ConstantInt.PolygonShapeType -> { + restorePolygonFences() + arrayOfDistance = arrayOf("0") + } + } + + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + } + } + + /** + * 重新设置围栏控件的中心点位置 + */ + fun resetFencesViewCentre(centreLatLng: LatLng) { + mBaiduMap?.projection?.toScreenLocation(centreLatLng)?.apply { + LogUtil.e("ddddssddfsdf$this") + if (mFencesCircleView.isVisible) { + mFencesCircleView.setCentreXY(this) + } else if (mFencesRectView.isVisible) { + mFencesRectView.setCentreXY(this) + } else if (mFencesPolygonView.isVisible) { + mFencesPolygonView.setCentreXY(this) + } + } + } + + /** + * 重新设置围栏控件的大小 + */ + private fun resetFencesViewSize() { + if (mFencesCircleView.isVisible) { + if (mFencesBean.radius <= 0.0) return + val radiusPx = zoomToCircleRadiusPx(mFencesBean.radius.toDouble()) + mFencesCircleView.setCircleRadiusPx(radiusPx.toFloat()) + } else if (mFencesRectView.isVisible) { + restoreRectFences() + } else if (mFencesPolygonView.isVisible) { + restorePolygonFences() + } + } + + private fun getLatLngAndCalDistance(): Array { + // 经纬度转换为手机坐标点 +// val x = mBaiduMap!!.projection.toScreenLocation(it).x + return when (mFencesBean.fenceShapeType) { + ConstantInt.CircleShapeType -> { + //手机坐标点转换为经纬度 + val centreLatLng = mFencesCircleView.getCircleCentrePoint() + .let { mBaiduMap!!.projection.fromScreenLocation(it) } + val endLatLng = mFencesCircleView.getCircleLineEndPoint() + .let { mBaiduMap!!.projection.fromScreenLocation(it) } + // mEndLatLng = endLatLng + LogUtil.e("mBaiduMap,getLatLngAndCalDistance 监听回调") + LogUtil.e("startLatLng=$centreLatLng,endLatLng=$endLatLng") + val distance = Util.measureLatLonDistance( + centreLatLng.latitude, + centreLatLng.longitude, + endLatLng.latitude, + endLatLng.longitude + ) + //把输出的GCJ02坐标转换为gps原生坐标WGS84 + val gpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + centreLatLng.latitude, centreLatLng.longitude + ) + mFencesBean.apply { + latitudeCenter = gpsWGS84[0] + longitudeCenter = gpsWGS84[1] + } + arrayOf( + Utils.formatDecimal(distance, 2) + ) + } + + ConstantInt.RectangleShapeType -> { + val centreLatLng = mFencesRectView.getRectCentrePoint() + .let { mBaiduMap!!.projection.fromScreenLocation(it) } + val rectALatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_A).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val rectBLatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_B).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val rectCLatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_C).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val rectDLatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_D).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val rectWidthDistance = Util.measureLatLonDistance( + rectALatLng.latitude, + rectALatLng.longitude, + rectBLatLng.latitude, + rectBLatLng.longitude + ) + val rectHeightDistance = Util.measureLatLonDistance( + rectBLatLng.latitude, + rectBLatLng.longitude, + rectCLatLng.latitude, + rectCLatLng.longitude + ) + + //把输出的GCJ02坐标转换为gps原生坐标WGS84 + val centreGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + centreLatLng.latitude, centreLatLng.longitude + ) + val rectAGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + rectALatLng.latitude, rectALatLng.longitude + ) + val rectBGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + rectBLatLng.latitude, rectBLatLng.longitude + ) + val rectCGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + rectCLatLng.latitude, rectCLatLng.longitude + ) + val rectDGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + rectDLatLng.latitude, rectDLatLng.longitude + ) + mFencesBean.apply { + latitudeCenter = centreGpsWGS84[0] + longitudeCenter = centreGpsWGS84[1] + latitudeA = rectAGpsWGS84[0] + longitudeA = rectAGpsWGS84[1] + latitudeB = rectBGpsWGS84[0] + longitudeB = rectBGpsWGS84[1] + latitudeC = rectCGpsWGS84[0] + longitudeC = rectCGpsWGS84[1] + latitudeD = rectDGpsWGS84[0] + longitudeD = rectDGpsWGS84[1] + } + //长度和高度 + arrayOf( + Utils.formatDecimal(rectWidthDistance, 2), + Utils.formatDecimal(rectHeightDistance, 2) + ) + } + + else -> { + val centreLatLng = mFencesPolygonView.getPolygonCentrePoint() + .let { mBaiduMap!!.projection.fromScreenLocation(it) } + val polygonALatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_A).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val polygonBLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_B).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val polygonCLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_C).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val polygonDLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_D).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val polygonELatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_E).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + val polygonFLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_F).let { + mBaiduMap!!.projection.fromScreenLocation(it) + } + + //把输出的GCJ02坐标转换为gps原生坐标WGS84 + val centreGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + centreLatLng.latitude, centreLatLng.longitude + ) + val polygonAGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + polygonALatLng.latitude, polygonALatLng.longitude + ) + val polygonBGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + polygonBLatLng.latitude, polygonBLatLng.longitude + ) + val polygonCGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + polygonCLatLng.latitude, polygonCLatLng.longitude + ) + val polygonDGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + polygonDLatLng.latitude, polygonDLatLng.longitude + ) + val polygonEGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + polygonELatLng.latitude, polygonELatLng.longitude + ) + val polygonFGpsWGS84 = LonAndLatUtil.convertFromGCJ02ToWGS84( + polygonFLatLng.latitude, polygonFLatLng.longitude + ) + mFencesBean.apply { + latitudeCenter = centreGpsWGS84[0] + longitudeCenter = centreGpsWGS84[1] + latitudeA = polygonAGpsWGS84[0] + longitudeA = polygonAGpsWGS84[1] + latitudeB = polygonBGpsWGS84[0] + longitudeB = polygonBGpsWGS84[1] + latitudeC = polygonCGpsWGS84[0] + longitudeC = polygonCGpsWGS84[1] + latitudeD = polygonDGpsWGS84[0] + longitudeD = polygonDGpsWGS84[1] + latitudeE = polygonEGpsWGS84[0] + longitudeE = polygonEGpsWGS84[1] + latitudeF = polygonFGpsWGS84[0] + longitudeF = polygonFGpsWGS84[1] + } + //多边形,不需要计算距离 + arrayOf("0") + } + } + } + + /** + * 还原矩形围栏大小 + */ + private fun restoreRectFences() { + mFencesBean.apply { + mFencesRectView.let { + it.visibility = View.VISIBLE + mBaiduMap!!.projection.apply { + val pointCentre = + toScreenLocation(getBaiduMapLatLng(latitudeCenter, longitudeCenter)) + val pointA = toScreenLocation( + getBaiduMapLatLng(latitudeA, longitudeA) + ) + val pointB = toScreenLocation( + getBaiduMapLatLng(latitudeB, longitudeB) + ) + val pointC = toScreenLocation( + getBaiduMapLatLng(latitudeC, longitudeC) + ) + val pointD = toScreenLocation( + getBaiduMapLatLng(latitudeD, longitudeD) + ) + it.setRectABCDXYPoint( + pointCentre, pointA, pointB, pointC, pointD + ) + } + } + } + } + + /** + * 还原多边形围栏大小 + */ + private fun restorePolygonFences() { + mFencesBean.apply { + mFencesPolygonView.let { + it.visibility = View.VISIBLE + mBaiduMap!!.projection.apply { + val pointCentre = + toScreenLocation(getBaiduMapLatLng(latitudeCenter, longitudeCenter)) + val pointA = toScreenLocation( + getBaiduMapLatLng(latitudeA, longitudeA) + ) + val pointB = toScreenLocation( + getBaiduMapLatLng(latitudeB, longitudeB) + ) + val pointC = toScreenLocation( + getBaiduMapLatLng(latitudeC, longitudeC) + ) + val pointD = toScreenLocation( + getBaiduMapLatLng(latitudeD, longitudeD) + ) + val pointE = toScreenLocation( + getBaiduMapLatLng(latitudeE, longitudeE) + ) + val pointF = toScreenLocation( + getBaiduMapLatLng(latitudeF, longitudeF) + ) + it.setPolygonABCDEFPoint( + pointCentre, pointA, pointB, pointC, pointD, pointE, pointF + ) + } + } + } + } + + /** + * 根据地图显示级别,计算比例尺的一像素对应几米 + */ + private fun getCircleRadiusPx(distanceRadius: Double): Double { + val zoom = getDistanceZoom(distanceRadius) + //一像素对应几米 + val pxDis = 2.0.pow(17.0 - zoom) + //计算现在半径有多少像素 + return distanceRadius / pxDis + } + + /** + * 根据地图显示级别,计算比例尺的一像素对应几米 + */ + private fun zoomToCircleRadiusPx(distanceRadius: Double): Double { + val zoom = getBaiduMapZoom() + //一像素对应几米 + val pxDis = 2.0.pow(17.0 - zoom) + //计算现在半径有多少像素 + return distanceRadius / pxDis + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/HistoryDataBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/HistoryDataBaiduMapFragment.kt new file mode 100644 index 0000000..2437dce --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/HistoryDataBaiduMapFragment.kt @@ -0,0 +1,243 @@ +package com.abbidot.tracker.ui.fragment.map.baidumap + +import android.content.Context +import android.graphics.Typeface +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.fragment.map.googlemap.GoogleBitmapHelper +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.map.BitmapDescriptor +import com.baidu.mapapi.map.BitmapDescriptorFactory +import com.baidu.mapapi.map.MarkerOptions +import com.baidu.mapapi.model.LatLng +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +/** + *Created by .yzq on 2022/6/28/028. + * @link + * @description: + */ +class HistoryDataBaiduMapFragment : BaseBaiduMapFragment() { + + // private val mScreenshotsViewModel: ScreenshotsViewModel by viewModels() + private lateinit var mGeoCoderViewModel: GeoCoderViewModel + + // private lateinit var mHistoryDataActivity: HistoryDataActivity + private var mLatLngList = mutableListOf() + + private var mPetIconDescriptor: BitmapDescriptor? = null + + private lateinit var mMarkerOptions: MarkerOptions + + //字体 + private var mTypeface = Typeface.DEFAULT + + //是否地图截屏 + private var isMapSnapshot = true + + private lateinit var mMapLoadOk: () -> Unit + + companion object { + @JvmStatic + fun newInstance( + context: Context, geoCoderViewModel: GeoCoderViewModel, mapLoadOk: () -> Unit + ) = HistoryDataBaiduMapFragment().apply { + mContext = context + mGeoCoderViewModel = geoCoderViewModel + mMapLoadOk = mapLoadOk + } + } + + override fun initData() { +// mHistoryDataActivity = mActivity as HistoryDataActivity + super.initData() + + mTypeface = + ViewUtil.instance.setTypeface(mContext!!, getString(R.string.number_din_cond_font)) + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { +// val today = Utils.getCurrentTime(Utils.DATE_FORMAT_PATTERN) +// mHistoryDataActivity.mDataDetailViewModel.getHistoryByDay( +// mHistoryDataActivity, mHistoryDataActivity.mPet.deviceId, today +// ) + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + /** + * 设置经纬度数据 + */ + fun setLatLngData(data: MutableList) { + mBaiduMap?.clear() + + if (data.size == 0) return + + var maxLat = data[0].latitude + var maxLon = data[0].longitude + var minLat = data[0].latitude + var minLon = data[0].longitude + + data.forEach { historyDataBean -> + historyDataBean.apply { + if (maxLat < latitude) maxLat = latitude + if (maxLon < longitude) maxLon = longitude + if (minLat > latitude) minLat = latitude + if (minLon > longitude) minLon = longitude + + val convertLonLat = getBaiduMapLatLng(latitude, longitude) + mLatLngList.add(convertLonLat) + } + } + + val zoom = getDistanceZoom(LatLng(minLat, minLon), LatLng(maxLat, maxLon)) + LogUtil.e("百度地图zoom==$zoom") + setBaiduMapZoom(zoom + 1) + + val maxIndex = data.size - 1 + mGeoCoderViewModel.getLatLonAddress( + mLatLngList[maxIndex].latitude, mLatLngList[maxIndex].longitude, false + ) + val type = MMKVUtil.getInt(MMKVKey.LineSelectPosition, ConstantInt.Line) + if (type == ConstantInt.Line) { + addSetLine() + } else { + addSetNumber() + } + } + + + /** + * 滑动改变 + */ + fun slideChanged(progress: Int) { + mMarkerOptions = MarkerOptions().position(mLatLngList[progress]).icon(mPetIconDescriptor) + mMarker?.remove() + mMarker = mBaiduMap!!.addOverlay(mMarkerOptions) + } + + /** + * 停止滑动 + */ + fun slideStopChanged(progress: Int) { + if (mLatLngList.size > 0) { + mGeoCoderViewModel.getLatLonAddress( + mLatLngList[progress].latitude, mLatLngList[progress].longitude, false + ) + } + } + + override fun clearAllMarker() { + super.clearAllMarker() + mLatLngList.clear() + } + + /** + * 画线 + */ + fun addSetLine() { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + getPetIcon() + } + val size = mLatLngList.size + if (size == 0) return@launch + mBaiduMap?.let { + it.clear() + moveCameraLocation( + mLatLngList[size - 1].latitude, + mLatLngList[size - 1].longitude, + needConvertGCJ02 = false + ) + addPolyline(it, mLatLngList, false, startCapImageRes = 0) + + mMarkerOptions = + MarkerOptions().position(mLatLngList[size - 1]).icon(mPetIconDescriptor) + mMarker?.remove() + mMarker = it.addOverlay(mMarkerOptions) + + mapSnapshot() + } + } + } + + /** + * 设置显示数字 + */ + fun addSetNumber() { + lifecycleScope.launch { + withContext(Dispatchers.IO) { + getPetIcon() + } + val size = mLatLngList.size + if (size == 0) return@launch + mBaiduMap?.let { + it.clear() + + moveCameraLocation( + mLatLngList[size - 1].latitude, + mLatLngList[size - 1].longitude, + needConvertGCJ02 = false + ) + + for (i in 0 until mLatLngList.size) { + it.addOverlay( + MarkerOptions().position(mLatLngList[i]).icon( + BitmapDescriptorFactory.fromBitmap( + GoogleBitmapHelper.imageUpNumberBitmap( + mContext!!, + R.drawable.pic_history_location, + (i + 1).toString(), + mTypeface + ) + ) + ) + ) + } + mMarkerOptions = + MarkerOptions().position(mLatLngList[size - 1]).icon(mPetIconDescriptor) + mMarker?.remove() + mMarker = it.addOverlay(mMarkerOptions) + + mapSnapshot() + } + } + } + + /** + * 获取宠物头像 + */ + private suspend fun getPetIcon() { + if (null == mPetIconDescriptor) { +// val bitmap = ImageUtil.getImageBitmapByUrl(mContext!!, mHistoryDataActivity.mPet.imgurl) +// mPetIconDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap) +// bitmap.recycle() + mPetIconDescriptor = BitmapDescriptorFactory.fromResource(R.drawable.pic_avatar_df) + } + } + + /** + * 截屏 + */ + private fun mapSnapshot() { + if (isMapSnapshot) { + isMapSnapshot = false +// mHistoryDataActivity.mTopBar?.postDelayed({ +// if (mHistoryDataActivity.isFrontRunning) { +// mScreenshotsViewModel.getHistoryDataBaiduMapSnapshotImage( +// mContext!!, mBaiduMap!!, mHistoryDataActivity.mPet.deviceId +// ) +// } +// }, 1000) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/HomeMapBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/HomeMapBaiduMapFragment.kt new file mode 100644 index 0000000..5c4ebdc --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/HomeMapBaiduMapFragment.kt @@ -0,0 +1,148 @@ +package com.abbidot.tracker.ui.fragment.map.baidumap + +import android.content.Context +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.model.LatLng + + +/** + *Created by .yzq on 2022/6/24/024. + * @link + * @description: + */ +class HomeMapBaiduMapFragment : BaseBaiduMapFragment() { + + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + //宠物当前位置信息 + var mDecPetAddressData: HistoryDataBean? = null + private var mMapDeviceBean: MapDeviceBean? = null + private lateinit var mMapLoadOk :() -> Unit + + //启动动画移动地图摄像机 + private var isMoveCamera = true + + //已经转换好的经纬度坐标 + private val mLatLngList = mutableListOf() + + /** + *是否显示信息窗(弹窗覆盖物InfoWindow) + */ + private var isShowInfoWindow = false + + companion object { + @JvmStatic + fun newInstance(context: Context,mapLoadOk: () -> Unit) = HomeMapBaiduMapFragment().apply { + mContext = context + mMapLoadOk = mapLoadOk + } + } + + fun setMapDeviceBean(mapDeviceBean: MapDeviceBean?) { + mMapDeviceBean = mapDeviceBean + } + + override fun liveDataObserve() { + //反地理编码成功返回 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(viewLifecycleOwner) { + //保存地理信息,方便传参给导航页面 + if (null == mDecPetAddressData) mDecPetAddressData = HistoryDataBean() + + var timeString = "" + mMapDeviceBean?.apply { +// timeString = String.format( +// getString(R.string.map_current_update_time), +// DateUtils.getRelativeTimeSpanString(updateTime * 1000) +// ) + timeString = Utils.formatTime(updateTime * 1000, Utils.DATE_FORMAT_PATTERN_EN6) + } + + mDecPetAddressData?.apply { + //更新位置时间 + dayTime = timeString + //详细位置 + address = it + } + showMarkerInfoWindow(mDecPetAddressData!!) + } + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { + //开启地图的定位图层 +// baiduMap.isMyLocationEnabled = true + startLocation(60, false) +// val locationConfiguration = +// MyLocationConfiguration(MyLocationConfiguration.LocationMode.NORMAL, true, null) +// //自定义精度圈边框颜色 +// baiduMap.setMyLocationConfiguration(locationConfiguration) + +// initFenceList() + +// mBaiduMap?.setOnMarkerClickListener { +// if (!isShowInfoWindow) { +// startReverseGeocode() +// } else { +// baiduMap.hideInfoWindow() +// } +// isShowInfoWindow = !isShowInfoWindow +// false +// } + + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + + /** + * 画线 + */ + fun addLine(lat: Double, lon: Double) { + val convertLonLat = getBaiduMapLatLng(lat, lon) + mLatLngList.add(convertLonLat) + addPolyline(mBaiduMap!!, mLatLngList, startCapImageRes = R.drawable.map_live_start) + refreshPetCurrentLocation( + convertLonLat, + needMoveCamera = isMoveCamera, + ) + isMoveCamera = false + } + + /** + * 计算用户和宠物的距离 + */ + fun getUserAndPetDistance(): Double { + var distance = 0.0 + mUserLatLng?.apply { + mPetLatLng?.let { + distance = + Util.measureLatLonDistance(latitude, longitude, it.latitude, it.longitude) + } + } + return distance + } + + /** + * 刚开始的反编译地理位置 + */ + private fun startReverseGeocode() { + mMapDeviceBean?.apply { + mGeoCoderViewModel.getLatLonAddress(latitude, longitude, false) + } + } + + + override fun onRefreshPetCurrentLocationOk(baiduMap: BaiduMap) { +// if (isShowInfoWindow) { +// isShowInfoWindow = false +// baiduMap.hideInfoWindow() +// } + startReverseGeocode() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/MapLiveBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/MapLiveBaiduMapFragment.kt new file mode 100644 index 0000000..d15e6a8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/MapLiveBaiduMapFragment.kt @@ -0,0 +1,72 @@ +package com.abbidot.tracker.ui.fragment.map.baidumap + +import android.content.Context +import com.abbidot.tracker.R +import com.abbidot.tracker.deprecated.ui.activity.map.MapLiveActivity +import com.baidu.mapapi.map.BaiduMap +import com.baidu.mapapi.model.LatLng + +/** + *Created by .yzq on 2022/6/28/028. + * @link + * @description: + */ +class MapLiveBaiduMapFragment : BaseBaiduMapFragment() { + + //启动动画移动地图摄像机 + private var isMoveCamera = true + + private lateinit var mMapLiveActivity: MapLiveActivity + + //已经转换好的经纬度坐标 + private lateinit var mLatLngList: MutableList + + //已经转换好的经纬度坐标 + private var mPetCurrentLatLng: LatLng? = null + + override fun initData() { + mLatLngList = mutableListOf() + mMapLiveActivity = mActivity as MapLiveActivity + super.initData() + } + + companion object { + @JvmStatic + fun newInstance(context: Context) = MapLiveBaiduMapFragment().apply { + mContext = context + } + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { + +// initFenceList() + + //先开始获取围栏数据 + mMapLiveActivity.mFencesManageViewModel.getFenceList( + mMapLiveActivity, mMapLiveActivity.mPet.deviceId + ) + } + + /** + * 刷新当前宠物位置 + */ + fun refreshPetCurrentLocation() { + mPetCurrentLatLng?.let { + refreshPetCurrentLocation(it.latitude, it.longitude, needConvertGCJ02 = false) + } + } + + /** + * 画线 + */ + fun addLine(lat: Double, lon: Double) { + val convertLonLat = getBaiduMapLatLng(lat, lon) + mPetCurrentLatLng = convertLonLat + mLatLngList.add(mPetCurrentLatLng!!) + addPolyline(mBaiduMap!!, mLatLngList, startCapImageRes = R.drawable.map_live_start) + refreshPetCurrentLocation( + convertLonLat, needMoveCamera = isMoveCamera, + ) + isMoveCamera = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/PreviewFencesBaiduMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/PreviewFencesBaiduMapFragment.kt new file mode 100644 index 0000000..d9ef7dd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/baidumap/PreviewFencesBaiduMapFragment.kt @@ -0,0 +1,33 @@ +package com.abbidot.tracker.ui.fragment.map.baidumap + +import android.content.Context +import com.baidu.location.BDLocation +import com.baidu.mapapi.map.BaiduMap + +/** + *Created by .yzq on 2024/5/15/015. + * @link + * @description: + */ +class PreviewFencesBaiduMapFragment : BaseBaiduMapFragment() { + + private lateinit var mMapLoadOk: () -> Unit + + companion object { + @JvmStatic + fun newInstance(context: Context, mapLoadOk: () -> Unit) = + PreviewFencesBaiduMapFragment().apply { + mContext = context + mMapLoadOk = mapLoadOk + } + } + + override fun onMapLoadOk(baiduMap: BaiduMap) { + startLocation() + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + override fun locationSuccess(location: BDLocation?) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/BaseGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/BaseGoogleMapFragment.kt new file mode 100644 index 0000000..8d39531 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/BaseGoogleMapFragment.kt @@ -0,0 +1,1104 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.Manifest +import android.annotation.SuppressLint +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.Bitmap +import android.location.Location +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.databinding.FragmentGoogleMapBinding +import com.abbidot.tracker.util.ImageUtil +import com.abbidot.tracker.util.LonAndLatUtil +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.widget.MapDeviceNetView +import com.abbidot.tracker.widget.MapMarkerInfoView +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.LocationServices +import com.google.android.gms.location.Priority +import com.google.android.gms.maps.CameraUpdate +import com.google.android.gms.maps.CameraUpdateFactory +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.GoogleMapOptions +import com.google.android.gms.maps.MapView +import com.google.android.gms.maps.OnMapReadyCallback +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.CameraPosition +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import com.google.android.gms.maps.model.CustomCap +import com.google.android.gms.maps.model.Dash +import com.google.android.gms.maps.model.Gap +import com.google.android.gms.maps.model.JointType +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.LatLngBounds +import com.google.android.gms.maps.model.Marker +import com.google.android.gms.maps.model.MarkerOptions +import com.google.android.gms.maps.model.PatternItem +import com.google.android.gms.maps.model.Polygon +import com.google.android.gms.maps.model.PolygonOptions +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import com.google.android.gms.maps.model.RoundCap +import com.google.android.gms.tasks.CancellationTokenSource +import com.google.android.gms.tasks.Task +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlinx.coroutines.launch + + +/** + * 谷歌地图 + */ +abstract class BaseGoogleMapFragment : + BaseFragment(FragmentGoogleMapBinding::inflate), OnMapReadyCallback { + + var mGoogleMap: GoogleMap? = null + private lateinit var mGoogleMapView: MapView + + /**地图样式类型:卫星/标准 + * GoogleMap.MAP_TYPE_NONE 不显示地图 + * GoogleMap.MAP_TYPE_NORMAL 标准普通地图 + * GoogleMap.MAP_TYPE_SATELLITE 卫星地图 + * GoogleMap.MAP_TYPE_TERRAIN 地形,地势 + * GoogleMap.MAP_TYPE_HYBRID 混合的 + */ +// private var mMapType = GoogleMap.MAP_TYPE_NORMAL + + private var mMapviewBundleKey = this.javaClass.simpleName + + //地图放大 + private var mGoogleMapZoom = 18f + + //地图摄像机移动动画时间 毫秒 + private val mAnimMoveDurationMs = 1000 + + //自定显示宠物头像地点信息图层 + var mMarker: Marker? = null + + //宠物大头针设置自定义头像 + private var mPetHeadIconBitmap: Bitmap? = null + + private var mMarkerInfoWindowAdapter: MarkerInfoWindowAdapter? = null + + //宠物头像地址 + var mPetHeadUrl = "" + + //头像为空,用默认类型的头像 + private var mPetType = ConstantInt.DogPetType + + //地图是否加载准备好了 + var isMapLoadOk = false + + //用户位置经纬度 + var mUserLatLng: LatLng? = null + + //宠物位置经纬度 + var mPetLatLng: LatLng? = null + + //第一次执行定位回调,方便只刷新用户位置 + private var isLocationSuccessCallback = true + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + mViewBinding = this.inflater(inflater, container, false) + LogUtil.e("${mMapviewBundleKey}===GoogleMapFragment--->onCreateView---$container") + + var mapViewBundle: Bundle? = null + if (null != savedInstanceState) { + mapViewBundle = savedInstanceState.getBundle(mMapviewBundleKey) + } +// mGoogleMapView = mViewBinding.googleMapView + + //指定地图的初始照相机位置(指定 null 以使用 默认摄像机位置) + val options = GoogleMapOptions().apply { + //默认华盛顿经纬度 + val lat = MMKVUtil.getDouble(MMKVKey.MapShowDefaultLat, 38.913611) + val lon = MMKVUtil.getDouble(MMKVKey.MapShowDefaultLon, -77.013222) + val position = CameraPosition.fromLatLngZoom(LatLng(lat, lon), 4f) + camera(position) + } + mGoogleMapView = MapView(this.requireActivity(), options) + mGoogleMapView.onCreate(mapViewBundle) + mViewBinding.root.addView(mGoogleMapView) + + return mViewBinding.root + } + + override fun initData() { + super.initData() + mGoogleMapView.getMapAsync(this) + } + + override fun onResume() { + super.onResume() + mGoogleMapView.onResume() + + mGoogleMap?.apply { + val mapTypeSp = Util.getMapTypeSp() + if (mapTypeSp == ConstantInt.Type0 && mapType != GoogleMap.MAP_TYPE_NORMAL) { + mapType = GoogleMap.MAP_TYPE_NORMAL + } else if (mapTypeSp == ConstantInt.Type1 && mapType != GoogleMap.MAP_TYPE_HYBRID) { + mapType = GoogleMap.MAP_TYPE_HYBRID + } + } + } + + override fun onStart() { + super.onStart() + mGoogleMapView.onStart() + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + + var mapViewBundle = outState.getBundle(mMapviewBundleKey) + if (null == mapViewBundle) { + mapViewBundle = Bundle() + outState.putBundle(mMapviewBundleKey, mapViewBundle) + } + mGoogleMapView.onSaveInstanceState(mapViewBundle) + } + + override fun onPause() { + super.onPause() + mGoogleMapView.onPause() + } + + + override fun onLowMemory() { + super.onLowMemory() + mGoogleMapView.onLowMemory() + } + + /** + * 设置地图样式 + */ + private fun setGoogleMapType() { + mGoogleMap?.apply { + //地图样式 + val mapTypeSp = Util.getMapTypeSp() + if (mapTypeSp == ConstantInt.Type0) { + mapType = GoogleMap.MAP_TYPE_NORMAL + } else if (mapTypeSp == ConstantInt.Type1) { + mapType = GoogleMap.MAP_TYPE_HYBRID + } + } + } + + /** + * 设置地图显示级别 + */ + fun setGoogleMapZoom(zoom: Float) { + mGoogleMapZoom = zoom + } + + /** + * 获取地图显示级别 + */ + fun getGoogleMapZoom() = mGoogleMapZoom + + /** + * 卫星标准地图模式切换 + */ + fun switchSatelliteAndNormalMapType() { + setGoogleMapType() + } + + /** + * 编辑围栏还原摄像机 + */ + private var mCameraUpdate: CameraUpdate? = null + + fun setCameraUpdateNull() { + //只有刚进入页面才会使用,只用一次,方便搜索地址移动摄像头 + mCameraUpdate = null + } + + /** + * 设置缩放等级 + */ + fun setZoomTo(zoomLevel: Float) { + mGoogleMap?.apply { + mGoogleMapZoom *= zoomLevel + moveCamera(CameraUpdateFactory.zoomTo(mGoogleMapZoom)) + } + } + + /** + * 根据CameraUpdate移动摄像机 + */ + fun moveCameraUpdate(lat: Double, lon: Double, isAnimMoveCamera: Boolean = true) { + if (null == mCameraUpdate) { + moveCameraLocation(lat, lon, isAnimMoveCamera) + LogUtil.e("moveCameraUpdate---------->CameraUpdate=null,摄像机移动靠moveCameraLocation经纬度") + return + } + mGoogleMap?.apply { + if (isAnimMoveCamera) { + animateCamera(mCameraUpdate!!, mAnimMoveDurationMs, null) + } else { + moveCamera(mCameraUpdate!!) + } + } + + } + + /** + * 根据CameraUpdate移动摄像机 + */ + fun moveCameraUpdate(latLng: LatLng, isAnimMoveCamera: Boolean = true) { + moveCameraUpdate(latLng.latitude, latLng.longitude, isAnimMoveCamera) + } + + /** + * 移动到某个位置 为中心点 + */ + fun moveCameraLocation(latLng: LatLng, isAnimMoveCamera: Boolean = true) { + mGoogleMap?.apply { + //animateCamera使用动画 + if (isAnimMoveCamera) { + animateCamera( + CameraUpdateFactory.newLatLngZoom(latLng, mGoogleMapZoom), + mAnimMoveDurationMs, + null + ) + } else { + moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, mGoogleMapZoom)) + } + } + } + + /** + * 移动到某个位置 为中心点 + */ + fun moveCameraLocation(lat: Double, lon: Double, isAnimMoveCamera: Boolean = true) { + moveCameraLocation(LatLng(lat, lon), isAnimMoveCamera) + } + + /** + * 切换用户和宠物的位置居中显示,移动摄像头中心 + */ + fun switchShowLocation(@ConstantInt type: Int) { + val latLng = if (type == ConstantInt.PetLocationType) { + //刷新下用户当前的位置 + getLastLocation() + mPetLatLng + } else mUserLatLng + latLng?.apply { + moveCameraLocation(this) + } + } + + /** + * 移动到某个位置 为中心点,animateCamera使用动画 + */ + fun moveAnimateCameraLocation( + latLng: LatLng, moveCameraCallback: GoogleMap.CancelableCallback? + ) { + //1秒动画执行时间 + mGoogleMap?.apply { + animateCamera( + CameraUpdateFactory.newLatLngZoom(latLng, mGoogleMapZoom), + mAnimMoveDurationMs, + moveCameraCallback + ) + } + } + + /** + * 设置大头针宠物头像 + */ + fun setPetHeadIconUrl(petHeadUrl: String, petType: Int) { + mPetHeadUrl = petHeadUrl + mPetType = petType + mPetHeadIconBitmap?.recycle() + mPetHeadIconBitmap = null + } + + /** + * 设置宠物头像地图Marker + */ + private suspend fun setPetHeadMarker( + latLng: LatLng, headBgResId: Int = R.drawable.pic_map_gps_avatar + ) { + + if (null == mPetHeadIconBitmap) { + //设置头像 + mPetHeadIconBitmap = + GoogleBitmapHelper.headToBitmap(mContext!!, headBgResId, mPetHeadUrl, mPetType) + } + val markerOptions = MarkerOptions().position(latLng) + markerOptions.icon(BitmapDescriptorFactory.fromBitmap(mPetHeadIconBitmap!!)) + + mMarker?.remove() + mGoogleMap?.apply { + mMarker = addMarker(markerOptions) + } + } + + /** + * 设置宠物当前位置经纬度 + */ + fun setPetCurrentLocation(petHeadUrl: String, lat: Double, lon: Double, petType: Int) { + lifecycleScope.launch(lifecycleScope.coroutineContext) { + setPetHeadIconUrl(petHeadUrl, petType) + mPetLatLng = LatLng(lat, lon).apply { + setPetHeadMarker(this) + } + } + } + + /** + * 当前宠物位置刷新 + * @param needMoveCamera 摄像机是否摄需要移动当前经纬度,地图中心 + * @param isAnimMoveCamera 动画移动地图摄像头 + */ + fun refreshPetCurrentLocation( + latLng: LatLng, + needMoveCamera: Boolean = true, + isAnimMoveCamera: Boolean = true, + headBgResId: Int = R.drawable.pic_map_gps_avatar + ) { + mPetLatLng = latLng + + if (null != mGoogleMap) { + + lifecycleScope.launch(lifecycleScope.coroutineContext) { + + setPetHeadMarker(latLng, headBgResId) + + //是否需要移动到地图中心点 + if (needMoveCamera) { + //animateCamera使用动画 + moveCameraLocation(latLng, isAnimMoveCamera) + } + + //刷新当前位置ok,延时等待移动地图摄像头动画 + if (isAnimMoveCamera) { + mGoogleMapView.postDelayed({ + onRefreshPetCurrentLocationOk(mGoogleMap!!) + }, 1000) + } else { + onRefreshPetCurrentLocationOk(mGoogleMap!!) + } + } + } + } + + /** + * 清空所有图层 + */ + open fun clearAllMarker() { + mGoogleMap?.clear() + clearFenceList() + } + + //在地图上画圆形围栏和多边形围栏集合 + private var mCircleFenceList: MutableList? = null + private var mPolygonFenceList: MutableList? = null + +// fun initFenceList() { +// if (null == mCircleFenceList) mCircleFenceList = mutableListOf() +// if (null == mRectFenceList) mRectFenceList = mutableListOf() +// } + + /** + * 地图上画形状,虚线风格 + */ + fun getDashedPatternStyle(patternLength: Float = 10f): List { + //自定义描边图案 https://developers.google.cn/maps/documentation/android-sdk/shapes#stroke_color + return listOf( + //图案设置成了重复的点序列,后跟一个长度为 20 像素的间隙、 + // 一个长度为 30 像素的短划线,以及另一个 20 像素的间隙。 +// Dot(), + Gap(patternLength), Dash(patternLength) + ) + } + + /** + * 地图添加图片 + * @param anchorCenter 图片居中显示 + */ + fun addImageMarker( + latLng: LatLng, + imageTypeResId: Int, + anchorCenter: Boolean = true, + isDraggable: Boolean = false + ): Marker? { + ImageUtil.getBitmapFromDrawableAndSvg(mContext!!, imageTypeResId)?.apply { + val imageTypeMarker = + MarkerOptions().position(latLng).icon(BitmapDescriptorFactory.fromBitmap(this)) + imageTypeMarker.let { + //图像上将置于标记的 LatLng 位置的点。该点默认为图像底部的中间位置。 + if (anchorCenter) it.anchor(it.anchorU, it.anchorV / 2) + if (isDraggable) it.draggable(isDraggable) + } + return mGoogleMap?.addMarker(imageTypeMarker) + } + return null + } + + fun addImageMarker( + lat: Double, + lon: Double, + imageTypeResId: Int, + anchorCenter: Boolean = true, + isDraggable: Boolean = false + ): Marker? { + return addImageMarker(LatLng(lat, lon), imageTypeResId, anchorCenter, isDraggable) + } + + /** + * Adds a [Circle] around the provided + * 地图上画一个圆形围栏 + *@param isDashedPattern 是否是虚线 + */ + fun addCircleFence( + center: LatLng, + radius: Double = 700.0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + if (null == mGoogleMap) return + + val circleFence = mGoogleMap!!.addCircle( + CircleOptions().center(center).radius(radius).strokeWidth(AppUtils.dpToPx(1).toFloat()) + .fillColor(fillColor).strokeColor(strokeColor) + ) + + if (isDashedPattern) { + circleFence.strokePattern = getDashedPatternStyle() + } + + if (null == mCircleFenceList) mCircleFenceList = mutableListOf() + mCircleFenceList?.add(circleFence) + } + + /** + * + * 地图上画一个圆形虚线围栏 + */ + fun addCircleImageFence( + center: LatLng, + radius: Double = 700.0, + imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashed: Boolean = true + ) { + if (imageTypeResId > 0) addImageMarker(center, imageTypeResId) + addCircleFence(center, radius, strokeColor, fillColor, isDashed) + } + + /** + * 地图上画多边形 + * @param isDashedPattern 是否是虚线 + */ + private fun mapDrawPolygon( + points: MutableList, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + mGoogleMap?.apply { + //构造PolygonOptions + val polygonOptions = + PolygonOptions().addAll(points).strokeWidth(AppUtils.dpToPx(1).toFloat()) + .fillColor(fillColor).strokeColor(strokeColor) + + if (isDashedPattern) { + polygonOptions.strokePattern(getDashedPatternStyle()) + } + + val rectFence = addPolygon(polygonOptions) + + if (null == mPolygonFenceList) mPolygonFenceList = mutableListOf() + mPolygonFenceList?.add(rectFence) + } + } + + /** + * 地图上画一个矩形围栏 + */ + fun addRectFence( + fencesBean: FencesBean, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + if (null == mGoogleMap) return + + val points = mutableListOf() + fencesBean.apply { + points.add(LatLng(latitudeA, longitudeA)) + points.add(LatLng(latitudeB, longitudeB)) + points.add(LatLng(latitudeC, longitudeC)) + points.add(LatLng(latitudeD, longitudeD)) + } + mapDrawPolygon(points, strokeColor, fillColor, isDashedPattern) + } + + /** + * + * 地图上画一个矩形虚线有中心图片围栏 + */ + fun addRectImageFence( + fencesBean: FencesBean, + imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = true + ) { + if (imageTypeResId > 0) addImageMarker( + LatLng( + fencesBean.latitudeCenter, fencesBean.longitudeCenter + ), imageTypeResId + ) + addRectFence(fencesBean, strokeColor, fillColor, isDashedPattern) + } + + /** + * + * 地图上画一个正在预览的矩形虚线围栏 + */ + fun addRectImageEditFence( + fencesBean: FencesBean, imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + ) { + fencesBean.apply { + addRectImageFence(this, imageTypeResId, strokeColor, fillColor) + addImageMarker(LatLng(latitudeA, longitudeA), R.drawable.icon_fence_dot_svg) + addImageMarker(LatLng(latitudeB, longitudeB), R.drawable.icon_fence_dot_svg) + addImageMarker(LatLng(latitudeC, longitudeC), R.drawable.icon_fence_dot_svg) + addImageMarker(LatLng(latitudeD, longitudeD), R.drawable.icon_fence_dot_svg) + } + } + + /** + * 地图上画一个多边形围栏 + */ + fun addPolygonFence( + fencesBean: FencesBean, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = false + ) { + if (null == mGoogleMap) return + + val points = mutableListOf() + fencesBean.apply { + points.add(LatLng(latitudeA, longitudeA)) + points.add(LatLng(latitudeB, longitudeB)) + points.add(LatLng(latitudeC, longitudeC)) + points.add(LatLng(latitudeD, longitudeD)) + points.add(LatLng(latitudeE, longitudeE)) + points.add(LatLng(latitudeF, longitudeF)) + } + mapDrawPolygon(points, strokeColor, fillColor, isDashedPattern) + } + + /** + * + * 地图上画一个多边形虚线有中心图片围栏 + */ + fun addPolygonImageFence( + fencesBean: FencesBean, + imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + isDashedPattern: Boolean = true + ) { + if (imageTypeResId > 0) addImageMarker( + LatLng( + fencesBean.latitudeCenter, fencesBean.longitudeCenter + ), imageTypeResId + ) + addPolygonFence(fencesBean, strokeColor, fillColor, isDashedPattern) + } + + /** + * + * 地图上画一个正在预览的矩形虚线围栏 + */ + fun addPolygonImageEditFence( + fencesBean: FencesBean, imageTypeResId: Int = 0, + strokeColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color2), + fillColor: Int = ContextCompat.getColor(mContext!!, R.color.select_color4), + ) { + fencesBean.apply { + addPolygonImageFence(this, imageTypeResId, strokeColor, fillColor) + addImageMarker(LatLng(latitudeA, longitudeA), R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(LatLng(latitudeB, longitudeB), R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(LatLng(latitudeC, longitudeC), R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(LatLng(latitudeD, longitudeD), R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(LatLng(latitudeE, longitudeE), R.drawable.icon_fence_polygon_operate_svg) + addImageMarker(LatLng(latitudeF, longitudeF), R.drawable.icon_fence_polygon_operate_svg) + } + } + + /** + * 清除地图有多个围栏圈 + */ + fun clearFenceList() { +// if (null == mCircleFenceList) throw NullPointerException( +// "mCircleList空指针,请在onMapLoadOk重写方法中实例化mCircleList,执行方法initFenceList()" +// ) +// if (null == mRectFenceList) throw NullPointerException( +// "mCircleList空指针,请在onMapLoadOk重写方法中实例化mCircleList,执行方法initFenceList()" +// ) + mCircleFenceList?.forEach { + it.remove() + } + mCircleFenceList?.clear() + + mPolygonFenceList?.forEach { + it.remove() + } + mPolygonFenceList?.clear() + } + + /** + * 自动出现自定义头部地点布局,不需要点击 + */ + fun showMarkerInfoWindow(historyDataBean: HistoryDataBean) { + mGoogleMap?.apply { + mMarkerInfoWindowAdapter = MarkerInfoWindowAdapter(mContext!!) + setInfoWindowAdapter(mMarkerInfoWindowAdapter) + mMarker?.let { + //传递位置信息给InfoWindowAdapter窗口 + it.tag = historyDataBean + it.setInfoWindowAnchor(0.5f, 0f) + it.showInfoWindow() + } + } + } + + /** + * 获取地图画线的PolylineOptions + */ + private fun getPolylineOptions(widthDp: Float, lineColorRes: Int): PolylineOptions { + return PolylineOptions().clickable(false).width(AppUtils.dpToPx(widthDp)) + .color(ContextCompat.getColor(mContext!!, lineColorRes)).geodesic(false) + } + + /** + * 一次性画线 2条线叠加,黑黄叠加 + * 至少需要2个点的经纬度 + * 每画一次就清空之前画的 + */ + fun addSinglePolyline(googleMap: GoogleMap, latLngList: MutableList) { + if (latLngList.size > 1) { + //先黄黑线 + getPolylineOptions(4f, R.color.txt_button).let { + it.addAll(latLngList) + googleMap.addPolyline(it).apply { + startCap = RoundCap() + endCap = RoundCap() + //连接类型,可以指定棱台或圆角 + jointType = JointType.ROUND + } + } + + //自定义描边图案 +// val pattern = listOf(Dot(), Gap(20F), Dash(30F), Gap(20F)) +// polyline.pattern = pattern + + //再画黄线 + getPolylineOptions(3.5f, R.color.select_color2).let { + it.addAll(latLngList) + googleMap.addPolyline(it).apply { + startCap = RoundCap() + endCap = RoundCap() + //连接类型,可以指定棱台或圆角 + jointType = JointType.ROUND + } + } + } + } + + private var mYellowPolyline: Polyline? = null + private var mBlackPolyline: Polyline? = null + + /** + * 可以重复画线 2条线叠加,黑黄叠加 + * 需要1个点的经纬度 + * 每加个点就画一次 + */ + fun addPolyline(googleMap: GoogleMap, latLng: LatLng) { + //先黑线 + if (null == mBlackPolyline) { + getPolylineOptions(4f, R.color.txt_button).let { + it.add(latLng) + mBlackPolyline = googleMap.addPolyline(it).apply { + endCap = RoundCap() + //连接类型,可以指定棱台或圆角 + jointType = JointType.ROUND + } + } + } else { + val blackPoints = mBlackPolyline!!.points + blackPoints.add(latLng) + mBlackPolyline!!.points = blackPoints + } + + //自定义描边图案 +// val pattern = listOf(Dot(), Gap(20F), Dash(30F), Gap(20F)) +// polyline.pattern = pattern +// polyline.tag = "A" + + //再画黄线 + if (null == mYellowPolyline) { + getPolylineOptions(3.5f, R.color.btn_yellow_color).let { + it.add(latLng) + mYellowPolyline = googleMap.addPolyline(it).apply { + //起点线帽图片,refWidth数值越小,图片越大 + startCap = CustomCap( + BitmapDescriptorFactory.fromResource(R.drawable.map_live_start), + QMUIDisplayHelper.dpToPx(3).toFloat() + ) + endCap = RoundCap() + jointType = JointType.ROUND + } + } + } else { + val yellowPoints = mYellowPolyline!!.points + yellowPoints.add(latLng) + mYellowPolyline!!.points = yellowPoints + } + + LogUtil.e("点的个数:${mBlackPolyline?.points?.size},${mYellowPolyline?.points?.size}") + } + + /** + * 可以重复画线 2条线叠加,黑黄叠加 + * 需要1个点的经纬度 + * 每加个点就画一次 + */ + fun addPolyLines(googleMap: GoogleMap, latLngList: MutableList) { + //先黑线 + if (null == mBlackPolyline) { + getPolylineOptions(4f, R.color.txt_button).let { + it.addAll(latLngList) + mBlackPolyline = googleMap.addPolyline(it).apply { + endCap = RoundCap() + //连接类型,可以指定棱台或圆角 + jointType = JointType.ROUND + } + } + } else { + val blackPoints = mBlackPolyline!!.points + blackPoints.addAll(latLngList) + mBlackPolyline!!.points = blackPoints + } + + //再画黄线 + if (null == mYellowPolyline) { + getPolylineOptions(3.5f, R.color.btn_yellow_color).let { + it.addAll(latLngList) + mYellowPolyline = googleMap.addPolyline(it).apply { + //起点线帽图片,refWidth数值越小,图片越大 + startCap = CustomCap( + BitmapDescriptorFactory.fromResource(R.drawable.map_live_start), + QMUIDisplayHelper.dpToPx(3).toFloat() + ) + endCap = RoundCap() + jointType = JointType.ROUND + } + } + } else { + val yellowPoints = mYellowPolyline!!.points + yellowPoints.addAll(latLngList) + mYellowPolyline!!.points = yellowPoints + } + LogUtil.e("点的个数:${mBlackPolyline?.points?.size},${mYellowPolyline?.points?.size}") + } + + override fun onMapReady(googleMap: GoogleMap) { + LogUtil.e("谷歌地图加载好了") + mGoogleMap = googleMap + +// val options = GoogleMapOptions().liteMode(true) + + isMapLoadOk = true + +// googleMap.uiSettings.apply { +// //右下角工具栏不显示 +// isMapToolbarEnabled = true +// //指南针 +// isCompassEnabled = true +// //定位按钮 +// isMyLocationButtonEnabled = true +// } + + //地图样式 + setGoogleMapType() + + googleMap.apply { + //显示实时交通信息 +// isTrafficEnabled = true + //检测权限,开启用户的当前位置https://developers.google.cn/maps/documentation/android-sdk/location?hl=zh-cn + if (ActivityCompat.checkSelfPermission( + mContext!!, Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( + mContext!!, Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + //启用“我的位置”图层。 + isMyLocationEnabled = true + uiSettings.let { + //禁止显示“我的位置”按钮。 + it.isMyLocationButtonEnabled = false + //用于设置是启用还是停用倾斜手势的偏好设置。 + // https://developers.google.cn/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/UiSettings?hl=zh-cn + it.isTiltGesturesEnabled = false + it.isMapToolbarEnabled = false + it.isTiltGesturesEnabled = false + it.isCompassEnabled = false + } + } + + onMapLoadOk(googleMap) + } + } + + //*************************定位代码start**************************************** + + // The Fused Location Provider provides access to location APIs. + private val mFusedLocationClient: FusedLocationProviderClient by lazy { + LocationServices.getFusedLocationProviderClient(mContext!!)// + } + private var mCancellationTokenSource: CancellationTokenSource? = null + + + /** + * 获取最后位置,用户当前的位置 + */ + @SuppressLint("MissingPermission") + fun getLastLocation() { + mFusedLocationClient.lastLocation.addOnCompleteListener { taskLocation -> + var location: Location? = null + if (taskLocation.isSuccessful && taskLocation.result != null) { + location = taskLocation.result + LonAndLatUtil.currentUserGPSLat = location.latitude + LonAndLatUtil.currentUserGPSLon = location.longitude + LogUtil.e("getLastLocation:${location.latitude}, ${location.longitude}") + } else { + LogUtil.e("getLastLocation:exception" + taskLocation.exception) + } + if (null != location) { + mUserLatLng = LatLng(location.latitude, location.longitude) + if (isLocationSuccessCallback) locationSuccess(location) + isLocationSuccessCallback = false + } else requestCurrentLocation() + } + + } + + /** + * 请求用户当前的位置 + */ + @SuppressLint("MissingPermission") + fun requestCurrentLocation() { + try { + if (null == mCancellationTokenSource) mCancellationTokenSource = + CancellationTokenSource() +// val currentLocationTask = mFusedLocationClient.getCurrentLocation( +// LocationRequest.PRIORITY_HIGH_ACCURACY, mCancellationTokenSource!!.token +// ) + val currentLocationTask = mFusedLocationClient.getCurrentLocation( + Priority.PRIORITY_HIGH_ACCURACY, mCancellationTokenSource!!.token + ) + currentLocationTask.addOnCompleteListener { task: Task -> + var location: Location? = null + val result = if (task.isSuccessful && task.result != null) { + location = task.result + LonAndLatUtil.currentUserGPSLat = location.latitude + LonAndLatUtil.currentUserGPSLon = location.longitude + "(success): ${location.latitude}, ${location.longitude}" + } else { + val exception = task.exception + "(failure): $exception" + } + + LogUtil.e("requestCurrentLocation $result") + if (null != location) { + mUserLatLng = LatLng(location.latitude, location.longitude) + if (isLocationSuccessCallback) locationSuccess(location) + isLocationSuccessCallback = false + } + } + } catch (e: Exception) { + LogUtil.e("requestCurrentLocation异常:${e.message}") + } + } + + //*************************定位代码end**************************************** + + /** + * 由 points 组成的区域 距离手机屏幕距离screenPaddingPx 像素单位 + */ + fun setLatLngZoom(context: Context, screenPaddingPx: Int, vararg points: LatLng) { + val width = context.resources.displayMetrics.widthPixels + val height = context.resources.displayMetrics.heightPixels + val padding = width / 8 + val builder = LatLngBounds.Builder() + for (item in points) { + builder.include(item) + } + //根据经纬度来设置缩放级别 +// mCameraUpdate = CameraUpdateFactory.newLatLngBounds( +// builder.build(), width, (height / 2.5f).toInt(), padding.toInt() +// ) + mCameraUpdate = CameraUpdateFactory.newLatLngBounds(builder.build(), screenPaddingPx) + } + + fun getMyZoom1(distance: Double): Float { + val scale = intArrayOf( + 3,//21 + 5,//20 + 10,//19 + 30,//18 + 50,//17 + 90,//16 + 200,//15 + 400,//14 + 800,//13 + 1400,//12 + 2900,//11 + 5800,//10 + 11000,//9 + 22000,//8 + 43000,//7 + 88000,//6 + 170000,//5 + 330000,//4 + 390000,//3 + 1530000,//2级缩放 + ) + var jl = distance + if (distance < 3) { + jl = 4.0 + } + LogUtil.e("最大和最小间经纬度直线距离 = $jl") + var i = 0 + while (i < scale.size - 2) { + if (scale[i] < jl && scale[i + 1] >= jl) { + break + } + i++ + } + LogUtil.e("根据距离获取Zoom=${21f - i}") + return 21f - i + } + + /** + * 根据显示距离获取缩放地图等级 + */ + fun getDistanceZoom(distance: Double): Float { + val scale = intArrayOf( + 10,//21 + 30,//20 + 50,//19 + 80,//18 + 100,//17 + 400,//16 + 800,//15 + 1000,//14 + 2000,//13 + 3000,//12 + 4000,//11 + 58000,//10 + 11000,//9 + 22000,//8 + 44000,//7 + 88000,//6 + 170000,//5 + 330000,//4 + 1000000,//3 + 1530000,//2级缩放 + ) + LogUtil.e("最大和最小间经纬度直线距离 = $distance") + val zoom: Float = if (distance <= scale[0]) { + 21f + } else if (distance >= scale[scale.size - 1]) { + 1f + } else { + var i = 0 + while (i < scale.size - 2) { + if (scale[i] < distance && scale[i + 1] >= distance) { + break + } + i++ + } + 21f - i + } + LogUtil.e("根据距离获取Zoom=$zoom") + return zoom + } + + /** + * 设置Marker弹窗消息的位置 + */ + fun setMarkerInfoViewOffset(markerInfoView: MapMarkerInfoView, latLng: LatLng) { + mGoogleMap?.projection?.toScreenLocation(latLng)?.let { + markerInfoView.setOffsetXY(it.x, it.y - AppUtils.dpToPx(56)) + } + } + + /** + * 设置地图网络类型显示位置 + */ + fun setMapNetTypeViewOffset(mapDeviceNetView: MapDeviceNetView, latLng: LatLng) { + mGoogleMap?.projection?.toScreenLocation(latLng)?.let { + mapDeviceNetView.visibility = View.VISIBLE + mapDeviceNetView.setOffsetXY(it.x + AppUtils.dpToPx(11), it.y - AppUtils.dpToPx(56)) + } + } + + /** + * 定位成功返回 + */ + open fun locationSuccess(location: Location?) {} + + /** + * 地图加载完毕 + */ + abstract fun onMapLoadOk(googleMap: GoogleMap) + + /** + * 宠物位置刷新完成 + */ + open fun onRefreshPetCurrentLocationOk(googleMap: GoogleMap) { + } + + override fun onStop() { + super.onStop() + mCancellationTokenSource?.cancel() + mGoogleMapView.onStop() + } + + override fun onDestroy() { + super.onDestroy() + mPetHeadIconBitmap?.recycle() + mGoogleMapView.onDestroy() + } +} + diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/EditWiFiZoneGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/EditWiFiZoneGoogleMapFragment.kt new file mode 100644 index 0000000..e36e7c5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/EditWiFiZoneGoogleMapFragment.kt @@ -0,0 +1,66 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.content.Context +import androidx.fragment.app.viewModels +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.Marker + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description: + */ +class EditWiFiZoneGoogleMapFragment( + +) : BaseGoogleMapFragment() { + + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + private lateinit var mMapLoadOk: () -> Unit + private lateinit var mWiFiZoneBean: WiFiZoneBean + + companion object { + @JvmStatic + fun newInstance(context: Context, wiFiZoneBean: WiFiZoneBean, mapLoadOk: () -> Unit) = + EditWiFiZoneGoogleMapFragment().apply { + mContext = context + mMapLoadOk = mapLoadOk + mWiFiZoneBean = wiFiZoneBean + } + } + + override fun onMapLoadOk(googleMap: GoogleMap) { + + googleMap.setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + override fun onMarkerDrag(marker: Marker) { + + } + + override fun onMarkerDragEnd(marker: Marker) { + marker.position.let { + mGeoCoderViewModel.getLatLonAddress(it.latitude, it.longitude, false) + mWiFiZoneBean.latitude = it.latitude + mWiFiZoneBean.longitude = it.longitude + } + } + + override fun onMarkerDragStart(marker: Marker) { + + } + }) + + getLastLocation() + + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + override fun liveDataObserve() { + mGeoCoderViewModel.mLatLonAddressLiveData.observe(viewLifecycleOwner) { + mWiFiZoneBean.address = it + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt new file mode 100644 index 0000000..542f97e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/FencesAddEditGoogleMapFragment.kt @@ -0,0 +1,471 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.content.Context +import android.graphics.Point +import android.location.Location +import android.view.View +import androidx.core.view.isVisible +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.common.map.FencesAddEditMapCommon +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.widget.FencesCircleView +import com.abbidot.tracker.widget.FencesPolygonView +import com.abbidot.tracker.widget.FencesRectView +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.LatLng +import kotlin.math.abs +import kotlin.math.pow + +/** + *Created by .yzq on 2024/4/23/023. + * @link + * @description: + */ +class FencesAddEditGoogleMapFragment : BaseGoogleMapFragment() { + + private lateinit var mFencesBean: FencesBean + private var isEditFences = false + private lateinit var mFencesCircleView: FencesCircleView + private lateinit var mFencesRectView: FencesRectView + private lateinit var mFencesPolygonView: FencesPolygonView + private var mOnActivityCallBack: FencesAddEditMapCommon.OnActivityCallBack? = null + + private lateinit var mMapLoadOk: () -> Unit + + //防止刚开始移动摄像头有时会执行2次 + private var isCanExecuteCameraCallBack = false + + //防止地图摄像头移动动画自动设置地图缩放级别,会执行setOnCameraMoveListener() + private var isMapZoomReady = false + + companion object { + @JvmStatic + fun newInstance( + context: Context, + fencesBean: FencesBean, + fencesCircleView: FencesCircleView, + fencesRectView: FencesRectView, + fencesPolygonView: FencesPolygonView, + isEdit: Boolean, + onActivityCallBack: FencesAddEditMapCommon.OnActivityCallBack?, + mapLoadOk: () -> Unit + ) = FencesAddEditGoogleMapFragment().apply { + mContext = context + mFencesBean = fencesBean + isEditFences = isEdit + mFencesCircleView = fencesCircleView + mFencesRectView = fencesRectView + mFencesPolygonView = fencesPolygonView + mOnActivityCallBack = onActivityCallBack + mMapLoadOk = mapLoadOk + } + } + + override fun onMapLoadOk(googleMap: GoogleMap) { + //监听摄像机移动中心点,摄像机移动结束时的回调接口。有时会执行2次 + mGoogleMap?.apply { + setOnCameraIdleListener { + if (isCanExecuteCameraCallBack) { + //现在围栏控件改成随地图移动,只需要执行一次 + isCanExecuteCameraCallBack = false + mapOkAndDistancePointLatLng() + } + } + + setOnCameraMoveListener { + mFencesBean.let { + resetFencesViewCentre( + LatLng( + it.latitudeCenter, it.longitudeCenter + ) + ) + } + + //监听地图放大缩小操作 + if (isMapZoomReady && getGoogleMapZoom() != cameraPosition.zoom) { + setGoogleMapZoom(cameraPosition.zoom) + resetFencesViewSize() + } + } + + setOnMapLongClickListener { + resetFencesViewCentre(it) + mapOkAndDistancePointLatLng(0) + } + } + + getLastLocation() + + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + override fun locationSuccess(location: Location?) { + if (isEditFences) { + mFencesBean.apply { + moveCameraUpdate(latitudeCenter, longitudeCenter) + } + + //延时执行,防止围栏没画好/地图摄像头移动中 + mFencesCircleView.postDelayed({ + isMapZoomReady = true + restoreEditFences() + }, 1500) + + return + } + + isMapZoomReady = true + location?.apply { + mFencesBean.latitudeCenter = latitude + mFencesBean.longitudeCenter = longitude + isCanExecuteCameraCallBack = true + moveCameraLocation(LatLng(latitude, longitude), false) + } + } + + /** + * 重新设置围栏控件的中心点位置 + */ + fun resetFencesViewCentre(centreLatLng: LatLng) { + mGoogleMap?.projection?.toScreenLocation(centreLatLng)?.apply { + mFencesCircleView.setCentreXY(this) + mFencesRectView.setCentreXY(this) + mFencesPolygonView.setCentreXY(this) + } + } + + /** + * 重新设置围栏控件的大小 + */ + private fun resetFencesViewSize() { + if (mFencesCircleView.isVisible) { + if (mFencesBean.radius <= 0.0) return + restoreCircleFences() + } else if (mFencesRectView.isVisible) { + restoreRectFences() + } else if (mFencesPolygonView.isVisible) { + restorePolygonFences() + } + } + + /** + * 地图准备好计算距离 + */ + fun mapOkAndDistancePointLatLng(delayMillis: Long = 1000) { + if (mFencesBean.startDrag) return + if (delayMillis == 0L) { + val arrayOfDistance = getLatLngAndCalDistance() + if (mFencesBean.startDrag) return + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + } else { + //延时计算距离,防止围栏没画好/地图摄像头移动中 + mFencesCircleView.postDelayed({ + val arrayOfDistance = getLatLngAndCalDistance() + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + }, delayMillis) + } + } + + private fun getLatLngAndCalDistance(): Array { + // 经纬度转换为手机坐标点 +// val x = mGoogleMap!!.projection.toScreenLocation(it).x + if (null == mGoogleMap) return arrayOf("0", "0") + + return when (mFencesBean.fenceShapeType) { + ConstantInt.CircleShapeType -> { + //手机坐标点转换为经纬度 + val centreLatLng = mFencesCircleView.getCircleCentrePoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + val startLatLng = mFencesCircleView.getCircleLineStartPoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + val endLatLng = mFencesCircleView.getCircleLineEndPoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + LogUtil.e("GoogleMap,setOnCameraIdleListener 监听回调") + LogUtil.e("startLatLng=$centreLatLng,endLatLng=$endLatLng") + val distance = Util.measureLatLonDistance( + centreLatLng.latitude, + centreLatLng.longitude, + endLatLng.latitude, + endLatLng.longitude + ) + mFencesBean.apply { + latitudeCenter = centreLatLng.latitude + longitudeCenter = centreLatLng.longitude + ancillaryLatitude = endLatLng.latitude + ancillaryLongitude = endLatLng.longitude + ancillaryOtherLatitude = startLatLng.latitude + ancillaryOtherLongitude = startLatLng.longitude + } + arrayOf( + Utils.formatDecimal(distance, 2) + ) + } + + ConstantInt.RectangleShapeType -> { + val centreLatLng = mFencesRectView.getRectCentrePoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + val rectALatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_A).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val rectBLatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_B).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val rectCLatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_C).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val rectDLatLng = mFencesRectView.getRectABCDPoint(FencesPolygonView.POINT_D).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val rectWidthDistance = Util.measureLatLonDistance( + rectALatLng.latitude, + rectALatLng.longitude, + rectBLatLng.latitude, + rectBLatLng.longitude + ) + val rectHeightDistance = Util.measureLatLonDistance( + rectBLatLng.latitude, + rectBLatLng.longitude, + rectCLatLng.latitude, + rectCLatLng.longitude + ) + mFencesBean.apply { + latitudeCenter = centreLatLng.latitude + longitudeCenter = centreLatLng.longitude + latitudeA = rectALatLng.latitude + longitudeA = rectALatLng.longitude + latitudeB = rectBLatLng.latitude + longitudeB = rectBLatLng.longitude + latitudeC = rectCLatLng.latitude + longitudeC = rectCLatLng.longitude + latitudeD = rectDLatLng.latitude + longitudeD = rectDLatLng.longitude + } + //长度和高度 + arrayOf( + Utils.formatDecimal(rectWidthDistance, 2), + Utils.formatDecimal(rectHeightDistance, 2) + ) + } + + else -> { + val centreLatLng = mFencesPolygonView.getPolygonCentrePoint() + .let { mGoogleMap!!.projection.fromScreenLocation(it) } + val polygonALatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_A).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val polygonBLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_B).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val polygonCLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_C).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val polygonDLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_D).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val polygonELatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_E).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + val polygonFLatLng = + mFencesPolygonView.getPolygonABCDEFPoint(FencesPolygonView.POINT_F).let { + mGoogleMap!!.projection.fromScreenLocation(it) + } + mFencesBean.apply { + latitudeCenter = centreLatLng.latitude + longitudeCenter = centreLatLng.longitude + latitudeA = polygonALatLng.latitude + longitudeA = polygonALatLng.longitude + latitudeB = polygonBLatLng.latitude + longitudeB = polygonBLatLng.longitude + latitudeC = polygonCLatLng.latitude + longitudeC = polygonCLatLng.longitude + latitudeD = polygonDLatLng.latitude + longitudeD = polygonDLatLng.longitude + latitudeE = polygonELatLng.latitude + longitudeE = polygonELatLng.longitude + latitudeF = polygonFLatLng.latitude + longitudeF = polygonFLatLng.longitude + } + //多边形,不需要计算距离 + arrayOf("0") + } + } + } + + /** + * 还原围栏 + */ + private fun restoreEditFences() { + var arrayOfDistance = arrayOf("0") + mFencesBean.apply { + when (mFencesBean.fenceShapeType) { + ConstantInt.CircleShapeType -> { + restoreCircleFences() + arrayOfDistance = arrayOf("$radius") + } + + ConstantInt.RectangleShapeType -> { + restoreRectFences() + arrayOfDistance = arrayOf("$longDistance", "$shortDistance") + } + + ConstantInt.PolygonShapeType -> { + restorePolygonFences() + arrayOfDistance = arrayOf("0") + } + } + + mOnActivityCallBack?.calculationDistance(arrayOfDistance) + } + isCanExecuteCameraCallBack = true + } + + /** + * 还原圆形围栏 + */ + private fun restoreCircleFences() { + if (null==mGoogleMap)return + mFencesBean.apply { + mFencesCircleView.let { + it.visibility = View.VISIBLE + val pointCentre = mGoogleMap!!.projection.toScreenLocation( + LatLng(latitudeCenter, longitudeCenter) + ) + val pointLineEnd = mGoogleMap!!.projection.toScreenLocation( + LatLng(ancillaryLatitude, ancillaryLongitude) + ) + val radius = abs(pointLineEnd.x - pointCentre.x).toFloat() + it.setCircleRadiusPx(radius) + + //圆形大小变了,其他形状的围栏也要改变大小 + val rectA = Point( + (pointLineEnd.x - radius * 2).toInt(), (pointLineEnd.y - radius).toInt() + ) + val rectB = Point(pointLineEnd.x, (pointLineEnd.y - radius).toInt()) + val rectC = Point(pointLineEnd.x, (pointLineEnd.y + radius).toInt()) + val rectD = + Point((pointLineEnd.x - radius * 2).toInt(), (pointLineEnd.y + radius).toInt()) + mFencesRectView.setRectABCDXYPoint(pointCentre, rectA, rectB, rectC, rectD) + val polygonA = Point(pointCentre.x, (pointCentre.y - radius).toInt()) + val polygonB = Point(pointLineEnd.x, (pointLineEnd.y - radius / 2).toInt()) + val polygonC = Point(pointLineEnd.x, (pointLineEnd.y + radius / 2).toInt()) + val polygonD = Point(pointCentre.x, (pointCentre.y + radius).toInt()) + val polygonE = Point( + (pointLineEnd.x - 2 * radius).toInt(), (pointLineEnd.y + radius / 2).toInt() + ) + val polygonF = Point( + (pointLineEnd.x - 2 * radius).toInt(), (pointLineEnd.y - radius / 2).toInt() + ) + mFencesPolygonView.setPolygonABCDEFPoint( + pointCentre, polygonA, polygonB, polygonC, polygonD, polygonE, polygonF + ) + } + } + } + + /** + * 还原矩形围栏大小 + */ + private fun restoreRectFences() { + if (null==mGoogleMap)return + mFencesBean.apply { + mFencesRectView.let { + it.visibility = View.VISIBLE + val pointCentre = mGoogleMap!!.projection.toScreenLocation( + LatLng(latitudeCenter, longitudeCenter) + ) + val pointA = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeA, longitudeA)) + val pointB = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeB, longitudeB)) + val pointC = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeC, longitudeC)) + val pointD = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeD, longitudeD)) + it.setRectABCDXYPoint(pointCentre, pointA, pointB, pointC, pointD) + + //矩形大小变了,其他形状的围栏也要改变大小 + val radius = abs(pointA.x - pointCentre.x).toFloat() + mFencesCircleView.setCircleRadiusPx(radius) + val minX = minOf(pointA.x, pointB.x, pointC.x, pointD.x) + val maxX = maxOf(pointA.x, pointB.x, pointC.x, pointD.x) + val minY = minOf(pointA.y, pointB.y, pointC.y, pointD.y) + val maxY = maxOf(pointA.y, pointB.y, pointC.y, pointD.y) + val difX = maxX - minX + val difY = maxY - minY + val polygonA = Point(minX + difX / 2, minY) + val polygonB = Point(maxX, minY + difY / 4) + val polygonC = Point(maxX, maxY - difY / 4) + val polygonD = Point(minX + difX / 2, maxY) + val polygonE = Point(minX, maxY - difY / 4) + val polygonF = Point(minX, minY + difY / 4) + mFencesPolygonView.setPolygonABCDEFPoint( + pointCentre, polygonA, polygonB, polygonC, polygonD, polygonE, polygonF + ) + } + } + } + + /** + * 还原多边形围栏大小 + */ + private fun restorePolygonFences() { + if (null==mGoogleMap)return + mFencesBean.apply { + mFencesPolygonView.let { + it.visibility = View.VISIBLE + val pointCentre = mGoogleMap!!.projection.toScreenLocation( + LatLng(latitudeCenter, longitudeCenter) + ) + val pointA = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeA, longitudeA)) + val pointB = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeB, longitudeB)) + val pointC = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeC, longitudeC)) + val pointD = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeD, longitudeD)) + val pointE = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeE, longitudeE)) + val pointF = mGoogleMap!!.projection.toScreenLocation(LatLng(latitudeF, longitudeF)) + it.setPolygonABCDEFPoint( + pointCentre, pointA, pointB, pointC, pointD, pointE, pointF + ) + + val maxX = maxOf(pointA.x, pointB.x, pointC.x, pointD.x, pointE.x, pointF.x) + //多边形大小变了,其他形状的围栏也要改变大小 + val radius = abs(maxX - pointCentre.x).toFloat() + mFencesCircleView.setCircleRadiusPx(radius) + val minX = minOf(pointA.x, pointB.x, pointC.x, pointD.x, pointE.x, pointF.x) + val minY = minOf(pointA.y, pointB.y, pointC.y, pointD.y, pointE.y, pointF.y) + val maxY = maxOf(pointA.y, pointB.y, pointC.y, pointD.y, pointE.y, pointF.y) + val rectA = Point(minX, minY) + val rectB = Point(maxX, minY) + val rectC = Point(maxX, maxY) + val rectD = Point(minX, maxY) + mFencesRectView.setRectABCDXYPoint(pointCentre, rectA, rectB, rectC, rectD) + } + } + } + + /** + * 根据地图显示级别,计算比例尺的一像素对应几米 + */ + private fun getCircleRadiusPx(distanceRadius: Double): Double { + val zoom = getDistanceZoom(distanceRadius) + //一像素对应几米 + val pxDis = 2.0.pow(15.5 - zoom) + //计算现在半径有多少像素 + return distanceRadius / pxDis + } + + /** + * 根据地图显示级别,计算比例尺的一像素对应几米 + */ + private fun zoomToCircleRadiusPx(distanceRadius: Double): Double { + val zoom = getGoogleMapZoom() + //一像素对应几米 + val pxDis = 2.0.pow(15.5 - zoom) + //计算现在半径有多少像素 + return distanceRadius / pxDis + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/GoogleBitmapHelper.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/GoogleBitmapHelper.kt new file mode 100644 index 0000000..c9aa6d5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/GoogleBitmapHelper.kt @@ -0,0 +1,164 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.content.Context +import android.graphics.* +import android.text.TextUtils +import android.util.Log +import androidx.annotation.ColorInt +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.drawable.DrawableCompat +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.ImageUtil +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2022/1/13/013. + * @link + * @description:谷歌地图BitmapHelper + */ +object GoogleBitmapHelper { + + /** + * 自定义定位图标,宠物头像,自定义位图 + * + * @param id 本地底部背景圆形图片 + * @param headUrl 网络宠物的头像url + */ + suspend fun headToBitmap( + context: Context, @DrawableRes id: Int, headUrl: String, petType: Int + ): Bitmap? { + var headUrlBitmap = if (TextUtils.isEmpty(headUrl)) { + var headResId = R.drawable.icon_dog_default_head_svg + if (petType == ConstantInt.CatPetType) headResId = R.drawable.icon_cat_default_head_svg + ImageUtil.getBitmapFromDrawableAndSvg(context, headResId) + } else { + ImageUtil.getImageBitmapByUrl(context, headUrl) + } + if (null == headUrlBitmap) headUrlBitmap = BitmapFactory.decodeResource( + context.resources, R.drawable.pic_avatar_df + ) + return combineBitmap(ImageUtil.getBitmapFromDrawableAndSvg(context, id), headUrlBitmap!!) +// return combineBitmap(BitmapFactory.decodeResource(context.resources, id), headUrlBitmap) + } + + /** + * 先等比压缩,然后合并两张bitmap为一张 + * + * @param background + * @param foreground + * @return Bitmap + */ + private fun combineBitmap(background: Bitmap?, foreground: Bitmap): Bitmap? { + if (null == background) { + return null + } + val bgWidth = background.width + val bgHeight = background.height + var foregroundBitmap = ImageUtil.zoomImg(foreground, bgWidth, bgWidth) + foregroundBitmap = toRoundBitmap(foregroundBitmap, bgWidth, bgHeight) +// val fgWidth = foregroundBitmap.width +// val fgHeight = foregroundBitmap.height +// val newmap = Bitmap.createBitmap(bgWidth, bgHeight, Bitmap.Config.ARGB_8888) +// val canvas = Canvas(newmap) +// canvas.drawBitmap(background, 0f, 0f, null) +// canvas.drawBitmap(foregroundBitmap, (bgWidth - fgWidth) / 2f, (bgHeight - fgHeight) / 2f, +// null) +// canvas.save() +// canvas.restore() + return ImageUtil.combineBitmap(background, foregroundBitmap) + } + + /** + * 转换为圆形bitmap + * + * @param bitmap + * @param bgWidth 底下圆形的宽 + * @param bgHeight 底下圆形的高 + * @return + */ + private fun toRoundBitmap(bitmap: Bitmap, bgWidth: Int, bgHeight: Int): Bitmap { + //算出底下圆形直径 +// val dia = (bgHeight - (bgHeight - bgWidth)).toFloat() + val dia = bgWidth.toFloat() + //底下圆形半径 + val radius = dia / 2f + + //前面同上,绘制图像分别需舆bitmap,canvas , paint对象 +// val bp = Bitmap.createScaledBitmap(bitmap, bgWidth, bgHeight, true) + val bp = Bitmap.createScaledBitmap(bitmap, bgWidth, bgWidth, true) + val bm = Bitmap.createBitmap(bgWidth, bgHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bm) + val paint = Paint(Paint.ANTI_ALIAS_FLAG) + //这里斋要先画出一个圈 + canvas.drawCircle(radius, radius, radius - (bgHeight - bgWidth) / 6f, paint) + //圆画好之后将画笔重置一下 + paint.reset() + //设置图像合成模式,该模式为只在源图像和目标图相交的地方绘制源图 + paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) + canvas.drawBitmap(bp, 0f, 0f, paint) + return bm + } + + + /** + * 在图片上写文字 + */ + fun imageUpNumberBitmap( + context: Context, + @DrawableRes id: Int, + text: String = "", + typeface: Typeface = Typeface.DEFAULT + ): Bitmap { + val bgBitmap = BitmapFactory.decodeResource(context.resources, id) + val newmap = Bitmap.createBitmap(bgBitmap.width, bgBitmap.height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(newmap) + canvas.drawBitmap(bgBitmap, 0f, 0f, null) + val paint = Paint() + paint.color = ContextCompat.getColor(context, R.color.txt_black_color) + paint.textSize = QMUIDisplayHelper.sp2px(context, 16).toFloat() + if (!TextUtils.isEmpty(text)) paint.typeface = typeface + //文字宽度 + val textWidth = paint.measureText(text) + val fontMetrics = paint.fontMetrics + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top + + val x = bgBitmap.width / 2f - textWidth / 2 + val y = bgBitmap.width.toFloat() - textHeight / 2 - (bgBitmap.height - bgBitmap.width) / 2 + canvas.drawText(text, x, y, paint) + canvas.save() + canvas.restore() + return newmap + } + + /** + * 谷歌demo自定义MarkerOptions,自定义位图 + * https://developers.google.cn/codelabs/maps-platform/maps-platform-101-android#5 + * + * Demonstrates converting a [Drawable] to a [BitmapDescriptor], + * for use as a marker icon. Taken from ApiDemos on GitHub: + * https://github.com/googlemaps/android-samples/blob/main/ApiDemos/kotlin/app/src/main/java/com/example/kotlindemos/MarkerDemoActivity.kt + */ + fun vectorToBitmap( + context: Context, @DrawableRes id: Int, @ColorInt color: Int + ): BitmapDescriptor { + val vectorDrawable = ResourcesCompat.getDrawable(context.resources, id, null) + if (vectorDrawable == null) { + Log.e("BitmapHelper", "Resource not found") + return BitmapDescriptorFactory.defaultMarker() + } + val bitmap = Bitmap.createBitmap( + vectorDrawable.intrinsicWidth, vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + vectorDrawable.setBounds(0, 0, canvas.width, canvas.height) + DrawableCompat.setTint(vectorDrawable, color) + vectorDrawable.draw(canvas) + return BitmapDescriptorFactory.fromBitmap(bitmap) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/HistoryDataGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/HistoryDataGoogleMapFragment.kt new file mode 100644 index 0000000..1be3214 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/HistoryDataGoogleMapFragment.kt @@ -0,0 +1,304 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.BitmapFactory +import android.graphics.Typeface +import androidx.core.app.ActivityCompat +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.MapMarkerInfoView +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.BitmapDescriptor +import com.google.android.gms.maps.model.BitmapDescriptorFactory +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.MarkerOptions + +/** + *Created by .yzq on 2022/1/13/013. + * @link + * @description:历史记录map谷歌地图 + */ +class HistoryDataGoogleMapFragment : BaseGoogleMapFragment() { + + // private val mScreenshotsViewModel: ScreenshotsViewModel by viewModels() + private lateinit var mGeoCoderViewModel: GeoCoderViewModel + private lateinit var mMarkerInfoView: MapMarkerInfoView + + private val mMarkerOptions = MarkerOptions() + private lateinit var mPetIconDescriptor: BitmapDescriptor + + private val mLatLngList = mutableListOf() + + //字体 + private var mTypeface = Typeface.DEFAULT + + //是否地图截屏 + private var isMapSnapshot = true + private lateinit var mMapLoadOk: () -> Unit + + private var mPetBean: PetBean? = null + private var mCurLatLng: LatLng? = null + + companion object { + @JvmStatic + fun newInstance( + context: Context, + markerInfoView: MapMarkerInfoView, + geoCoderViewModel: GeoCoderViewModel, + mapLoadOk: () -> Unit + ) = HistoryDataGoogleMapFragment().apply { + mContext = context + mGeoCoderViewModel = geoCoderViewModel + mMarkerInfoView = markerInfoView + mMapLoadOk = mapLoadOk + } + } + + override fun initData() { + super.initData() + mTypeface = + ViewUtil.instance.setTypeface(mContext!!, getString(R.string.number_din_cond_font)) + } + + override fun onMapLoadOk(googleMap: GoogleMap) { +// val mutableList = mLatLngList +// mutableList.add(LatLng(22.5654646, 113.9514812)) +// mutableList.add(LatLng(22.5666646, 113.9524812)) +// mutableList.add(LatLng(22.5676646, 113.9525812)) +// mutableList.add(LatLng(22.5687646, 113.9525912)) +// mutableList.add(LatLng(22.5698646, 113.9522912)) +// mutableList.add(LatLng(22.5659646, 113.9530112)) +// mutableList.add(LatLng(22.5660646, 113.9545112)) +// mutableList.add(LatLng(22.5670646, 113.9555112)) +// mutableList.add(LatLng(22.5680646, 113.9565112)) +// mutableList.add(LatLng(22.5690646, 113.9575112)) +// mutableList.add(LatLng(22.5690646, 113.9585112)) +// mutableList.add(LatLng(22.5690646, 113.9596112)) +// mutableList.add(LatLng(22.5700646, 113.9626112)) +// mutableList.add(LatLng(22.5720646, 113.9656112)) +// mutableList.add(LatLng(22.5760646, 113.9666112)) + + //点击地图任意地方都不消失自定义头部地点布局 + googleMap.apply { + setOnCameraMoveListener { + mCurLatLng?.let { + setMarkerInfoViewOffset(mMarkerInfoView, it) + } + } + + //检测权限,开启用户的当前位置https://developers.google.cn/maps/documentation/android-sdk/location?hl=zh-cn + if (ActivityCompat.checkSelfPermission( + mContext!!, Manifest.permission.ACCESS_FINE_LOCATION + ) == PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission( + mContext!!, Manifest.permission.ACCESS_COARSE_LOCATION + ) == PackageManager.PERMISSION_GRANTED + ) { + //启用“我的位置”图层。 + isMyLocationEnabled = false + } + } + + getLastLocation() + + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + suspend fun setPetBean(petBean: PetBean) { + if (petBean == mPetBean) { + return + } + mPetBean = petBean +// lifecycleScope.launch { +// +// } + mPetBean?.apply { + var bitmap = GoogleBitmapHelper.headToBitmap( + mContext!!, R.drawable.pic_map_gps_avatar, imgurl, petType + ) + if (null == bitmap) bitmap = BitmapFactory.decodeResource( + mContext!!.resources, R.drawable.icon_location_svg + ) + mPetIconDescriptor = BitmapDescriptorFactory.fromBitmap(bitmap!!) + } + mMarker?.setIcon(mPetIconDescriptor) + } + + /** + * 设置经纬度数据 + */ + fun setLatLngData(data: MutableList) { + mGoogleMap?.clear() + + if (data.size == 0) return + + var maxLat = data[0].latitude + var maxLon = data[0].longitude + var minLat = data[0].latitude + var minLon = data[0].longitude + + data.forEach { historyDataBean -> + historyDataBean.apply { + if (maxLat < latitude) maxLat = latitude + if (maxLon < longitude) maxLon = longitude + if (minLat > latitude) minLat = latitude + if (minLon > longitude) minLon = longitude + val latLng = LatLng(latitude, longitude) + mLatLngList.add(latLng) + } + } + + //根据经纬度来设置缩放级别 + setLatLngZoom( + mContext!!, AppUtils.dpToPx(50), LatLng(minLat, minLon), LatLng(maxLat, maxLon) + ) + + val type = MMKVUtil.getInt(MMKVKey.LineSelectPosition, ConstantInt.Line) + if (type == ConstantInt.Line) { + addSetLine() + } else { + addSetNumber() + } + + val maxIndex = data.size - 1 + mGeoCoderViewModel.getLatLonAddress( + mLatLngList[maxIndex].latitude, mLatLngList[maxIndex].longitude, false + ) + + mCurLatLng = mLatLngList[maxIndex] + setMarkerInfoViewOffset(mMarkerInfoView, mLatLngList[maxIndex]) + } + + /** + * 滑动改变 + */ + fun slideChanged(progress: Int) { + setPetMarkerLatLng(mLatLngList[progress]) + } + + /** + * 停止滑动 + */ + fun slideStopChanged(progress: Int) { + if (mLatLngList.size > 0) { + moveCameraLocation( + mLatLngList[progress].latitude, mLatLngList[progress].longitude, false + ) + mGeoCoderViewModel.getLatLonAddress( + mLatLngList[progress].latitude, mLatLngList[progress].longitude, false + ) + + mCurLatLng = mLatLngList[progress] + setMarkerInfoViewOffset(mMarkerInfoView, mLatLngList[progress]) + } + } + + override fun clearAllMarker() { + super.clearAllMarker() + mLatLngList.clear() + } + + /** + * 画线 + */ + fun addSetLine() { + val size = mLatLngList.size + LogUtil.e("画线点有 $size 个") + if (size == 0) return + mGoogleMap?.let { + it.clear() + + moveCameraUpdate(mLatLngList[size - 1]) + + addSinglePolyline(it, mLatLngList) + +// refreshPetCurrentLocation( +// mLatLngList[size - 1], needMoveCamera = false, isAnimMoveCamera = false +// ) + + setPetMarkerLatLng(mLatLngList[size - 1]) + } + } + + /** + * 设置显示数字 + */ + fun addSetNumber() { + val size = mLatLngList.size + LogUtil.e("轨迹谷歌地图设置显示数字$size,经纬度点") + if (size == 0) return + mGoogleMap?.let { + it.clear() + + moveCameraUpdate(mLatLngList[size - 1]) + +// refreshPetCurrentLocation( +// mLatLngList[size - 1], needMoveCamera = false, isAnimMoveCamera = false +// ) + + for (i in 0 until mLatLngList.size) { + val numberMarker = MarkerOptions().position(mLatLngList[i]).icon( + BitmapDescriptorFactory.fromBitmap( + GoogleBitmapHelper.imageUpNumberBitmap( + mContext!!, + R.drawable.pic_history_location, + (i + 1).toString(), + mTypeface + ) + ) + ) + it.addMarker(numberMarker) + } + + setPetMarkerLatLng(mLatLngList[size - 1]) + } + } + + /** + * 设置宠物Marker经纬度 + */ + fun setPetMarkerLatLng(latLng: LatLng) { + if (::mPetIconDescriptor.isInitialized) { + mGoogleMap?.apply { + mMarkerOptions.position(latLng).icon(mPetIconDescriptor) + mMarker?.remove() + mMarker = addMarker(mMarkerOptions) + //显示最上面,层级最高 + mMarker?.zIndex = 1f + } + } + } + + private fun moveCamera(latLng: LatLng) { +// moveAnimateCameraLocation(latLng, object : GoogleMap.CancelableCallback { +// override fun onCancel() { +// LogUtil.e("moveCamera onCancel") +// } +// +// override fun onFinish() { +// LogUtil.e("moveCamera onFinish") +// if (isMapSnapshot) { +// isMapSnapshot = false +// mHistoryDataActivity.mTopBar?.postDelayed({ +// if (mHistoryDataActivity.isFrontRunning) { +// mScreenshotsViewModel.getHistoryDataGoogleMapSnapshotImage( +// mContext!!, mGoogleMap!!, mHistoryDataActivity.mPet.deviceId +// ) +// } +// }, 1200) +// } +// } +// }) + moveAnimateCameraLocation(latLng, null) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/HomeMapGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/HomeMapGoogleMapFragment.kt new file mode 100644 index 0000000..272f8f9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/HomeMapGoogleMapFragment.kt @@ -0,0 +1,308 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.animation.ValueAnimator +import android.content.Context +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.core.content.ContextCompat +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.abbidot.tracker.widget.MapDeviceNetView +import com.abbidot.tracker.widget.MapMarkerInfoView +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.Circle +import com.google.android.gms.maps.model.CircleOptions +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Polyline +import com.google.android.gms.maps.model.PolylineOptions +import kotlin.math.pow + +/** + *Created by .yzq on 2022/1/13/013. + * @link + * @description:首页map谷歌地图 + */ +class HomeMapGoogleMapFragment : BaseGoogleMapFragment() { + + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + private lateinit var mMarkerInfoView: MapMarkerInfoView + private lateinit var mMapDeviceNetView: MapDeviceNetView + + //宠物当前反地理位置信息 + var mDecPetAddressData: HistoryDataBean? = null + private var mMapDeviceBean: MapDeviceBean? = null + private lateinit var mMapLoadOk: () -> Unit + + //启动动画移动地图摄像机 + private var isMoveCamera = true + private var mPolyline: Polyline? = null + private var mRippleCircle: Circle? = null + private var mValueAnimator: ValueAnimator? = null + + // private var mAnimatorDuration=2000L + private var mRippleCirclePercent = 0.0 + private var mRippleCircleRadius = 200 + + companion object { + @JvmStatic + fun newInstance( + context: Context, + markerInfoView: MapMarkerInfoView, + mapDeviceNetView: MapDeviceNetView, + mapLoadOk: () -> Unit + ) = HomeMapGoogleMapFragment().apply { + mContext = context + mMarkerInfoView = markerInfoView + mMapDeviceNetView = mapDeviceNetView + mMapLoadOk = mapLoadOk + } + } + + fun setMapDeviceBean(mapDeviceBean: MapDeviceBean?) { + mMapDeviceBean = mapDeviceBean + } + + override fun liveDataObserve() { + //反地理编码成功返回 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(viewLifecycleOwner) { + //保存地理信息,方便传参给导航页面 + if (null == mDecPetAddressData) mDecPetAddressData = HistoryDataBean() + + mMapDeviceBean?.apply { +// val timeString = String.format( +// getString(R.string.map_current_update_time), +// DateUtils.getRelativeTimeSpanString( +// updateTime * 1000 +// ) +// ) + val timeString = + Utils.formatTime(latLonUpdateTime * 1000, Utils.DATE_FORMAT_PATTERN_EN6) + mDecPetAddressData?.let { history -> + //更新位置时间 + history.dayTime = timeString + //详细位置 + history.address = it + //传递位置信息给InfoWindowAdapter窗口 +// mMarker?.tag = history + +// showMarkerInfoWindow(history) + mMarkerInfoView.visibility = View.VISIBLE + mMarkerInfoView.setShowText(timeString, it) + } + } + } + } + + override fun onMapLoadOk(googleMap: GoogleMap) { +// initFenceList() + //设置自定义头部地点布局 + mGoogleMap?.apply { + setOnCameraMoveListener { + setMarkerInfoViewOffset() + } + getLastLocation() + } + + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + + override fun onRefreshPetCurrentLocationOk(googleMap: GoogleMap) { + if (null == mMarker) return + else startReverseGeocode() + } + + /** + * 刚开始的反编译地理位置 + */ + private fun startReverseGeocode() { + mMapDeviceBean?.apply { + mGeoCoderViewModel.getLatLonAddress(latitude, longitude, false) + setMarkerInfoViewOffset() + +// val time = System.currentTimeMillis() - mReverseGeocodeTime +// //判断有没有超过设置的刷新时间 +// if (time > mMapFragment.mMapDeviceBean!!.gnssInterval * 60 * 1000) { +// mGeoCoderViewModel.getLatLonAddress(mMapFragment.mMapDeviceBean!!.latitude, +// mMapFragment.mMapDeviceBean!!.longitude) +// } else { +// //传递位置信息给InfoWindowAdapter窗口 +// mMarker?.tag = mMapFragment.mHistoryDataBean +// showMarkerInfoWindow() +// } +// } + } + } + + fun setMarkerInfoViewOffset() { + mPetLatLng?.apply { + setMarkerInfoViewOffset(mMarkerInfoView, LatLng(latitude, longitude)) + setMapNetTypeViewOffset(mMapDeviceNetView, LatLng(latitude, longitude)) + } + } + + /** + * 点击大头针开始更新反地理编码 + */ + private fun reverseGeocode() { +// val time = System.currentTimeMillis() - mReverseGeocodeTime + //判断有没有超过设置的刷新时间 +// if (time > mMapFragment.mMapDeviceBean!!.gnssInterval * 60 * 1000) { +// mGeoCoderViewModel.getLatLonAddress(mMapFragment.mMapDeviceBean!!.latitude, +// mMapFragment.mMapDeviceBean!!.longitude) +// } + } + + /** + * 计算用户和宠物的距离 + */ + fun getUserAndPetDistance(): Double { + var distance = 0.0 + mUserLatLng?.apply { + mPetLatLng?.let { + distance = + Util.measureLatLonDistance(latitude, longitude, it.latitude, it.longitude) + } + } + return distance + } + + /** + * 画用户和宠物之间的虚线 + */ + private fun addUserAndPetLine(petLatLng: LatLng) { + mUserLatLng?.apply { + mGoogleMap?.let { + if (null == mPolyline) { + val polylineOptions = + PolylineOptions().clickable(false).width(AppUtils.dpToPx(1.5f)) + .color(ContextCompat.getColor(mContext!!, R.color.blue_color9)) + .geodesic(false) + mPolyline = it.addPolyline(polylineOptions) + mPolyline?.pattern = getDashedPatternStyle(18f) + } + mPolyline?.let { pLine -> + val latList = mutableListOf() + latList.add(petLatLng) + latList.add(this) + pLine.points = latList + } + } + } + } + + /** + * 初始化并添加水波纹圆 + */ + private fun addRippleCircle() { + mPetLatLng?.apply { + mGoogleMap?.let { + if (null == mRippleCircle) { + mRippleCircle = it.addCircle( + CircleOptions().center(this) + .radius(mRippleCircleRadius * mRippleCirclePercent) + .strokeWidth(AppUtils.dpToPx(2).toFloat()) + .fillColor(ContextCompat.getColor(mContext!!, R.color.black10)) + .strokeColor(ContextCompat.getColor(mContext!!, R.color.black10)) + ) + + setRippleCircleAnim() + } + } + } + } + + /** + * 设置水波纹动画 + */ + private fun setRippleCircleAnim() { + if (null == mValueAnimator) { + mValueAnimator = ViewUtil.instance.setValueAnimator( + 0.0, 100.0, 1500, LinearInterpolator() + ) { + mRippleCirclePercent = it.animatedValue as Float / 100.0 + mRippleCircle?.apply { + mPetLatLng?.let { petLatLng -> + center = petLatLng + radius = mRippleCircleRadius * mRippleCirclePercent + } + } + }?.apply { + repeatMode = ValueAnimator.RESTART + repeatCount = ValueAnimator.INFINITE + } + } + } + + /** + * 画线 + */ + fun addLine(latLng: LatLng) { +// addPolyline(mGoogleMap!!, latLng) +// addUserAndPetLine(latLng) + refreshPetCurrentLocation(latLng, needMoveCamera = isMoveCamera) + + mRippleCircle?.apply { + //一像素对应几米 + val pxDis = 2.0.pow(15.5 - getGoogleMapZoom()) + mRippleCircleRadius = (pxDis * 150).toInt() + fillColor = (ContextCompat.getColor(mContext!!, R.color.select_color5)) + strokeColor = (ContextCompat.getColor(mContext!!, R.color.select_color5)) + mValueAnimator?.duration = 2000 + } + } + + /** + * 画多条线 + */ + fun addLines(trackList: MutableList) { + val latLngList = mutableListOf() + for (i in 0 until trackList.size) { + trackList[i].apply { + val latLng = LatLng(latitude, longitude) + latLngList.add(latLng) + if (i == trackList.size - 1) { +// addUserAndPetLine(latLng) + refreshPetCurrentLocation(latLng, needMoveCamera = isMoveCamera) + } + } + } +// addPolyLines(mGoogleMap!!, latLngList) + + mRippleCircle?.apply { + //一像素对应几米 + val pxDis = 2.0.pow(15.5 - getGoogleMapZoom()) + mRippleCircleRadius = (pxDis * 150).toInt() + fillColor = (ContextCompat.getColor(mContext!!, R.color.select_color5)) + strokeColor = (ContextCompat.getColor(mContext!!, R.color.select_color5)) + mValueAnimator?.duration = 2000 + } + } + + /** + * 开始水波纹动画 + */ + fun startRippleCircleAnim() { + mPetLatLng?.apply { + //一像素对应几米 + val pxDis = 2.0.pow(15.5 - getGoogleMapZoom()) + mRippleCircleRadius = (pxDis * 150).toInt() +// addUserAndPetLine(this) + addRippleCircle() + } + } + + override fun onDetach() { + super.onDetach() + mValueAnimator?.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/MapLiveGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/MapLiveGoogleMapFragment.kt new file mode 100644 index 0000000..a52b84d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/MapLiveGoogleMapFragment.kt @@ -0,0 +1,63 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.annotation.SuppressLint +import android.content.Context +import com.abbidot.tracker.deprecated.ui.activity.map.MapLiveActivity +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.LatLng + +/** + *Created by .yzq on 2022/1/13/013. + * @link + * @description:实时跟踪map谷歌地图 + */ +class MapLiveGoogleMapFragment : BaseGoogleMapFragment() { + + private lateinit var mMapLiveActivity: MapLiveActivity + + //启动动画移动地图摄像机 + private var isMoveCamera = true + private var mPetCurrentLatLng: LatLng? = null + + companion object { + @JvmStatic + fun newInstance(context: Context) = MapLiveGoogleMapFragment().apply { + mContext = context + } + } + + override fun initData() { + mMapLiveActivity = mActivity as MapLiveActivity + super.initData() + setGoogleMapZoom(14f) + } + + @SuppressLint("MissingPermission") + override fun onMapLoadOk(googleMap: GoogleMap) { + +// initFenceList() + + //先开始获取围栏数据 + mMapLiveActivity.mFencesManageViewModel.getFenceList(mMapLiveActivity, + mMapLiveActivity.mPet.deviceId) + } + + /** + * 刷新当前宠物位置 + */ + fun refreshPetCurrentLocation() { + mPetCurrentLatLng?.let { + refreshPetCurrentLocation(it) + } + } + + /** + * 画线 + */ + fun addLine(latLng: LatLng) { + mPetCurrentLatLng = latLng + addPolyline(mGoogleMap!!, latLng) + refreshPetCurrentLocation(latLng, needMoveCamera = isMoveCamera) + isMoveCamera = false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/MarkerInfoWindowAdapter.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/MarkerInfoWindowAdapter.kt new file mode 100644 index 0000000..8707878 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/MarkerInfoWindowAdapter.kt @@ -0,0 +1,71 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.widget.TypefaceTextView +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.Marker +import com.mapbox.core.utils.TextUtils + +/** + *Created by .yzq on 2022/1/14/014. + * @link + * @description:自定义点击头像顶部出现布局 https://developers.google.cn/maps/documentation/android-sdk/infowindows + * + * 该接口包含两个供您实现的方法:getInfoWindow(Marker) 和 getInfoContents(Marker)。 + * API 将首先调用 getInfoWindow(Marker),如果系统返回 null,则会接着调用 getInfoContents(Marker)。 + * 如果仍返回 null,则系统会使用默认信息窗口。 + */ +class MarkerInfoWindowAdapter(context: Context) : GoogleMap.InfoWindowAdapter { + + private val mContext = context + + override fun getInfoContents(marker: Marker): View? { + val view = LayoutInflater.from(mContext).inflate(R.layout.marker_info_contents, null) + + // 1. Get tag +// val place = marker.tag as? Place ?: return null +// view.findViewById(R.id.text_view_title).text = place.name +// view.findViewById(R.id.text_view_address).text = place.address +// view.findViewById(R.id.text_view_rating).text = +// "Rating: %.2f".format(place.rating) + + LogUtil.d("MarkerInfoWindowAdapter------------>getInfoContents") + initData(view, marker) + + return view + } + + override fun getInfoWindow(marker: Marker): View? { + val view = LayoutInflater.from(mContext).inflate(R.layout.marker_info_contents, null) + LogUtil.d("MarkerInfoWindowAdapter------------>getInfoWindow") + initData(view, marker) + return view + } + + /** + * 初始化 + */ + private fun initData(view: View, marker: Marker) { + val locationTextView = view.findViewById(R.id.map_pet_current_location) + val locationUpdateTextView = + view.findViewById(R.id.map_pet_location_update_time) + + val tag = marker.tag as HistoryDataBean? + + LogUtil.d("$tag-----------") + if (null == tag) return + + if (TextUtils.isEmpty(tag.dayTime)) { + locationUpdateTextView.visibility = View.GONE + } else { + locationUpdateTextView.visibility = View.VISIBLE + locationUpdateTextView.text = tag.dayTime + } + locationTextView.text = tag.address + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/PreviewFencesGoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/PreviewFencesGoogleMapFragment.kt new file mode 100644 index 0000000..75dd1a4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/PreviewFencesGoogleMapFragment.kt @@ -0,0 +1,33 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.content.Context +import android.location.Location +import com.google.android.gms.maps.GoogleMap + +/** + *Created by .yzq on 2024/4/23/023. + * @link + * @description: + */ +class PreviewFencesGoogleMapFragment : BaseGoogleMapFragment() { + + private lateinit var mMapLoadOk: () -> Unit + + companion object { + @JvmStatic + fun newInstance(context: Context, mapLoadOk: () -> Unit) = + PreviewFencesGoogleMapFragment().apply { + mContext = context + mMapLoadOk = mapLoadOk + } + } + + override fun onMapLoadOk(googleMap: GoogleMap) { + getLastLocation() + //变量是否初始化 + if (::mMapLoadOk.isInitialized) mMapLoadOk() + } + + override fun locationSuccess(location: Location?) { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/WiFiZone2GoogleMapFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/WiFiZone2GoogleMapFragment.kt new file mode 100644 index 0000000..a734eaa --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/map/googlemap/WiFiZone2GoogleMapFragment.kt @@ -0,0 +1,101 @@ +package com.abbidot.tracker.ui.fragment.map.googlemap + +import android.content.Context +import android.location.Location +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.vm.GeoCoderViewModel +import com.google.android.gms.maps.GoogleMap +import com.google.android.gms.maps.model.LatLng +import com.google.android.gms.maps.model.Marker + +/** + *Created by .yzq on 2024/6/4/004. + * @link + * @description: + */ +class WiFiZone2GoogleMapFragment : BaseGoogleMapFragment() { + + private val mGeoCoderViewModel: GeoCoderViewModel by viewModels() + + //wifi zone当前位置信息 //保存地理信息,方便传参给其他页面 + var mDecWifiAddressData: HistoryDataBean? = null + + companion object { + @JvmStatic + fun newInstance(context: Context) = WiFiZone2GoogleMapFragment().apply { + mContext = context + } + } + + override fun onMapLoadOk(googleMap: GoogleMap) { + + mDecWifiAddressData = HistoryDataBean() + + googleMap.apply { + setOnMarkerDragListener(object : GoogleMap.OnMarkerDragListener { + override fun onMarkerDrag(marker: Marker) { + + } + + override fun onMarkerDragEnd(marker: Marker) { + marker.position.let { + geoCoderLatLon(it.latitude, it.longitude) + } + } + + override fun onMarkerDragStart(marker: Marker) { + } + }) + + setOnMapLongClickListener { latLng -> + mMarker?.position = latLng + geoCoderLatLon(latLng.latitude, latLng.longitude) + } + + //点击地图任意地方都不消失自定义头部地点布局 + setOnMapClickListener { + mMarker?.showInfoWindow() + } + } + getLastLocation() + } + + override fun liveDataObserve() { + //反地理编码成功返回 + mGeoCoderViewModel.mLatLonAddressLiveData.observe(viewLifecycleOwner) { + mMarker?.apply { + mDecWifiAddressData?.let { history -> + //更新位置时间 + // history.dayTime = timeString + //详细位置 + history.address = it + //传递位置信息给InfoWindowAdapter窗口 + showMarkerInfoWindow(history) + } + } + } + } + + private fun geoCoderLatLon(latitude: Double, longitude: Double) { + mDecWifiAddressData?.apply { + this.latitude = latitude + this.longitude = longitude + } + mGeoCoderViewModel.getLatLonAddress(latitude, longitude, false) + } + + override fun locationSuccess(location: Location?) { + location?.apply { + val latLng = LatLng(latitude, longitude) + moveCameraLocation(latLng, true) + if (null == mMarker) { + mMarker = addImageMarker( + latLng, R.drawable.icon_location_svg, anchorCenter = false, isDraggable = true + ) + } else mMarker?.position = latLng + geoCoderLatLon(latitude, longitude) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/pet/HomePetFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/pet/HomePetFragment.kt new file mode 100644 index 0000000..f032120 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/pet/HomePetFragment.kt @@ -0,0 +1,285 @@ +package com.abbidot.tracker.ui.fragment.pet + +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.HomePetFamilyAdapter +import com.abbidot.tracker.adapter.PetLabelsAdapter +import com.abbidot.tracker.adapter.PetsBadgesAdapter +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.PetAllInfoBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentHomePetBinding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.data.DataBoardActivity +import com.abbidot.tracker.ui.activity.pet.BadgesListActivity +import com.abbidot.tracker.ui.activity.pet.FamilyMembersActivity +import com.abbidot.tracker.ui.activity.pet.InviteFamilyActivity +import com.abbidot.tracker.ui.activity.pet.PetProfileActivity +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.PetInfoViewModel +import com.qmuiteam.qmui.util.QMUIDisplayHelper + + +/** + * 首页宠物切换页面 + * create an instance of this fragment. + */ +class HomePetFragment : BaseFragment(FragmentHomePetBinding::inflate) { + + private val mPetViewModel: PetInfoViewModel by viewModels() + + private lateinit var mHomeV2Activity: HomeV2Activity + + private lateinit var mPetLabelsAdapter: PetLabelsAdapter + private lateinit var mPetsBadgesAdapter: PetsBadgesAdapter + private lateinit var mFamilyAdapter: HomePetFamilyAdapter + + private var mPetAllInfoBean: PetAllInfoBean? = null + + companion object { + @JvmStatic + fun newInstance(context: Context) = HomePetFragment().apply { + mContext = context + } + } + + override fun initData() { + mHomeV2Activity = activity as HomeV2Activity + mViewBinding.apply { + root.setBackgroundResource(R.color.transparent) + mPetLabelsAdapter = PetLabelsAdapter(mContext!!, null) + ViewUtil.instance.setRecyclerViewFlexboxLayout( + mContext!!, + rvHomePetV2Label, + mPetLabelsAdapter, + top = QMUIDisplayHelper.dpToPx(8), + right = AppUtils.dpToPx(10) + ) + + //宠物勋章 + val badges = mutableListOf() + for (i in 0 until 4) badges.add(DataBean()) + mPetsBadgesAdapter = PetsBadgesAdapter(mContext!!, badges, 60) + ViewUtil.instance.setRecyclerViewGridLayout( + mContext!!, + rvHomePetV2Badges, + mPetsBadgesAdapter, + spanCount = 4, + AppUtils.dpToPx(60), + offset = AppUtils.dpToPx(48) + ) + + mFamilyAdapter = HomePetFamilyAdapter(mContext!!, null).apply { + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + mHomeV2Activity.getPet()?.let { pet -> + if (Util.checkPackageLimit(mHomeV2Activity, pet.deviceId)) { + return + } + } + goActivityNeedPet(InviteFamilyActivity::class.java) + } + }) + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + goActivityNeedPet(FamilyMembersActivity::class.java) + } + }) + } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvHomePetV2Family, mFamilyAdapter, top = QMUIDisplayHelper.dpToPx(16) + ) + + setOnClickListenerViews( + btnHomePetManageActivity, + btnHomePetFamilyManage, + btnHomePetMoreAbout, + btnHomePetBadgesViewAll + ) + } + + getPetAllInfoData() + } + + fun getPetAllInfoData() { + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + mHomeV2Activity.getPet(false)?.apply { + val share = MMKVUtil.getInt(MMKVKey.Shared) + //被邀请的不显示家庭邀请信息 + mViewBinding.apply { + if (share == ConstantInt.NoShare) { + btnHomePetMoreAbout.visibility = View.VISIBLE + btnHomePetManageActivity.visibility = View.VISIBLE + rlHomePetV2FamilyLayout.visibility = View.VISIBLE + } else { + btnHomePetMoreAbout.visibility = View.GONE + btnHomePetManageActivity.visibility = View.GONE + rlHomePetV2FamilyLayout.visibility = View.GONE + } + } + mPetViewModel.getPetAllInfo(mHomeV2Activity, petId) + } + } + + override fun liveDataObserve() { +// XEventBus.observe(viewLifecycleOwner, EventName.RefreshPet) { +// getPetAllInfoData() +// } + XEventBus.observe(viewLifecycleOwner, EventName.SetGoal) { + getPetAllInfoData() + } + XEventBus.observe(viewLifecycleOwner, EventName.RefreshFamily) { + getPetAllInfoData() + } + XEventBus.observe(viewLifecycleOwner, EventName.DeleteFamily) { + getPetAllInfoData() + } + + mPetViewModel.apply { + mPetAllInfoLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + mPetAllInfoBean = this + setInfoData(this) + } + } + }) + } + } + } + + private fun setLottieViewData(assetName: String) { + mViewBinding.homePetTopLottieView.apply { + if (isAnimating) cancelAnimation() + if (TextUtils.isEmpty(assetName)) { + cancelAnimation() + visibility = View.GONE + } else { + visibility = View.VISIBLE + setAnimation(assetName) + playAnimation() + } + } + } + + private fun setInfoData(petAllInfoBean: PetAllInfoBean) { + mViewBinding.apply { + petAllInfoBean.let { + it.petInfo?.let { pet -> + when (pet.petType) { + ConstantInt.DogPetType -> setLottieViewData("lottie/active_dog.json") + ConstantInt.CatPetType -> setLottieViewData("lottie/cat_and_ball.json") + else -> setLottieViewData("") + } + tvHomePetV2Type.text = mPetViewModel.getTypeName(mContext!!, pet.petType) + tvHomePetV2Breed.text = pet.breed + tvHomePetV2Birthday.text = Utils.stringToDate( + pet.birthdayDate, + Utils.DATE_FORMAT_PATTERN_CN, + Utils.DATE_FORMAT_PATTERN_EN7 + ) + tvHomePetV2Sex.text = mPetViewModel.getSexName(mContext!!, pet.gender) + val sexImage = if (pet.gender == ConstantInt.Man) { + R.drawable.icon_male_svg + } else { + R.drawable.icon_female_svg + } + tvHomePetV2Sex.setCompoundDrawablesWithIntrinsicBounds( + sexImage, 0, 0, 0 + ) + val labels = mPetViewModel.getLabelData(pet.labels) + mPetLabelsAdapter.setData(labels, true) + val badgeList = mPetViewModel.getBadges(mContext!!, pet.createDate, true) + mPetsBadgesAdapter.setData(badgeList, true) + } + it.activity?.let { act -> + //直接单位是min,不需要转换 + val goal = if (act.goal >= ConstantInt.MinSportGoal) act.goal + else ConstantInt.DefaultSportGoal + MMKVUtil.putInt(MMKVKey.ActivityGoal, goal) + val goalStr = + getString(R.string.txt_activity_goal) + ": $goal" + getString(R.string.txt_min) + tvHomePetV2ActivityGoal.text = goalStr + //单位是秒,需要转换min + tvHomePetV2ActivityTime.text = "${act.activeTime / 60}" + tvHomePetV2ActivityCalories.text = Utils.formatDecimal(act.calories, 2) + } + + mFamilyAdapter.setData(null) + it.invites?.let { family -> + mFamilyAdapter.setData(family, true) + if (family.size > 0) { + btnHomePetFamilyManage.visibility = View.VISIBLE + } else { + btnHomePetFamilyManage.visibility = View.GONE + } + } + } + } + } + + private fun goActivityNeedDeviceId(cls: Class<*>) { + mPetAllInfoBean?.petInfo?.apply { + Intent(mContext, cls).let { + it.putExtra(ConstantString.DeviceId, deviceId) + startActivity(it) + } + } + } + + private fun goActivityNeedPet(cls: Class<*>) { + mPetAllInfoBean?.petInfo?.apply { + Intent(mContext, cls).let { + it.putExtra(ConstantString.Pet, this) + startActivity(it) + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnHomePetManageActivity -> goActivityNeedPet(DataBoardActivity::class.java) + + btnHomePetFamilyManage -> goActivityNeedPet(FamilyMembersActivity::class.java) + + btnHomePetMoreAbout -> { + mPetAllInfoBean?.petInfo?.let { + val intent = Intent( + mContext, PetProfileActivity::class.java + ) + intent.putExtra(ConstantString.Edit, true) + intent.putExtra(ConstantString.Pet, it) + startActivity(intent) + } + } + + btnHomePetBadgesViewAll -> goActivityNeedPet(BadgesListActivity::class.java) + } + } + } + + override fun onDestroy() { + super.onDestroy() + mViewBinding.homePetTopLottieView.cancelAnimation() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/pet/PetV2Fragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/pet/PetV2Fragment.kt new file mode 100644 index 0000000..e6dc0ce --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/pet/PetV2Fragment.kt @@ -0,0 +1,142 @@ +package com.abbidot.tracker.ui.fragment.pet + +import android.content.Context +import android.view.View +import androidx.core.view.WindowInsetsCompat +import androidx.fragment.app.Fragment +import androidx.viewpager2.widget.ViewPager2 +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.adapter.ViewPagerAdapter +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.databinding.FragmentPetV2Binding +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.fragment.device.HomeTrackFragment +import com.abbidot.tracker.util.ViewUtil + + +/** + * 首页pet + * create an instance of this fragment. + */ +class PetV2Fragment : BaseFragment(FragmentPetV2Binding::inflate) { + + private lateinit var mFragments: MutableList + private lateinit var mHomeV2Activity: HomeV2Activity + + //当前这个页面显示的宠物下标 + private var mCurrentShowPetPos = -1 + + companion object { + @JvmStatic + fun newInstance(context: Context) = PetV2Fragment().apply { + mContext = context + } + } + + override fun initData() { + mHomeV2Activity = activity as HomeV2Activity + mFragments = mutableListOf( + HomePetFragment.newInstance(mContext!!), HomeTrackFragment.newInstance(mContext!!) + ) + + mViewBinding.apply { + mHomeV2Activity.edgeToEdgeAdapterBars( + root, WindowInsetsCompat.Type.statusBars() + ) + ilHomePetTopBar.ivTopPetBtnSmall.visibility = View.GONE + + rgSwitchPetTrack.setOnCheckedChangeListener { _, checkedId -> + when (checkedId) { + rbPetSwitch.id -> { + vp2SwitchPetTrack.currentItem = 0 + } + + rbTrackSwitch.id -> { + vp2SwitchPetTrack.currentItem = 1 + } + } + } + + vp2SwitchPetTrack.let { + it.adapter = ViewPagerAdapter(mFragments, requireActivity()) + ViewUtil.instance.setViewPager2Config( + it, object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + when (position) { + 0 -> rgSwitchPetTrack.check(rbPetSwitch.id) + 1 -> rgSwitchPetTrack.check(rbTrackSwitch.id) + } + } + }) + setOnClickListenerViews( + ilHomePetTopBar.homeDataPetNameSmall, ilHomePetTopBar.homeDataPetHeadSmall.root + ) + } + if (AppUtils.isDebug()) { + ilHomePetTopBar.homeDataPetHeadSmall.appHeadImage.setOnLongClickListener { + mHomeV2Activity.goToDebugActivity() + true + } + } + } + } + + override fun onResume() { + super.onResume() + //其他页面是否选择了宠物 + if (mCurrentShowPetPos != mHomeV2Activity.mSelectPetPosition) { + showPetNameAndHead(mHomeV2Activity.mSelectPetPosition) + } else { + (mFragments[1] as HomeTrackFragment).getPetTrackerInfoData() + } + (mFragments[1] as HomeTrackFragment).onResume() + } + + override fun onPause() { + super.onPause() + (mFragments[1] as HomeTrackFragment).onPause() + } + + /** + * 设置需要更新的标识 + */ + fun setNeedUpdateTag() { + mCurrentShowPetPos = -1 + } + + /** + * 显示选择宠物 + */ + fun showPetNameAndHead(position: Int) { + //变量初始化 + if (!::mHomeV2Activity.isInitialized) { + return + } + if (mHomeV2Activity.mPetList.size == 0) { + return + } + mCurrentShowPetPos = position + +// showLoading(true) + + ViewUtil.instance.selectPetDialogShow( + mContext!!, + mHomeV2Activity.mPetList, + position, + mViewBinding.ilHomePetTopBar.homeDataPetNameSmall, + mViewBinding.ilHomePetTopBar.homeDataPetHeadSmall.appHeadImage + ) + + (mFragments[0] as HomePetFragment).getPetAllInfoData() + (mFragments[1] as HomeTrackFragment).getPetTrackerInfoData() + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + ilHomePetTopBar.homeDataPetNameSmall, ilHomePetTopBar.homeDataPetHeadSmall.root -> mHomeV2Activity.selectPetDialog() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/subscribe/CreditCardPaymentFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/subscribe/CreditCardPaymentFragment.kt new file mode 100644 index 0000000..96a9ae6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/subscribe/CreditCardPaymentFragment.kt @@ -0,0 +1,218 @@ +package com.abbidot.tracker.ui.fragment.subscribe + +import android.content.Context +import android.content.Intent +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.abbidot.baselibrary.constant.ResultCode +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.CreditCardAdapter +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.databinding.FragmentCreditCardPaymentBinding +import com.abbidot.tracker.ui.activity.subscribe.AddCreditCardPaymentActivity +import com.abbidot.tracker.ui.activity.subscribe.PaymentMethodActivity +import com.abbidot.tracker.ui.activity.subscribe.PaymentSuccessActivity +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.vm.SubscriptionPayViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + + +/** + * 选择信用卡支付 + */ +class CreditCardPaymentFragment : BaseFragment( + FragmentCreditCardPaymentBinding::inflate +) { + + private val mSubscriptionPayViewModel: SubscriptionPayViewModel by viewModels() + private lateinit var mCreditCardAdapter: CreditCardAdapter + private lateinit var mPaymentMethodActivity: PaymentMethodActivity + + companion object { + @JvmStatic + fun newInstance(context: Context) = CreditCardPaymentFragment().apply { + mContext = context + } + } + + override fun initData() { + mViewBinding.apply { + mPaymentMethodActivity = mActivity!! as PaymentMethodActivity + root.setBackgroundResource(R.color.transparent) + mCreditCardAdapter = CreditCardAdapter(mContext!!, null).apply { + setOnAgainClickListener(object : BaseRecyclerAdapter.OnAgainClickListener { + override fun onAgainListener() { + val intent = Intent(mContext, AddCreditCardPaymentActivity::class.java) + launchActivity(mActivity!!, mActivityResultLauncher, intent) + } + }) + + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + val card = getData()[pos] + if (card.checked) return + for (i in 0 until getData().size) { + val data = getData()[i] + if (data.checked) { + data.checked = false + notifyItemChanged(i) + break + } + } + getData()[pos].checked = true + notifyItemChanged(pos) + } + }) + } + ViewUtil.instance.setRecyclerViewVerticalLinearLayout( + mContext!!, rvCreditCardPaymentList, mCreditCardAdapter, top = AppUtils.dpToPx(10) + ) + + setOnClickListenerViews(btnCreditCardPaymentAdd, btnMakePaymentCreditCard) + } + getCardData() + } + + private fun getCardData() { + viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) { + val list = MyDatabase.creditCardDao().findAll(MyDatabase.CreditCardTableName) + list?.let { + if (it.isNotEmpty()) mActivity!!.runOnUiThread { + val sortedByList = list.sortedByDescending { l -> l.isPrimary }.toMutableList() + setCheckAndPrimaryState(sortedByList) + mCreditCardAdapter.setData(sortedByList, true) + } + } + } + } + + fun goEditCard(cardBean: CreditCardBean) { + Intent(mContext, AddCreditCardPaymentActivity::class.java).let { + it.putExtra(ConstantString.Card, cardBean) + it.putExtra(ConstantString.Edit, true) + launchActivity(mActivity!!, mActivityResultLauncher, it) + } + } + + override fun liveDataObserve() { + mSubscriptionPayViewModel.apply { + mSubscriptionCustomerLiveData.observe(viewLifecycleOwner) { + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.let { o -> + mPaymentMethodActivity.mOrderBean?.apply { + customerId = o.customerId + mSubscriptionPayViewModel.createStripeOrder(this) + } + } + } + + override fun onRequestError(exceptionCode: String?) { + showNoCancelableLoading(false) + } + }) + } + + mCreateStripeOrderLiveData.observe(viewLifecycleOwner) { + showNoCancelableLoading(false) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + val intent = Intent(mContext, PaymentSuccessActivity::class.java) + this.rechargeType = mPaymentMethodActivity.mRechargeType + mPaymentMethodActivity.mOrderBean?.let { o -> + this.deviceId = o.deviceId + } + intent.putExtra(ConstantString.Amount, this) + startActivity(intent) + } + } + }) + } + } + } + + private fun setCheckAndPrimaryState(list: MutableList) { + if (list.size == 0) return + var haveCheck = false + for (l in list) { + if (l.checked) { + haveCheck = true + break + } + } + if (!haveCheck) { + list[0].checked = true + } + + mViewBinding.apply { + btnMakePaymentCreditCard.visibility = View.VISIBLE + tvCreditCardPaymentChoose.visibility = View.VISIBLE + btnCreditCardPaymentAdd.visibility = View.VISIBLE + } + } + + /** + * 接收添加信用卡返回结果 + */ + private val mActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { +// if (null != it.data) { +// if (it.resultCode == ResultCode.ResultCode_1) { +// val card = Util.getParcelableAdaptive( +// it.data!!, ConstantString.Card, CreditCardBean::class.java +// ) +// if (null != card) { +// val list = mCreditCardAdapter.getData() +// if (card.isPrimary) { +// for (l in list) { +// if (l.isPrimary) l.isPrimary = false +// if (l.checked) l.checked = false +// } +// } +// list.add(card) +// val sortedByList = +// list.sortedByDescending { l -> l.isPrimary }.toMutableList() +// setCheckAndPrimaryState(sortedByList) +// mCreditCardAdapter.setData(sortedByList, true) +// } +// } +// } + if (it.resultCode == ResultCode.ResultCode_1) { + getCardData() + } + } + + private fun startCreditCardPayment() { + val list = mCreditCardAdapter.getData() + for (card in list) { + if (card.checked) { + showNoCancelableLoading(true) + mSubscriptionPayViewModel.createCustomer(card) + break + } + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnCreditCardPaymentAdd -> { + val intent = Intent(mContext, AddCreditCardPaymentActivity::class.java) + launchActivity(mActivity!!, mActivityResultLauncher, intent) + } + + btnMakePaymentCreditCard -> startCreditCardPayment() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/ui/fragment/subscribe/PaypalPaymentFragment.kt b/app/src/main/java/com/abbidot/tracker/ui/fragment/subscribe/PaypalPaymentFragment.kt new file mode 100644 index 0000000..9fea323 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/ui/fragment/subscribe/PaypalPaymentFragment.kt @@ -0,0 +1,74 @@ +package com.abbidot.tracker.ui.fragment.subscribe + +import android.content.Context +import android.content.Intent +import android.view.View +import androidx.fragment.app.viewModels +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.databinding.FragmentPaypalPaymentBinding +import com.abbidot.tracker.ui.activity.WebViewActivity +import com.abbidot.tracker.ui.activity.subscribe.PaymentMethodActivity +import com.abbidot.tracker.vm.SubscriptionPayViewModel + + +/** + * Paypal支付方式 + */ +class PaypalPaymentFragment : + BaseFragment(FragmentPaypalPaymentBinding::inflate) { + + private val mSubscriptionPayViewModel: SubscriptionPayViewModel by viewModels() + private lateinit var mPaymentMethodActivity: PaymentMethodActivity + + companion object { + @JvmStatic + fun newInstance(context: Context) = PaypalPaymentFragment().apply { + mContext = context + } + } + + override fun initData() { + mPaymentMethodActivity = mActivity!! as PaymentMethodActivity + mViewBinding.apply { + root.setBackgroundResource(R.color.transparent) + setOnClickListenerViews(btnSureRedirectPaypal) + } + } + + override fun liveDataObserve() { + mSubscriptionPayViewModel.mCreatePaypalOrderLiveData.observe(viewLifecycleOwner) { + showNoCancelableLoading(false) + dealRequestResult(it, object : GetResultCallback { + override fun onResult(any: Any) { + it.getOrNull()?.apply { + val intent = Intent(mContext, WebViewActivity::class.java) + intent.putExtra( + ConstantString.Title, getString(R.string.txt_pay_order_payment_title) + ) + intent.putExtra(ConstantString.Url, url) + this.rechargeType = mPaymentMethodActivity.mRechargeType + mPaymentMethodActivity.mOrderBean?.let { o -> + this.deviceId = o.deviceId + } + intent.putExtra(ConstantString.Amount, this) + mPaymentMethodActivity.startActivity(intent) + } + } + }) + } + } + + override fun onClick(v: View?) { + mViewBinding.apply { + when (v!!) { + btnSureRedirectPaypal -> mPaymentMethodActivity.mOrderBean?.let { + showNoCancelableLoading(true) + mSubscriptionPayViewModel.createPaypalOrder(it) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/BleReportManage.kt b/app/src/main/java/com/abbidot/tracker/util/BleReportManage.kt new file mode 100644 index 0000000..f325f38 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/BleReportManage.kt @@ -0,0 +1,317 @@ +package com.abbidot.tracker.util + +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.bean.BleReportDataBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.bluetooth.SRBleCmdUtil +import com.abbidot.tracker.vm.SocketSendMessageViewModel +import com.clj.fastble.utils.HexUtil + +/** + *Created by .yzq on 2025/3/17/017. + * @link + * @description:蓝牙上报数据相关处理 + */ +class BleReportManage( + socketSendMessageViewModel: SocketSendMessageViewModel, socketUtilManage: SocketUtilManage +) { + + private var mSocketSendMessageViewModel = socketSendMessageViewModel + private var mSocketUtilManage = socketUtilManage + + //设备状态上报请求CMD + private val mTogetherDeviceStateCmdList = mutableListOf() + + //5.14 Live定位信息上报 + private val mLiveCmdList = mutableListOf() + private val mSportDataCmdList = mutableListOf() + private val mSleepDataCmdList = mutableListOf() + + private var mLatLonUpdateTime = 100L + + /** + * 处理蓝牙上报的数据 + */ + fun dealBleReportData(mac: String, data: ByteArray?) { + try { + data?.let { + val totalLength = SRBleCmdUtil.instance.byteToInt(data[0]) + val timeStampByteArray = data.sliceArray(2..5) + mTogetherDeviceStateCmdList.clear() + mTogetherDeviceStateCmdList.addAll(timeStampByteArray.toMutableList()) + mLiveCmdList.clear() + mLiveCmdList.addAll(timeStampByteArray.toMutableList()) + mSportDataCmdList.clear() + mSportDataCmdList.addAll(timeStampByteArray.toMutableList()) + mSleepDataCmdList.clear() + mSleepDataCmdList.addAll(timeStampByteArray.toMutableList()) + val utcTimeStamp = SRBleCmdUtil.instance.byte4ArrayToLong(timeStampByteArray) + LogUtil.e("状态更新时间=$utcTimeStamp") + val bleReportDataBean = BleReportDataBean().apply { + updateTime = utcTimeStamp +// latLonUpdateTime = utcTimeStamp + this.mac = mac + } + var length = SRBleCmdUtil.instance.byteToInt(data[6]) + var tempLength = totalLength - length + //设备状态 + if (SRBleCmdUtil.instance.byteToInt(data[7]) == ConstantInt.Type1) { + dealDeviceStateIsLive(data.sliceArray(8..8 + length - 2), bleReportDataBean) + //判断后面还有没有其他设备数据 + if (tempLength > 10) { + length = SRBleCmdUtil.instance.byteToInt(data[34]) + tempLength -= length + //定位信息 + if (SRBleCmdUtil.instance.byteToInt(data[35]) == ConstantInt.Type2) { + dealLocationData( + data.sliceArray(36..36 + length - 2), bleReportDataBean + ) + //判断后面还有没有其他设备数据 + if (tempLength > 10) { + length = SRBleCmdUtil.instance.byteToInt(data[50]) + if (SRBleCmdUtil.instance.byteToInt(data[51]) == ConstantInt.Type3) { + //运动睡眠 + dealSeepSportData( + data.sliceArray(52..52 + length - 2), bleReportDataBean + ) + } + } + } else if (SRBleCmdUtil.instance.byteToInt(data[35]) == ConstantInt.Type3) { + //判断是否有定位信息 + if (!bleReportDataBean.haveLocationData) { + val locationData = mutableListOf().apply { + //定位方式 + add(4) + add(1) + val lon = (LonAndLatUtil.currentUserGPSLon * 1000000).toLong() + val lonByteArray = SRBleCmdUtil.instance.longTo4ByteArray(lon) + addAll(lonByteArray.toMutableList()) + val lat = (LonAndLatUtil.currentUserGPSLat * 1000000).toLong() + val latByteArray = SRBleCmdUtil.instance.longTo4ByteArray(lat) + addAll(latByteArray.toMutableList()) + addAll(timeStampByteArray.toMutableList()) + } + LogUtil.e("设备没有定位信息,融合手机定位") + dealLocationData(locationData.toByteArray(), bleReportDataBean) + } + //运动睡眠 + dealSeepSportData( + data.sliceArray(36..36 + length - 2), bleReportDataBean + ) + } + } + + XEventBus.post(EventName.BleReportData, bleReportDataBean) + //有定位点直播才上报 + if (bleReportDataBean.liveFlag == ConstantInt.Type1 && mLiveCmdList.size > 30) { + mSocketSendMessageViewModel.sendCmd( + mSocketUtilManage, mac, 0x0E, mLiveCmdList + ) + } + if (mTogetherDeviceStateCmdList.size > 4) { + mSocketSendMessageViewModel.sendCmd( + mSocketUtilManage, mac, 0x0F, mTogetherDeviceStateCmdList + ) + } + if (mSportDataCmdList.size > 4) { + mSocketSendMessageViewModel.sendCmd( + mSocketUtilManage, mac, 0x0A, mSportDataCmdList + ) + } + if (mSleepDataCmdList.size > 4) { + mSocketSendMessageViewModel.sendCmd( + mSocketUtilManage, mac, 0x0B, mSleepDataCmdList + ) + } + } + } + } catch (e: Exception) { + LogUtil.e("解析蓝牙上报数据异常:${e.message}") + } + } + + /** + * 处理设备状态数据,返回是否正在直播状态 + */ + private fun dealDeviceStateIsLive(data: ByteArray, bleReportDataBean: BleReportDataBean) { + val gnssIntervalByteArray = data.sliceArray(4..5) + val cFencesIdByteArray = data.sliceArray(14..17) + val wiFiZoneMacByteArray = data.sliceArray(19..24) + mTogetherDeviceStateCmdList.apply { + add(data[12]) + add(data[13]) + //数据内容标志 + add(1) + //是否直播 + add(data[11]) + //red + add(data[8]) + add(data[9]) + add(data[10]) + //LED灯状态 + add(data[6]) + //LED灯开关状态 + add(data[7]) + //GNSS定位刷新时间 + addAll(gnssIntervalByteArray.toMutableList()) + //当前围栏id + addAll(cFencesIdByteArray.toMutableList()) + //当前GPS信号强度 + add(data[2]) + //当前电池电量 + add(data[3]) + //设备开关机状态 + add(data[0]) + //当前LTE网络信号 + add(data[1]) + //是否在WiFi zone + add(data[18]) + //WiFi zone Mac地址 + addAll(wiFiZoneMacByteArray.toMutableList()) + } + mLiveCmdList.apply { + //时区 + add(data[25]) + //当前LTE网络信号 + add(data[1]) + //当前GPS信号强度 + add(data[2]) + //当前电池电量 + add(data[3]) + //是否直播 + add(data[11]) + } + bleReportDataBean.apply { + haveDeviceStateData = true + LogUtil.e("设备状态数据=${HexUtil.formatHexString(data, true)}") + //设备状态 + powerSwitch = SRBleCmdUtil.instance.byteToInt(data[0]) + lteSignal = SRBleCmdUtil.instance.byteToInt(data[1]) + gpsSignal = SRBleCmdUtil.instance.byteToInt(data[2]) + batteryLevel = SRBleCmdUtil.instance.byteToInt(data[3]) + gnssInterval = SRBleCmdUtil.instance.byte2ArrayToInt(gnssIntervalByteArray) + ledMode = SRBleCmdUtil.instance.byteToInt(data[6]) + ledSwitch = SRBleCmdUtil.instance.byteToInt(data[7]) + ledRedValue = SRBleCmdUtil.instance.byteToInt(data[8]) + ledGreenValue = SRBleCmdUtil.instance.byteToInt(data[9]) + ledBlueValue = SRBleCmdUtil.instance.byteToInt(data[10]) + liveFlag = SRBleCmdUtil.instance.byteToInt(data[11]) + inSafeZone = SRBleCmdUtil.instance.byteToInt(data[12]) + inNoGoZone = SRBleCmdUtil.instance.byteToInt(data[13]) + cFencesId = SRBleCmdUtil.instance.byte4ArrayToLong(cFencesIdByteArray).toString() + inWifiZone = SRBleCmdUtil.instance.byteToInt(data[18]) + wiFiZoneMac = + HexUtil.formatHexString(wiFiZoneMacByteArray, true).split(" ", ":").toString() + timeZone = SRBleCmdUtil.instance.byteToInt(data[25]) - 12 + val msg = + "设备状态=$powerSwitch,lte=$lteSignal,gps=$gpsSignal,battery=$batteryLevel,gnss=$gnssInterval,ledMode=$ledMode,ledSwitch=$ledSwitch,ledRed=$ledRedValue,ledGreen=$ledGreenValue,ledBlue=$ledBlueValue,liveFlag=$liveFlag,safeZone=$inSafeZone,noGoZone=$inNoGoZone,inWiFiZone=$inWifiZone,wiFiZoneMac=$wiFiZoneMac\n" + LogUtil.e("解析后:$msg") + } + } + + /** + * 处理定位数据 + */ + private fun dealLocationData(data: ByteArray, bleReportDataBean: BleReportDataBean) { + LogUtil.e("定位数据=${HexUtil.formatHexString(data, true)}") + bleReportDataBean.apply { + //定位方式 + locationMode = SRBleCmdUtil.instance.byteToInt(data[0]) + val locationCount = SRBleCmdUtil.instance.byteToInt(data[1]) + var msg = "定位方式=$locationMode,定位点数=$locationCount," + if (locationCount == 0) { + return@apply + } + haveLocationData = true + + mLiveCmdList.apply { + add(data[0]) + add(data[1]) + } + val locations = mutableListOf() + for (i in 0 until locationCount) { + val location = HistoryDataBean() + val lonByteArray = data.sliceArray(2 + i * 12..5 + i * 12) + val latByteArray = data.sliceArray(6 + i * 12..9 + i * 12) + val timeStampByteArray = data.sliceArray(10 + i * 12..13 + i * 12) + location.timeStamp = SRBleCmdUtil.instance.byte4ArrayToLong(timeStampByteArray) + latLonUpdateTime = location.timeStamp + + if (i == locationCount - 1) { + if (mLatLonUpdateTime == latLonUpdateTime) { + LogUtil.e("同一个时间戳的经纬度不重复上传,$latLonUpdateTime") + } else { + mLatLonUpdateTime = latLonUpdateTime + mTogetherDeviceStateCmdList[6] = 3 + mTogetherDeviceStateCmdList.addAll(lonByteArray.toMutableList()) + mTogetherDeviceStateCmdList.addAll(latByteArray.toMutableList()) + mTogetherDeviceStateCmdList.add(data[0]) + mTogetherDeviceStateCmdList.add((timeZone + 12).toByte()) + //速度 + mTogetherDeviceStateCmdList.add(0) + mTogetherDeviceStateCmdList.add(0) + } + } + + mLiveCmdList.let { + it.addAll(timeStampByteArray.toMutableList()) + it.addAll(lonByteArray.toMutableList()) + it.addAll(latByteArray.toMutableList()) + //速度 + it.add(0) + it.add(0) + it.add(inSafeZone.toByte()) + it.add(inNoGoZone.toByte()) + } + + location.longitude = + SRBleCmdUtil.instance.byte4ArrayToLong(lonByteArray) / 1000000.0 + location.latitude = SRBleCmdUtil.instance.byte4ArrayToLong(latByteArray) / 1000000.0 + locations.add(location) + msg += "lon=${location.longitude},lat=${location.latitude},locationTimeStamp=${location.timeStamp}," + } + locationList = locations + LogUtil.e("解析后:$msg") + } + } + + /** + *处理运动睡眠数据 + */ + private fun dealSeepSportData(data: ByteArray, bleReportDataBean: BleReportDataBean) { + LogUtil.e("运动睡眠数据=${HexUtil.formatHexString(data, true)}") + val cActiveTimeByteArray = data.sliceArray(13..14) + val cSleepTimeByteArray = data.sliceArray(15..16) + mSportDataCmdList.apply { + addAll(0, data.sliceArray(0..11).toMutableList()) + add((bleReportDataBean.timeZone + 12).toByte()) + add(data[12]) + addAll(cActiveTimeByteArray.toMutableList()) + } + mSleepDataCmdList.apply { + addAll(0, cSleepTimeByteArray.toMutableList()) + add(0, data[12]) + addAll(0, data.sliceArray(0..3).toMutableList()) + add((bleReportDataBean.timeZone + 12).toByte()) + } + bleReportDataBean.apply { + haveActiveSleepData = true + year = SRBleCmdUtil.instance.byte2ArrayToInt(data.sliceArray(0..1)) + month = SRBleCmdUtil.instance.byteToInt(data[2]) + day = SRBleCmdUtil.instance.byteToInt(data[3]) + activeTime = SRBleCmdUtil.instance.byte4ArrayToLong(data.sliceArray(4..7)) + steps = SRBleCmdUtil.instance.byte4ArrayToLong(data.sliceArray(8..11)) + //几点的数据 + cTime = SRBleCmdUtil.instance.byteToInt(data[12]) + cActiveTime = SRBleCmdUtil.instance.byte2ArrayToInt(cActiveTimeByteArray) + cSleepTime = SRBleCmdUtil.instance.byte2ArrayToInt(cSleepTimeByteArray) + + val msg = + "year=$year,month=$month,day=$day,sportTime=$activeTime,step=$steps,timeZone=$timeZone,whatTime=$cTime,cSportTime=$cActiveTime,cSleepTime=$cSleepTime" + LogUtil.e("解析后:$msg") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/CoilEngine.kt b/app/src/main/java/com/abbidot/tracker/util/CoilEngine.kt new file mode 100644 index 0000000..12440c0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/CoilEngine.kt @@ -0,0 +1,62 @@ +package com.abbidot.tracker.util + +import android.content.Context +import android.graphics.Bitmap +import android.net.Uri +import android.widget.ImageView +import coil.ImageLoader +import coil.load +import coil.request.ImageRequest +import com.huantansheng.easyphotos.engine.ImageEngine + +/** + * 相册拍照显示图片封装 + */ +class CoilEngine private constructor() : ImageEngine { + + companion object { + val instance by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { CoilEngine() } + } + + private fun ImageView.loadImage( + uri: Uri, + imageLoader: ImageLoader = context.imageLoader(), + builder: ImageRequest.Builder.() -> Unit = {}, + ) { +// if (SDK_INT >= Build.VERSION_CODES.Q) { +// if (uri.startsWith("content://")) { +// load(Uri.parse(uri), imageLoader, builder) +// } else { +// load(uri, imageLoader, builder) +// } +// } else { +// load(File(uri), imageLoader, builder) +// } + load(uri, imageLoader, builder) + } + + private fun Context.imageLoader(): ImageLoader { + return ImageLoader.Builder(this).componentRegistry { +// add(VideoFrameFileFetcher(this@imageLoader)) +// add(VideoFrameUriFetcher(this@imageLoader)) +// add(VideoFrameDecoder(this@imageLoader)) + }.build() + } + + + override fun loadPhoto(context: Context, uri: Uri, imageView: ImageView) { + imageView.loadImage(uri) + } + + override fun loadGifAsBitmap(context: Context, gifUri: Uri, imageView: ImageView) { + imageView.loadImage(gifUri) + } + + override fun loadGif(context: Context, gifUri: Uri, imageView: ImageView) { + imageView.loadImage(gifUri) + } + + override fun getCacheBitmap(context: Context, uri: Uri, width: Int, height: Int): Bitmap? { + return null + } +} diff --git a/app/src/main/java/com/abbidot/tracker/util/FileUtil.kt b/app/src/main/java/com/abbidot/tracker/util/FileUtil.kt new file mode 100644 index 0000000..a0ff3af --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/FileUtil.kt @@ -0,0 +1,153 @@ +package com.abbidot.tracker.util + +import android.app.Activity +import android.content.Context +import android.os.Environment +import com.abbidot.baselibrary.util.LogUtil +import java.io.File +import java.math.BigDecimal + +/** + *Created by .yzq on 2022/3/24/024. + * @link + * @description: + */ +class FileUtil { + + companion object { + + /** + * 保存截屏图片目录名字 + */ + const val SCREENSHOT_DIRECTORY_NAME = "screenShot" + + /** + * 保存分享资源目录名字 + */ + const val SHARE_DIRECTORY_NAME = "share" + + /** + * 保存头像目录名字 + */ + const val HEAD_DIRECTORY_NAME = "headImage" + + /** + * data首页截屏图片文件名字 + */ + const val DATA_HISTORY_FILE_NAME = "dataHistory.png" + + /** + * 保存DFU固件目录名字 + */ + const val DFU_DIRECTORY_NAME = "dfu" + + /** + * 保存log文件目录名字 + */ + const val LOG_DIRECTORY_NAME = "log" + + /** + * 获取缓存目录 + *

+ * 当SD卡存在或者SD卡不可被移除的时候,就调用getExternalCacheDir()方法来获取缓存路径,否则就调用getCacheDir() + * 方法来获取缓存路径。前者获取到的就是 + * /sdcard/Android/data//cache 这个路径,而后者获取到的是 /data/data//cache 这个路径 + * 注意:这两种方式的缓存都会在卸载app的时候被系统清理到 + * ———————————————— + * 版权声明:本文为CSDN博主「Fannatic」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。 + * 原文链接:https://blog.csdn.net/u010001013/article/details/103243859 + * + * @param context + * @return + */ + private fun getDiskCacheDir(context: Context): String { + return if (Environment.MEDIA_MOUNTED == Environment.getExternalStorageState() || !Environment.isExternalStorageRemovable()) { + //存在外部SD卡上的,在手机里可以直接看到 + context.externalCacheDir!!.path + } else { + //存在app的内部存储上,要root以后,用Root Explorer 文件管理器才能看到 + context.cacheDir.path + } + } + + /** + * 创建、获取缓存目录下的文件夹 + */ + fun getDiskCacheDirectory(context: Context, directoryName: String): File { + val file = File(getDiskCacheDir(context) + "/" + directoryName) + if (!file.exists()) { + //只能创建单层目录 + val mkdir = file.mkdir() + LogUtil.e(directoryName + "目录创建" + mkdir) + } + return file + } + + /** + * 获取缓存太小MB + */ + fun getCacheSize(activity: Activity): String { + val finalSize: String + var cacheSize = activity.cacheDir.size() + if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { + cacheSize += activity.externalCacheDir?.size() ?: 0 + } + finalSize = getFormatSize(cacheSize) + return finalSize + } + + fun getFormatSize(size: Long): String { + if (size <= 0) { + return "0.00\tKb" + } + val kSize = size / 1024f + if (kSize < 1) { + val result = BigDecimal(kSize.toString()) + return result.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "\tKb" + } + + val mSize = kSize / 1024 + val result = BigDecimal(mSize.toString()) + return result.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString() + "\tM" + } + + /** + * 清除缓存 + */ + fun clearCache(activity: Activity) { + activity.cacheDir.clearFile() + if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) { + activity.externalCacheDir?.clearFile() + } + } + + /** + * 获取文件或者文件夹的大小 + */ + fun File.size(): Long { + var size = 0L + try { + if (isFile) { + size += length() + } + if (isDirectory) { + listFiles()?.forEach { + size += it.size() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + return size + } + + /** + * 删除文件或文件夹 + */ + fun File.clearFile() { + if (isFile) delete() + if (isDirectory) listFiles()?.forEach { it.clearFile() } + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/GoogleLocationUtil.java b/app/src/main/java/com/abbidot/tracker/util/GoogleLocationUtil.java new file mode 100644 index 0000000..2219969 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/GoogleLocationUtil.java @@ -0,0 +1,68 @@ +package com.abbidot.tracker.util; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.os.Looper; + +import androidx.annotation.NonNull; + +import com.abbidot.baselibrary.util.LogUtil; +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationCallback; +import com.google.android.gms.location.LocationRequest; +import com.google.android.gms.location.LocationResult; +import com.google.android.gms.location.LocationServices; + +/** + * Created by .yzq on 2023/10/18/018. + * + * @link + * @description: 谷歌定位,暂未使用 + */ +public class GoogleLocationUtil { + private LocationRequest mLocationRequest; + + @SuppressLint("MissingPermission") + public GoogleLocationUtil(Context context) { + createLocationRequest(); + + FusedLocationProviderClient fusedLocationClient = + LocationServices.getFusedLocationProviderClient(context); + + LogUtil.Companion.e("GoogleLocationUtil"); + + // LocationSettingsRequest.Builder builder = new LocationSettingsRequest.Builder(); + // + // builder.addLocationRequest(mLocationRequest); + + fusedLocationClient.requestLocationUpdates(mLocationRequest, new LocationCallback() { + @Override + public void onLocationResult(@NonNull LocationResult locationResult) { + super.onLocationResult(locationResult); + LogUtil.Companion.e("onLocationResult" + locationResult.getLastLocation()); + } + }, Looper.myLooper()); + + fusedLocationClient.getLastLocation().addOnSuccessListener((Activity) context, location -> { + if (location != null) { + // Logic to handle location object + LogUtil.Companion.e("addOnSuccessListener" + location.getLatitude()); + } else { + // Handle null case or Request periodic location update https://developer.android.com/training/location/receive-location-updates + LogUtil.Companion.e("addOnSuccessListener,location == null"); + } + }); + + } + + + private void createLocationRequest() { + mLocationRequest = LocationRequest.create(); + //更新间隔 + mLocationRequest.setInterval(2000); + mLocationRequest.setFastestInterval(1000); + mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY); + } + +} diff --git a/app/src/main/java/com/abbidot/tracker/util/ImageUtil.kt b/app/src/main/java/com/abbidot/tracker/util/ImageUtil.kt new file mode 100644 index 0000000..7a177ba --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/ImageUtil.kt @@ -0,0 +1,254 @@ +package com.abbidot.tracker.util + +import android.app.Activity +import android.content.Context +import android.content.res.Resources +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.graphics.Canvas +import android.graphics.Matrix +import android.graphics.Rect +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.VectorDrawable +import android.os.Build +import android.os.Handler +import android.os.Looper +import android.util.DisplayMetrics +import android.view.PixelCopy +import android.view.View +import android.view.Window +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat +import coil.ImageLoader +import coil.request.ImageRequest +import coil.transform.CircleCropTransformation +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.MyApplication +import com.abbidot.tracker.R +import com.qmuiteam.qmui.util.QMUIDisplayHelper + + +/** + *Created by .yzq on 2022/3/1/001. + * @link + * @description: + */ +object ImageUtil { + /** + * Return the bitmap of screen.截图 + * + * @param activity The activity. + * @return the bitmap of screen + */ + fun screenShot(activity: Activity): Bitmap? { + return screenShot(activity, false) + } + + /** + * Return the bitmap of screen. + * + * @param activity The activity. + * @param isDeleteStatusBar True to delete status bar, false otherwise.删除状态栏 + * @return the bitmap of screen + */ + private fun screenShot(activity: Activity, isDeleteStatusBar: Boolean): Bitmap? { + val decorView: View = activity.window.decorView + val bmp: Bitmap = viewToBitmap(decorView) + val dm = DisplayMetrics() + activity.windowManager.defaultDisplay.getMetrics(dm) + return if (isDeleteStatusBar) { + val statusBarHeight: Int = getStatusBarHeight() + LogUtil.e("$statusBarHeight") + Bitmap.createBitmap( + bmp, 0, statusBarHeight, dm.widthPixels, dm.heightPixels - statusBarHeight + ) + } else { + Bitmap.createBitmap(bmp, 0, 0, dm.widthPixels, dm.heightPixels) + } + } + + /** + * 获取状态栏高度 + */ + private fun getStatusBarHeight(): Int { + val resources: Resources = MyApplication.instance.resources + val resourceId: Int = resources.getIdentifier("status_bar_height", "dimen", "android") + return resources.getDimensionPixelSize(resourceId) + } + + /** + * View to bitmap. + *View转换为bitmap + * + * @param view The view. + * @return bitmap + */ + fun viewToBitmap(view: View): Bitmap { + val drawingCacheEnabled = view.isDrawingCacheEnabled + val willNotCacheDrawing = view.willNotCacheDrawing() + view.isDrawingCacheEnabled = true + view.setWillNotCacheDrawing(false) + var drawingCache = view.drawingCache + val bitmap: Bitmap + if (null == drawingCache || drawingCache.isRecycled) { + view.measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + ) + view.layout(0, 0, view.measuredWidth, view.measuredHeight) + view.buildDrawingCache() + drawingCache = view.drawingCache + if (null == drawingCache || drawingCache.isRecycled) { + bitmap = Bitmap.createBitmap( + view.measuredWidth, view.measuredHeight, Bitmap.Config.RGB_565 + ) + val canvas = Canvas(bitmap) + view.draw(canvas) + } else { + bitmap = Bitmap.createBitmap(drawingCache) + } + } else { + bitmap = Bitmap.createBitmap(drawingCache) + } + view.setWillNotCacheDrawing(willNotCacheDrawing) + view.isDrawingCacheEnabled = drawingCacheEnabled + return bitmap + } + + + /** + * View转换为bitmap + * 要注明“android:hardwareAccelerated="false"”,不然会报错Software rendering doesn't support hardware bitmaps + */ + fun viewToBitmap(view: View, bitmapCallback: (Bitmap) -> Unit) { + val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) + val canvas = Canvas(bitmap) + view.draw(canvas) + bitmapCallback.invoke(bitmap) + } + + /** + * View转换为bitmap + */ + fun viewToBitmap( + context: Context, view: View, window: Window, bitmapCallback: (Bitmap) -> Unit + ) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //三星手机未渲染显示view部分会有黑边 + LogUtil.d("captureView version O 以上") + // 高于 O 的,使用 PixelCopy + val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888) + bitmap.eraseColor(ContextCompat.getColor(context, R.color.share_color)) + val location = IntArray(2) + view.getLocationInWindow(location) + PixelCopy.request( + window, + Rect(location[0], location[1], location[0] + view.width, location[1] + view.height), + bitmap, + { + if (it == PixelCopy.SUCCESS) { + LogUtil.d("captureView 获取到 bitmap") + bitmapCallback.invoke(bitmap) + } else { + LogUtil.d("captureView 未获取到 bitmap, copyResult = $it") + } + }, + Handler(Looper.getMainLooper()) + ) + } else { + LogUtil.d("captureView version O 以下") + val tBitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.RGB_565) + tBitmap.eraseColor(ContextCompat.getColor(context, R.color.share_color)) + val canvas = Canvas(tBitmap) + view.draw(canvas) + canvas.setBitmap(null) + bitmapCallback.invoke(tBitmap) + } + } + + /** + * 等比缩放bitmap + * + * @param bm + * @param newWidth + * @param newHeight + * @return + */ + fun zoomImg(bm: Bitmap, newWidth: Int, newHeight: Int): Bitmap { + // 获得图片的宽高 + val width = bm.width + val height = bm.height + // 计算缩放比例 + val scaleWidth = newWidth.toFloat() / width + val scaleHeight = newHeight.toFloat() / height + // 取得想要缩放的matrix参数 + val matrix = Matrix() + matrix.postScale(scaleWidth, scaleHeight) + // 得到新的图片 + return Bitmap.createBitmap(bm, 0, 0, width, height, matrix, true) + } + + /** + * 合并两张bitmap为一张 + * + * @param background + * @param foreground + * @return Bitmap + */ + fun combineBitmap(background: Bitmap, foreground: Bitmap): Bitmap { + val bgWidth = background.width + val bgHeight = background.height + val fgWidth = foreground.width + val fgHeight = foreground.height + val newBitmap = Bitmap.createBitmap(bgWidth, bgHeight, Bitmap.Config.ARGB_8888) + val canvas = Canvas(newBitmap) + canvas.drawBitmap(background, 0f, 0f, null) + canvas.drawBitmap(foreground, (bgWidth - fgWidth) / 2f, (bgHeight - fgHeight) / 2f, null) + canvas.save() + canvas.restore() + return newBitmap + } + + /** + * 使用coil和协程获取网络图片Bitmap + */ + suspend fun getImageBitmapByUrl(context: Context, url: String): Bitmap { + val imageLoader = ImageLoader(context) + val request = + ImageRequest.Builder(context).data(url).transformations(CircleCropTransformation()) + //默认大小50dp + .size(QMUIDisplayHelper.dpToPx(50)).allowHardware(false).build() + val drawable = imageLoader.execute(request).drawable ?: return getBitmapFromDrawableAndSvg( + context, R.drawable.icon_dog_default_head_svg + ) ?: BitmapFactory.decodeResource( + context.resources, R.drawable.pic_avatar_df + ) + return (drawable as BitmapDrawable).bitmap + } + + /** + * 适配 SVG转Bitmap,drawableRes转Bitmap + */ + fun getBitmapFromDrawableAndSvg(context: Context, @DrawableRes drawableId: Int): Bitmap? { + return when (val drawable = ContextCompat.getDrawable(context, drawableId)) { + is BitmapDrawable -> { + drawable.bitmap + } + + is VectorDrawable, is VectorDrawableCompat -> { + val bitmap = Bitmap.createBitmap( + drawable.intrinsicWidth, drawable.intrinsicHeight, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + drawable.setBounds(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + bitmap + } + + else -> { + throw IllegalArgumentException("unsupported drawable type") + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/LonAndLatUtil.kt b/app/src/main/java/com/abbidot/tracker/util/LonAndLatUtil.kt new file mode 100644 index 0000000..88bcb4d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/LonAndLatUtil.kt @@ -0,0 +1,146 @@ +package com.abbidot.tracker.util + +import kotlin.math.abs +import kotlin.math.cos +import kotlin.math.sin +import kotlin.math.sqrt + + +/** + *Created by .yzq on 2022/6/25/025. + * @link + * @description: + */ +class LonAndLatUtil { + + companion object { + + //当前用户的GPS经纬度 + var currentUserGPSLat = 0.0 + var currentUserGPSLon = 0.0 + + private const val a = 6378245.0 + private const val ee = 0.00669342162296594323 + + /** + * GPS坐标(WGS84)转火星GCJ02坐标 + * @return + */ + fun convertFromWGS84ToGCJ02(latitude: Double, longitude: Double): DoubleArray { + //初始化坐标转换工具类,指定源坐标类型和坐标数据 + // sourceLatLng待转换坐标 +// val converter = CoordinateConverter().from(CoordinateConverter.CoordType.GPS) +// .coord(LatLng(latitude, longitude)) + //desLatLng 转换后的坐标 +// val desLatLng = converter.convert() + return convert(latitude, longitude) + } + + /** + * 火星GCJ02坐标转GPS坐标(WGS84) + * @return + */ + fun convertFromGCJ02ToWGS84(lat: Double, lon: Double): DoubleArray { + val dd = DoubleArray(2) + val initDelta = 0.01 + val threshold = 0.000001 + var dLat = initDelta + var dLon = initDelta + var mLat = lat - dLat + var mLon = lon - dLon + var pLat = lat + dLat + var pLon = lon + dLon + var wgsLat: Double + var wgsLon: Double + var i = 0 + while (true) { + wgsLat = (mLat + pLat) / 2 + wgsLon = (mLon + pLon) / 2 + val tmp = convertFromWGS84ToGCJ02(wgsLat, wgsLon) + dLat = tmp[0] - lat + dLon = tmp[1] - lon + if ((abs(dLat) < threshold) && (abs(dLon) < threshold)) { + break + } + if (dLat > 0) { + pLat = wgsLat + } else { + mLat = wgsLat + } + if (dLon > 0) { + pLon = wgsLon + } else { + mLon = wgsLon + } + if (++i > 1000) break + } + dd[0] = wgsLat + dd[1] = wgsLon + return dd + } + + /** + * 动态个数参数 + * vararg + * 数组多个元素传入函数的变长参数需要在数组名前加 * 号 + */ + fun getMinLatOrLon(vararg latOrLonList: Double): Double { + if (latOrLonList.isNotEmpty()) { + var minLatOrLon = latOrLonList[0] + latOrLonList.forEach { + if (minLatOrLon > it) minLatOrLon = it + } + return minLatOrLon + } + return 0.0 + } + + fun getMaxLatOrLon(vararg latOrLonList: Double): Double { + if (latOrLonList.isNotEmpty()) { + var maxLatOrLon = latOrLonList[0] + latOrLonList.forEach { + if (maxLatOrLon < it) maxLatOrLon = it + } + return maxLatOrLon + } + return 0.0 + } + + /** + * 把公式部分抽取出来 + */ + private fun convert(latitude: Double, longitude: Double): DoubleArray { + val dd = DoubleArray(2) + var dLat: Double = convertLat(longitude - 105.0, latitude - 35.0) + var dLon: Double = convertLon(longitude - 105.0, latitude - 35.0) + val radLat = latitude / 180.0 * Math.PI + var magic = sin(radLat) + magic = 1 - ee * magic * magic + val sqrtMagic = sqrt(magic) + dLat = dLat * 180.0 / (a * (1 - ee) / (magic * sqrtMagic) * Math.PI) + dLon = dLon * 180.0 / (a / sqrtMagic * cos(radLat) * Math.PI) + dd[0] = latitude + dLat + dd[1] = longitude + dLon + return dd + } + + private fun convertLat(x: Double, y: Double): Double { + var ret = -100.0 + 2.0 * x + 3.0 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * sqrt( + if (x > 0) x else -x + ) + ret += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0 + ret += (20.0 * sin(y * Math.PI) + 40.0 * sin(y / 3.0 * Math.PI)) * 2.0 / 3.0 + ret += (160.0 * sin(y / 12.0 * Math.PI) + 320 * sin(y * Math.PI / 30.0)) * 2.0 / 3.0 + return ret + } + + private fun convertLon(x: Double, y: Double): Double { + var ret = + 300.0 + x + 2.0 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * sqrt(if (x > 0) x else -x) + ret += (20.0 * sin(6.0 * x * Math.PI) + 20.0 * sin(2.0 * x * Math.PI)) * 2.0 / 3.0 + ret += (20.0 * Math.sin(x * Math.PI) + 40.0 * sin(x / 3.0 * Math.PI)) * 2.0 / 3.0 + ret += (150.0 * sin(x / 12.0 * Math.PI) + 300.0 * sin(x / 30.0 * Math.PI)) * 2.0 / 3.0 + return ret + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/NumberPickerValueFill0Format.kt b/app/src/main/java/com/abbidot/tracker/util/NumberPickerValueFill0Format.kt new file mode 100644 index 0000000..80c27a4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/NumberPickerValueFill0Format.kt @@ -0,0 +1,17 @@ +package com.abbidot.tracker.util + +import android.widget.NumberPicker + +/** + *Created by .yzq on 2024/2/21/021. + * @link + * @description: + */ +class NumberPickerValueFill0Format: NumberPicker.Formatter { + override fun format(value: Int): String { +// if (value < 10) { +// return "0$value" +// } + return "$value" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/SocketUtilManage.kt b/app/src/main/java/com/abbidot/tracker/util/SocketUtilManage.kt new file mode 100644 index 0000000..c7a7f7f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/SocketUtilManage.kt @@ -0,0 +1,223 @@ +package com.abbidot.tracker.util + +import android.content.Context +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.clj.fastble.utils.HexUtil +import com.easysocket.EasySocket +import com.easysocket.config.EasySocketOptions +import com.easysocket.connection.action.SocketStatus +import com.easysocket.entity.OriginReadData +import com.easysocket.entity.SocketAddress +import com.easysocket.interfaces.conn.ISocketActionListener + + +/** + *Created by .yzq on 2025/4/18/018. + * @link + * @description: + */ +class SocketUtilManage { + + private val iP = "3.101.8.36" +// private val iP = "192.168.0.69" + + //默认国外服务器端口 + private var port = 6868 + + @OptIn(ExperimentalUnsignedTypes::class) + private val crcTable = ushortArrayOf( + 0x0000u, + 0xCC01u, + 0xD801u, + 0x1400u, + 0xF001u, + 0x3C00u, + 0x2800u, + 0xE401u, + 0xA001u, + 0x6C00u, + 0x7800u, + 0xB401u, + 0x5000u, + 0x9C01u, + 0x8801u, + 0x4400u + ) + + /** + * 初始化EasySocket + */ + fun initEasySocket(context: Context) { + if (AppUtils.isChina()) { + //国内socket端口号 + port = 9168 + } + //socket配置 + val options = EasySocketOptions.Builder().setSocketAddress(SocketAddress(iP, port)) + .setMaxWriteBytes(300) + // 强烈建议定义一个消息协议,方便解决 socket黏包、分包的问题 + // .setReaderProtocol(new DefaultMessageProtocol()) // 默认的消息协议 + .build() + //初始化EasySocket + EasySocket.getInstance().createConnection(options, context).setDebug(AppUtils.isDebug()) + + //监听socket相关行为 + EasySocket.getInstance().subscribeSocketAction(object : ISocketActionListener { + override fun onSocketConnSuccess(socketAddress: SocketAddress?) { + LogUtil.e("socket连接成功") + } + + override fun onSocketConnFail(socketAddress: SocketAddress?, isNeedReconnect: Boolean) { + LogUtil.e("socket连接失败,是否需要重连:$isNeedReconnect") + } + + override fun onSocketDisconnect( + socketAddress: SocketAddress?, isNeedReconnect: Boolean + ) { + LogUtil.e("socket断开连接,是否需要重连:$isNeedReconnect") + } + + override fun onSocketResponse( + socketAddress: SocketAddress?, originReadData: OriginReadData? + ) { + LogUtil.d("socket监听器收到对象数据=" + originReadData?.bodyString) + } + + override fun onSocketResponse(socketAddress: SocketAddress?, readData: String?) { + LogUtil.d("socket监听器收到字符串数据=$readData") + } + + override fun onSocketResponse(socketAddress: SocketAddress?, readData: ByteArray?) { + val data = HexUtil.formatHexString(readData, true) + LogUtil.e("socket监听器收到字节数组数据=$data") + } + }) + } + + /** + * 单字节 CRC16 计算 + */ + @OptIn(ExperimentalUnsignedTypes::class) + private fun crcGet16(crc: UShort, byte: UByte): UShort { + var currentCrc = crc.toInt() + val lowerNibble = byte.toInt() and 0x0F + val upperNibble = (byte.toInt() shr 4) and 0x0F + + // 处理低 4 位 + var tmp = crcTable[currentCrc and 0x0F].toInt() + currentCrc = (currentCrc shr 4) and 0x0FFF + currentCrc = currentCrc xor tmp xor crcTable[lowerNibble].toInt() + + // 处理高 4 位 + tmp = crcTable[currentCrc and 0x0F].toInt() + currentCrc = (currentCrc shr 4) and 0x0FFF + currentCrc = currentCrc xor tmp xor crcTable[upperNibble].toInt() + + return currentCrc.toUShort() + } + + /** + * 计算 CRC16(处理字节数组) + */ + private fun crcCalc16(data: ByteArray): UShort { + // 初始值(初始 CRC 为 0) + var crc = 0 + data.forEach { byte -> + crc = crcGet16(crc.toUShort(), byte.toUByte()).toInt() + } + return crc.toUShort() + } + + /** + * 大端序转换:0x3C (高字节) 0x30 (低字节) + */ + private fun UShort.toBytesBE(): ByteArray = byteArrayOf( + (this.toInt() shr 8).toByte(), this.toByte() + ) + + /** + * 小端序转换:0x30 (低字节) 0x3C (高字节) + */ + private fun UShort.toBytesLE(): ByteArray = byteArrayOf( + this.toByte(), (this.toInt() shr 8).toByte() + ) + + /** + * 发送一个的消息, + */ + fun sendMessage(mac: String, msgID: Byte, msgData: MutableList) { + try { +// val data = byteArrayOf(0x01, 0x03, 0x00, 0x01) +// //输出 15408 +// val crc16 = crcCalc16(data) +// // 输出 0x3c30 +// val bytes = crc16.toBytesBE() +// LogUtil.e("CRC16: ${HexUtil.formatHexString(bytes, true)},$crc16") + + val macList = mac.split(":") + val macLen = macList.size + val macByteArray = ByteArray(macLen) + for (i in 0 until macLen) { + macByteArray[i] = macList[i].toInt(16).toByte() + } + val deviceIdByteArray = byteArrayOf( + macByteArray[macLen - 1], + macByteArray[macLen - 2], + macByteArray[macLen - 3], + macByteArray[macLen - 4] + ) + + val cmdList = mutableListOf() + //Device ID ,4 Bytes + cmdList.addAll(deviceIdByteArray.toMutableList()) + //BLE MAC地址,6 Bytes + cmdList.addAll(macByteArray.toMutableList()) + //Seq No.:包顺序号,2 Bytes + cmdList.add(0) + cmdList.add(1) + //后续消息数:1 Byte + cmdList.add(0) + //消息ID:MsgID,1 Byte + cmdList.add(msgID) + //消息数据:Msg Data + cmdList.addAll(msgData.toMutableList()) + val packetLength = 19 + msgData.size + //包长:从包起始符到包结束符整个数据包的长度,1 Byte + cmdList.add(0, packetLength.toByte()) + val crc = crcCalc16(cmdList.toByteArray()) + val crc16ByteArray = crc.toBytesBE() + //校验值:CRC16,2 Bytes + cmdList.addAll(crc16ByteArray.toMutableList()) + //包结束符:字符‘#’,1 Byte + cmdList.add('#'.code.toByte()) + //包起始符:字符‘@’,1 Byte + cmdList.add(0, '@'.code.toByte()) + val cmdByteArray = cmdList.toByteArray() + + val cmdByteArrayFormat = HexUtil.formatHexString(cmdByteArray, true) + LogUtil.e("$mac==${cmdList.size}个字节,$cmdByteArrayFormat") + + val socketStatus = EasySocket.getInstance().defconnection.connectionStatus + //socket状态已连接 + if (socketStatus == SocketStatus.SOCKET_CONNECTED) { + //发送 + EasySocket.getInstance().upMessage(cmdByteArray) + } + } catch (e: Exception) { + LogUtil.e("socket 发消息异常:${e.message}") + } + } + + fun destroy() { + try { + //destroyConnection()代表销毁整个连接状态,跟disconnect()不一样,如果此时要进行socket连接需要重新创建一个连接 + EasySocket.getInstance().destroyConnection() + //注销监听socket相关行为 + EasySocket.getInstance().defOptions.reconnectionManager.detach() + LogUtil.e("socket销毁连接") + } catch (e: Exception) { + LogUtil.e("socket销毁连接异常:${e.message}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/ThirdPartyUtil.kt b/app/src/main/java/com/abbidot/tracker/util/ThirdPartyUtil.kt new file mode 100644 index 0000000..44613a5 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/ThirdPartyUtil.kt @@ -0,0 +1,49 @@ +package com.abbidot.tracker.util + +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.MyApplication +import com.abbidot.tracker.base.BaseActivity +import com.hjq.toast.Toaster +import com.tencent.mm.opensdk.modelmsg.SendAuth +import com.tencent.mm.opensdk.openapi.IWXAPI +import com.tencent.mm.opensdk.openapi.WXAPIFactory + +/** + *Created by .yzq on 2022/7/8/008. + * @link + * @description:第三方授权工具类,微信和支付宝 + */ +class ThirdPartyUtil { + + companion object { + // APP_ID 替换为你的应用从官方网站申请到的合法appID + const val WXAppID = "wx19e9c16c9af43924" + + //IWXAPI单例模式, 是第三方 app 和微信通信的 openApi 接口 + val mApi: IWXAPI by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + LogUtil.e("创建微信Api") + // 通过 WXAPIFactory 工厂,获取 IWXAPI 的实例 + WXAPIFactory.createWXAPI(MyApplication.instance, WXAppID, true) + } + + val instance: ThirdPartyUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + ThirdPartyUtil() + } + } + + /** + * 开始微信登录 + */ + fun startWXLogin(activity: BaseActivity<*>) { + if (!mApi.isWXAppInstalled) { + Toaster.show("没有安装微信") + return + } + activity.showLoading(true) + // send oauth request + val req = SendAuth.Req() + req.scope = "snsapi_userinfo" + req.state = "wechat_sdk_demo_test" + mApi.sendReq(req) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/Util.kt b/app/src/main/java/com/abbidot/tracker/util/Util.kt new file mode 100644 index 0000000..39d506e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/Util.kt @@ -0,0 +1,786 @@ +package com.abbidot.tracker.util + +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.location.LocationManager +import android.os.Build +import android.os.Parcelable +import android.provider.Settings +import androidx.lifecycle.LifecycleCoroutineScope +import cn.jpush.android.api.JPushInterface +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.BleReportDataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.MessageBean +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.dialog.PackageUpgradeDialog +import com.abbidot.tracker.ui.activity.HomeV2Activity +import com.abbidot.tracker.ui.activity.SayHelloActivity +import com.abbidot.tracker.ui.activity.device.AddNewTracker1Activity +import com.abbidot.tracker.ui.activity.pet.ShowInviteDeviceActivity +import com.abbidot.tracker.ui.activity.pet.first.FirstSetPetTypeActivity +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.baidu.mapapi.utils.DistanceUtil +import com.clj.fastble.BleManager +import com.google.android.gms.maps.model.LatLng +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.XXPermissions +import com.hjq.permissions.permission.PermissionLists +import com.hjq.permissions.permission.base.IPermission +import com.hjq.toast.Toaster +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import okhttp3.MediaType.Companion.toMediaTypeOrNull +import okhttp3.MultipartBody +import okhttp3.RequestBody.Companion.asRequestBody +import java.io.File +import java.util.Random +import kotlin.math.acos +import kotlin.math.cos +import kotlin.math.sin + + +/** + *Created by .yzq on 2022/1/19/019. + * @link + * @description:工具类 + */ +class Util { + + companion object { + + /** + * 计算2个经纬度间距离,结果返回 米 + */ + fun measureLatLonDistance( + startLat: Double, startLon: Double, endLat: Double, endLon: Double + ): Double { + val dis = if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + //百度地图计算两点之间的直线距离,单位:米,经纬度百度输出的GCJ02坐标,不需要转换 + DistanceUtil.getDistance( + com.baidu.mapapi.model.LatLng(startLat, startLon), + com.baidu.mapapi.model.LatLng(endLat, endLon) + ) + } else { + //mapbox计算2个经纬度间距离,结果返回 米 +// TurfMeasurement.distance(Point.fromLngLat(startLon, startLat), +// Point.fromLngLat(endLon, endLat), TurfConstants.UNIT_METERS) + DistanceUtil.getDistance( + com.baidu.mapapi.model.LatLng(startLat, startLon), + com.baidu.mapapi.model.LatLng(endLat, endLon) + ) + } + + return Utils.formatDecimal(dis).toDouble() + } + + /** + * 转换英制单位 + * @param type 0=in 1=lbs 2=mph 3=ft + */ + fun metricConvertUnits(value: Float, type: Int, keepNum: Int = 1): Float { + var convertValue = value + val measureUnit = getMeasureSpUnit() + if (measureUnit == ConstantInt.Type1) { + when (type) { + ConstantInt.Type0 -> convertValue = cmToIn(value.toDouble()).toFloat() + ConstantInt.Type1 -> convertValue = kgToLbs(value.toDouble()).toFloat() + ConstantInt.Type2 -> convertValue = kmhToMph(value.toDouble()).toFloat() + ConstantInt.Type3 -> convertValue = mToFT(value.toDouble()).toFloat() + ConstantInt.Type4 -> convertValue = kmToMile(value.toDouble()).toFloat() + } + } + + return Utils.formatDecimal(convertValue, keepNum).toFloat() + } + + /** + * 转换公制单位 + * @param type 0=cm 1=kg 2=km/h 3=m + */ + fun convertMetricUnits(value: Float, type: Int, keepNum: Int = 1): Float { + var convertValue = value + val measureUnit = getMeasureSpUnit() + if (measureUnit == ConstantInt.Type1) { + when (type) { + ConstantInt.Type0 -> convertValue = inToCm(value.toDouble()).toFloat() + ConstantInt.Type1 -> convertValue = lbsToKg(value.toDouble()).toFloat() + } + } + + return Utils.formatDecimal(convertValue, keepNum).toFloat() + } + + + /** + * 获取单位 + */ + fun getMeasureSpUnit() = MMKVUtil.getInt(MMKVKey.MeasureUnit, 1) + + /** + * 获取单位名称 + */ + fun getMeasureUnitName(context: Context): String { + return if (getMeasureSpUnit() == ConstantInt.Type1) context.getString(R.string.txt_imperial) + else context.getString(R.string.txt_metric) + } + + /** + * km/h转换mph + */ + private fun kmhToMph(value: Double): Double { + return value / 0.6213712 + } + + private fun mToFT(value: Double): Double { + return value * 3.2808399 + } + + private fun kmToMile(value: Double): Double { + return value * 0.621 + } + + private fun cmToIn(value: Double): Double { + return value * 0.3937 + } + + private fun inToCm(value: Double): Double { + return value * 2.54 + } + + private fun kgToLbs(value: Double): Double { + return value * 2.2065 + } + + private fun lbsToKg(value: Double): Double { + return value * 0.4532 + } + + /** + * 获取宠物的体重身高范围 + * @param type 是身高还是体重 0身高 1体重 + * @param valueType 最大值还是最小值 0大 1小 + */ + fun getPetHeightAndWeightRangeValue(petType: Int, type: Int, valueType: Int): Int { + var value = 0 + val measureUnit = getMeasureSpUnit() + if (petType == ConstantInt.CatPetType) { + //身高 + if (type == ConstantInt.HeightType) { + if (valueType == ConstantInt.MaxType) { + if (measureUnit == ConstantInt.Type1) value = 25 + else if (measureUnit == ConstantInt.Type2) value = 65 + } else if (valueType == ConstantInt.MinType) { + if (measureUnit == ConstantInt.Type1) value = 3 + else if (measureUnit == ConstantInt.Type2) value = 7 + } + } else if (type == ConstantInt.WeightType) { + //1体重 + if (valueType == ConstantInt.MaxType) { + if (measureUnit == ConstantInt.Type1) value = 30 + else if (measureUnit == ConstantInt.Type2) value = 13 + } else if (valueType == ConstantInt.MinType) { + if (measureUnit == ConstantInt.Type1) value = 1 + else if (measureUnit == ConstantInt.Type2) value = 0 + } + } + } else if (petType == ConstantInt.DogPetType) { + //身高 + if (type == ConstantInt.HeightType) { + if (valueType == ConstantInt.MaxType) { + if (measureUnit == ConstantInt.Type1) value = 50 + else if (measureUnit == ConstantInt.Type2) value = 130 + } else if (valueType == ConstantInt.MinType) { + if (measureUnit == ConstantInt.Type1) value = 3 + else if (measureUnit == ConstantInt.Type2) value = 7 + } + } else if (type == ConstantInt.WeightType) { + //1体重 + if (valueType == ConstantInt.MaxType) { + if (measureUnit == ConstantInt.Type1) value = 200 + else if (measureUnit == ConstantInt.Type2) value = 90 + } else if (valueType == ConstantInt.MinType) { + if (measureUnit == ConstantInt.Type1) value = 1 + else if (measureUnit == ConstantInt.Type2) value = 0 + } + } + } else { + //身高 + if (type == ConstantInt.HeightType) { + if (valueType == ConstantInt.MaxType) { + if (measureUnit == ConstantInt.Type1) value = 20 + else if (measureUnit == ConstantInt.Type2) value = 50 + } else if (valueType == ConstantInt.MinType) { + if (measureUnit == ConstantInt.Type1) value = 1 + else if (measureUnit == ConstantInt.Type2) value = 1 + } + } else if (type == ConstantInt.WeightType) { + //1体重 + if (valueType == ConstantInt.MaxType) { + if (measureUnit == ConstantInt.Type1) value = 45 + else if (measureUnit == ConstantInt.Type2) value = 20 + } else if (valueType == ConstantInt.MinType) { + if (measureUnit == ConstantInt.Type1) value = 1 + else if (measureUnit == ConstantInt.Type2) value = 1 + } + } + } + return value + } + + /** + * 获取宠物的体重身高默认值 + * @param type 是身高还是体重 0身高 1体重 + */ + fun getPetHeightAndWeightDefaultValue(petType: Int, type: Int): Int { + var value = 0 + val measureUnit = getMeasureSpUnit() + if (petType == ConstantInt.CatPetType) { + //身高 + if (type == ConstantInt.HeightType) { + if (measureUnit == ConstantInt.Type1) value = 10 + else if (measureUnit == ConstantInt.Type2) value = 25 + } else if (type == ConstantInt.WeightType) { + //1体重 + if (measureUnit == ConstantInt.Type1) value = 9 + else if (measureUnit == ConstantInt.Type2) value = 4 + } + } else if (petType == ConstantInt.DogPetType) { + //身高 + if (type == ConstantInt.HeightType) { + if (measureUnit == ConstantInt.Type1) value = 20 + else if (measureUnit == ConstantInt.Type2) value = 50 + } else if (type == ConstantInt.WeightType) { + //1体重 + if (measureUnit == ConstantInt.Type1) value = 22 + else if (measureUnit == ConstantInt.Type2) value = 10 + } + } else { + //身高 + if (type == ConstantInt.HeightType) { + if (measureUnit == ConstantInt.Type1) value = 1 + else if (measureUnit == ConstantInt.Type2) value = 1 + } else if (type == ConstantInt.WeightType) { + //1体重 + if (measureUnit == ConstantInt.Type1) value = 1 + else if (measureUnit == ConstantInt.Type2) value = 1 + } + } + return value + } + + /** + * 获取单位 + * @param type 0=cm 1=kg 2=km/h 3=m 4=km + */ + fun getMetricUnits(context: Context, type: Int): String { + var unit = "" + val measureUnit = getMeasureSpUnit() + when (type) { + ConstantInt.Type0 -> { + if (measureUnit == ConstantInt.Type2) unit = context.getString(R.string.txt_cm) + else if (measureUnit == ConstantInt.Type1) unit = + context.getString(R.string.txt_in) + } + + ConstantInt.Type1 -> { + if (measureUnit == ConstantInt.Type2) unit = context.getString(R.string.txt_kg) + else if (measureUnit == ConstantInt.Type1) unit = + context.getString(R.string.txt_lb) + } + + ConstantInt.Type2 -> { + if (measureUnit == ConstantInt.Type2) unit = + context.getString(R.string.txt_km_h) + else if (measureUnit == ConstantInt.Type1) unit = + context.getString(R.string.txt_mph) + } + + ConstantInt.Type3 -> { + if (measureUnit == ConstantInt.Type2) unit = context.getString(R.string.txt_m) + else if (measureUnit == ConstantInt.Type1) unit = + context.getString(R.string.txt_ft) + } + + ConstantInt.Type4 -> { + if (measureUnit == ConstantInt.Type2) unit = context.getString(R.string.txt_km) + else if (measureUnit == ConstantInt.Type1) unit = + context.getString(R.string.txt_distance_unit) + } + } + return unit + } + + /** + * 计算两点之间距离 + * @param start + * @param end + * @return 米 + */ + fun getDistance(start: LatLng, end: LatLng): Double { + val lat1 = Math.PI / 180 * start.latitude + val lat2 = Math.PI / 180 * end.latitude + val lon1 = Math.PI / 180 * start.longitude + val lon2 = Math.PI / 180 * end.longitude + //地球半径 + val r = 6371.393 + //两点间距离 km,如果想要米的话,结果*1000就可以了 + val d = acos(sin(lat1) * sin(lat2) + cos(lat1) * cos(lat2) * cos(lon2 - lon1)) * r + return d * 1000 + } + + /** + * 检查权限并获取权限 + */ + fun checkPermissions( + context: Context, + requestPermissionCallback: RequestPermissionCallback, + permissionTag: String, + vararg permissions: IPermission + ) { + XXPermissions.with(context).permissions(permissions) + .request(object : OnPermissionCallback { + override fun onGranted( + permissions: MutableList, all: Boolean + ) { + if (all) { + LogUtil.e("获取${permissionTag}权限成功") + requestPermissionCallback.onRequestPermissionSuccess() + } else { + LogUtil.e("获取部分权限成功,但部分权限未正常授予") + requestPermissionCallback.onRequestPermissionFail() + } + } + + override fun onDenied( + permissions: MutableList, doNotAskAgain: Boolean + ) { + if (doNotAskAgain) { + LogUtil.e("被永久拒绝授权,请手动授予定位权限") + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(context, permissions) + } else { + LogUtil.e("获取${permissionTag}权限失败") + requestPermissionCallback.onRequestPermissionFail() + } + } + }) + } + + + /** + * 上传调用的方法 ,传入本地文件路径,协程挂载方法 + */ + fun uploadFile(pathImg: String, fileType: String = "image/png"): MultipartBody.Part { + val file = File(pathImg) + val fileRQ = file.asRequestBody(fileType.toMediaTypeOrNull()) + return MultipartBody.Part.createFormData("file", file.name, fileRQ) + } + + +// var pattern: String { +// switch self { +// case .visa: return "^4[0-9]{12}(?:[0-9]{3})?$" +// case .visaElectron: return "^(4026|417500|4508|4844|491(3|7))" +// case .mastercard: return "^5[1-5][0-9]{14}$" +// case .maestro: return "^(5018|5020|5038|6304|6759|676[1-3])" +// case .americanExpress: return "^3[47][0-9]{13}$" +// case .dinnersClub: return "^3(?:0[0-5]|[68][0-9])[0-9]{11}$" +// case .discovery: return "^6(?:011|5[0-9]{2})[0-9]{12}$" +// case .jcb: return "^(?:2131|1800|35\\d{3})\\d{11}$" +// case .unknown: +// return "" +// } +// } + /** + * 合法的信用卡类型 + */ + fun getCreditCardType(cardNumber: String): Int { + //visa + var regex = "^4[0-9]{12}(?:[0-9]{3})?$" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_visa_card + } + //visaElectron + regex = "^(4026|417500|4508|4844|491(3|7))" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_visa_card + } + //mastercard + regex = "^5[1-5][0-9]{14}$" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_mastercard + } + //maestro + regex = "^(5018|5020|5038|6304|6759|676[1-3])" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_maestrocard + } + //americanExpress + regex = "^3[47][0-9]{13}$" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_amexcard + } + //dinnersClub + regex = "^3(?:0[0-5]|[68][0-9])[0-9]{11}$" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_newcard + } + //discovery + regex = "^6(?:011|5[0-9]{2})[0-9]{12}$" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_discovercard + } + //jcb + regex = "^(?:2131|1800|35\\d{3})\\d{11}$" + if (Utils.isRegularExpression(cardNumber, regex)) { + return R.drawable.ico_lay_newcard + } + //unknown类型卡片,不合法 + return 0 + } + + private const val CARD_NUMBER_FORMAT = "XXXX XXXX XXXX XXXX" + private const val CARD_NUMBER_FORMAT_AMEX = "XXXX XXXXXX XXXXX" + + /** + * 格式化卡号 + */ + fun formatCardNumber(cardNumber: String): String { + val unformattedText = cardNumber.replace(" ", "") + val sbFormattedNumber = StringBuilder() + val format = if (cardNumber.length == 15) { + CARD_NUMBER_FORMAT_AMEX + } else { + CARD_NUMBER_FORMAT + } + var j = 0 + for (i in format.indices) { + if (j > unformattedText.length - 1) { + break + } + if (format[i] == 'X') { + sbFormattedNumber.append(unformattedText[j++]) + } else { + sbFormattedNumber.append(format[i]) + } + } + return sbFormattedNumber.toString() + } + + /** + * getParcelable 不同版本适配 + */ + fun getParcelableAdaptive(intent: Intent, key: String, clazz: Class): T? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.extras?.getParcelable(key, clazz) + } else { + intent.extras?.getParcelable(key) + } + } + + /** + * getParcelableArrayList 不同版本适配 + */ + fun getParcelableArrayListAdaptive( + intent: Intent, key: String, clazz: Class + ): ArrayList? { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + intent.extras?.getParcelableArrayList(key, clazz) + } else { + intent.extras?.getParcelableArrayList(key) + } + } + + /** + * 获取AndroidManifest.xml中的meta-data值 + */ + fun getMetadata(context: Context, key: String): String { + val app = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + context.packageManager.getApplicationInfo( + context.packageName, + PackageManager.ApplicationInfoFlags.of(PackageManager.GET_META_DATA.toLong()) + ) + } else { + context.packageManager.getApplicationInfo( + context.packageName, PackageManager.GET_META_DATA + ) + } + return app.metaData.getString(key) ?: "" + } + + /** + * 处理登录成功后的数据 + */ + fun dealLoginSuccessData(context: Context, userBean: UserBean) { + LogUtil.e("处理登录成功后的数据:$userBean") + //通知相关页面关闭 + XEventBus.post(EventName.FinishActivity) + userBean.apply { + MMKVUtil.putString(MMKVKey.UserId, userId) + MMKVUtil.putString(MMKVKey.Email, email) + MMKVUtil.putString(MMKVKey.Token, token) + MMKVUtil.putString(MMKVKey.UserName, userName) + MMKVUtil.putInt(MMKVKey.MeasureUnit, measurementUnitType) + MMKVUtil.putInt(MMKVKey.isBindDevice, isBindDevice) + MMKVUtil.putInt(MMKVKey.isBindPet, isBindPet) + MMKVUtil.putInt(MMKVKey.isExistNewInvite, isExistNewInvite) + MMKVUtil.putBoolean(MMKVKey.FirstOpen, false) + val rand = Random() + //极光推送设置别名 + JPushInterface.setAlias(context, rand.nextInt(1000), userId) + } + } + + /** + * 退出登录 + */ + fun logOut(activity: BaseActivity<*>, lifecycleScope: LifecycleCoroutineScope) { + val rand = Random() + //此接口只能清除当前设备与目标 alias 的绑定关系 + // 想清除目标 alias 与所有设备的绑定关系,请想清除目标 alias 与所有设备的绑定关系 + // ,调用服务器接口https://docs.jiguang.cn/jpush/server/push/rest_api_v3_device + JPushInterface.deleteAlias(activity, rand.nextInt(1000)) + XEventBus.post(EventName.LogOut) + SRBleUtil.instance.cancelBleScan("已经在扫描蓝牙,退出登录取消扫描") + BleManager.getInstance().disconnectAllDevice() + lifecycleScope.launch { + val email = MMKVUtil.getString(MMKVKey.Email) + MMKVUtil.clearAll() + //保存最后一次登录的邮箱 + MMKVUtil.putString(MMKVKey.Email, email) + withContext(Dispatchers.IO) { + MyDatabase.deviceDao().deleteAll(MyDatabase.DeviceTableName) + MyDatabase.messageDao().deleteAll(MyDatabase.MessageTableName) + MyDatabase.creditCardDao().deleteAll(MyDatabase.CreditCardTableName) + } + activity.startActivityFinish(Intent(activity, SayHelloActivity::class.java)) + } + } + + /** + * 检测首次是否绑定设备和宠物 + */ + fun checkBindDeviceAndPetGoActivity(context: Context): Intent { + val intent = Intent() + val isExistNewInvite = MMKVUtil.getInt(MMKVKey.isExistNewInvite) + if (isExistNewInvite != ConstantInt.NoShare) { + intent.setClass(context, ShowInviteDeviceActivity::class.java) + return intent + } + val isBindDevice = MMKVUtil.getInt(MMKVKey.isBindDevice) + val isBindPet = MMKVUtil.getInt(MMKVKey.isBindPet) + if (isBindDevice == ConstantInt.NoBind) { + intent.setClass(context, AddNewTracker1Activity::class.java) + intent.putExtra(ConstantString.isFirstBind, true) + } else if (isBindPet == ConstantInt.NoBind) { + intent.setClass(context, FirstSetPetTypeActivity::class.java) + intent.putExtra(ConstantString.isFirstBind, true) + } else { + intent.setClass(context, HomeV2Activity::class.java) + } + return intent + } + + fun dealMessage(serverMessageList: MutableList) { + MyDatabase.messageDao().findAll(MyDatabase.MessageTableName)?.let { messageList -> + for (message in serverMessageList) { + var isAdd = false + for (m in messageList) { + if (message.messageId == m.messageId) { + isAdd = true + break + } + } + if (!isAdd) MyDatabase.messageDao().insert(message) + } + } + } + + /** + * 检测是否有必要权限和开关 + */ + fun checkNeedPermissionsEnabled(context: Context): Boolean { + val locationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + var hasPermissions = true + //检测蓝牙权限和蓝牙开关 + if (!XXPermissions.isGrantedPermissions( + context, mutableListOf( + PermissionLists.getBluetoothScanPermission(), + PermissionLists.getBluetoothConnectPermission() + ) + ) || !SRBleUtil.instance.isBlueEnable(context) + ) { + hasPermissions = false + } + if (hasPermissions) { + //检查定位权限 + if (!XXPermissions.isGrantedPermissions( + context, mutableListOf( + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + ) || !locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) + ) { + hasPermissions = false + } + } + if (hasPermissions) { + //检测通知权限 + if (!XXPermissions.isGrantedPermissions( + context, mutableListOf( + PermissionLists.getPostNotificationsPermission(), + PermissionLists.getNotificationServicePermission() + ) + ) + ) { + hasPermissions = false + } + } + return hasPermissions + } + + /** + * 检查蓝牙权限和开关 + */ + fun checkBluetoothPermissionsEnabled( + context: Context, successAuthorize: () -> Unit, isShowToast: Boolean = true + ) { + checkPermissions( + context, + object : RequestPermissionCallback { + override fun onRequestPermissionSuccess() { + //判断蓝牙是否打开 + if (!SRBleUtil.instance.isBlueEnable(context)) { + SRBleUtil.instance.openBluetooth(context) + return + } + successAuthorize() + } + + override fun onRequestPermissionFail() { + if (isShowToast) Toaster.show(R.string.txt_permissions_fail_tips) + } + }, + "checkBluetoothPermissionsEnabled,定位和蓝牙权限,Android12蓝牙扫描和连接权限", + PermissionLists.getBluetoothScanPermission(), + PermissionLists.getBluetoothConnectPermission(), + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + } + + /** + * 检查定位权限和GPS开关 + */ + fun checkLocationPermissionsGpsEnabled( + context: Context, successAuthorize: () -> Unit, isShowToast: Boolean = true + ) { + val locationManager = + context.getSystemService(Context.LOCATION_SERVICE) as LocationManager + checkPermissions( + context, + object : RequestPermissionCallback { + override fun onRequestPermissionSuccess() { + //判断GPS是否打开 + if (!locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + val intent = Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + context.startActivity(intent) + return + } + successAuthorize() + } + + override fun onRequestPermissionFail() { + if (isShowToast) Toaster.show(R.string.txt_permissions_fail_tips) + } + }, + "checkLocationPermissionsGpsEnabled,定位权限", + PermissionLists.getAccessFineLocationPermission(), + PermissionLists.getAccessCoarseLocationPermission() + ) + } + + /** + * 基础套餐权限控制 + */ + fun checkPackageLimit( + activity: BaseActivity<*>, deviceId: String + ): Boolean { + LogUtil.e("基础套餐权限控制deviceId=$deviceId") + val mealType = MMKVUtil.getInt(MMKVKey.MealType) + if (ConstantInt.BasicPackage == mealType) { + PackageUpgradeDialog(activity, deviceId).show() + return true + } + return false + } + + /** + * BleReportDataBean数据转换MapDeviceBean数据 + */ + fun bleReportDataToMapDevice( + mapDeviceBean: MapDeviceBean, bleReportData: BleReportDataBean + ) { + bleReportData.apply { + mapDeviceBean.let { + it.inSafeZone = inSafeZone + it.gpsSignal = gpsSignal + it.batteryLevel = batteryLevel + it.ledSwitch = ledSwitch + it.gnssInterval = gnssInterval + it.liveFlag = liveFlag + it.updateTime = updateTime + it.latLonUpdateTime = latLonUpdateTime + it.lteSignal = lteSignal + it.powerSwitch = powerSwitch + it.inNoGoZone = inNoGoZone + it.inWifiZone = inWifiZone + it.ledRedValue = ledRedValue + it.ledGreenValue = ledGreenValue + it.ledBlueValue = ledBlueValue + } + } + } + + /** + * 是否是超时上报了 + * + * @param updateTime 10位时间戳 + */ + fun isTimeoutReport(updateTime: Long): Boolean { + val now = System.currentTimeMillis() / 1000 + val updateMin = (now - updateTime) / 60 + return updateMin > ConstantInt.ReportTimeOutTime + } + + /** + * 获取保存的地图模式 地图类型,0标准 1卫星地图 + */ + fun getMapTypeSp() = MMKVUtil.getInt(MMKVKey.MapType, ConstantInt.Type0) + fun getShowFenceSp() = MMKVUtil.getBoolean(MMKVKey.ShowFence, true) + + } + + /** + *请求获取权限成功失败回调 + */ + interface RequestPermissionCallback { + fun onRequestPermissionSuccess() + fun onRequestPermissionFail() {} + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/ViewUtil.kt b/app/src/main/java/com/abbidot/tracker/util/ViewUtil.kt new file mode 100644 index 0000000..be2c0ee --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/ViewUtil.kt @@ -0,0 +1,1113 @@ +package com.abbidot.tracker.util + +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.animation.TimeInterpolator +import android.animation.ValueAnimator +import android.app.Activity +import android.content.Context +import android.graphics.Rect +import android.graphics.Typeface +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.StateListDrawable +import android.os.Build +import android.text.InputFilter +import android.text.InputType +import android.text.TextUtils +import android.text.method.HideReturnsTransformationMethod +import android.text.method.PasswordTransformationMethod +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.animation.Animation +import android.view.animation.AnimationUtils +import android.view.animation.DecelerateInterpolator +import android.view.animation.LinearInterpolator +import android.widget.CompoundButton +import android.widget.DatePicker +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.NumberPicker +import android.widget.ProgressBar +import android.widget.RelativeLayout +import android.widget.TextView +import androidx.annotation.DrawableRes +import androidx.core.content.ContextCompat +import androidx.core.view.setPadding +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.FragmentActivity +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import coil.load +import coil.transform.Transformation +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.BuildConfig +import com.abbidot.tracker.R +import com.abbidot.tracker.adapter.GridItemDecoration +import com.abbidot.tracker.adapter.SelectMapTypeCardShadeAdapter +import com.abbidot.tracker.adapter.SpaceItemDecoration +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.MenuTxtBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.GetResultCallback +import com.abbidot.tracker.constant.MultipleEntity +import com.abbidot.tracker.dialog.MyAlertDialog +import com.abbidot.tracker.dialog.SelectMapTypeDialog +import com.abbidot.tracker.ui.activity.WebViewActivity +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.widget.TypefaceTextView +import com.daimajia.androidanimations.library.Techniques +import com.daimajia.androidanimations.library.YoYo +import com.daimajia.androidanimations.library.YoYo.YoYoString +import com.google.android.flexbox.FlexboxLayoutManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.hjq.permissions.OnPermissionCallback +import com.hjq.permissions.XXPermissions +import com.hjq.permissions.permission.PermissionLists +import com.hjq.permissions.permission.base.IPermission +import com.hjq.toast.Toaster +import com.huantansheng.easyphotos.EasyPhotos +import com.qmuiteam.qmui.alpha.QMUIAlphaImageButton +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import com.qmuiteam.qmui.widget.dialog.QMUIBottomSheet.BottomListSheetBuilder +import com.qmuiteam.qmui.widget.grouplist.QMUICommonListItemView +import java.util.Calendar + + +/** + *Created by .yzq on 2021/12/22/022. + * @link + * @description: + */ +class ViewUtil private constructor() { + + //单例模式 + companion object { + //粗体字体 + const val FONT_BOLD = "fonts/Roboto_Bold.ttf" + const val FONT_BOLD_NAME = "roboto_bold_font" + + //普通黑色字体 + const val FONT_COMMON = "fonts/Roboto_Regular.ttf" + const val FONT_COMMON_NAME = "roboto_regular_font" + + //数字字体 + const val FONT_NUMBER = "fonts/DinCond_Bold.otf" + const val FONT_NUMBER_NAME = "number_din_cond_font" + + //信用卡字体 + const val FONT_CREDIT = "fonts/Credit.ttf" + const val FONT_CREDIT_NAME = "credit_card_font" + + const val textImageUrl = + "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic1.zhimg.com%2Fv2-087b76d6a57befbf484e3aa8138037fa_1440w.jpg%3Fsource%3D172ae18b&refer=http%3A%2F%2Fpic1.zhimg.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1644660067&t=b8edb5a50920007abdd445d461ff6bab" + + val instance: ViewUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + ViewUtil() + } + } + + /** + * 设置字体 + */ + fun setTypeface(context: Context, type: String?): Typeface { + //默认字体 + var typeface = Typeface.DEFAULT + when (type) { + FONT_BOLD_NAME -> { + typeface = Typeface.createFromAsset(context.assets, FONT_BOLD) + } + + FONT_COMMON_NAME -> { + typeface = Typeface.createFromAsset(context.assets, FONT_COMMON) + } + + FONT_NUMBER_NAME -> { + typeface = Typeface.createFromAsset(context.assets, FONT_NUMBER) + } + + FONT_CREDIT_NAME -> { + typeface = Typeface.createFromAsset(context.assets, FONT_CREDIT) + } + } + + return typeface + } + + + /** + * 设置QMUICommonListItemView数据,一组多个水平控件的按钮 + * + * @param name 按钮名字用于左边显示 + * @param accessoryType 右边显示类型 + */ + fun showQMUICommonListItemView( + context: Context, + qMUIItemView: QMUICommonListItemView, + onClickListener: View.OnClickListener?, + name: String, + accessoryType: Int + ) { + val tf = setTypeface(context, context.getString(R.string.roboto_bold_font)) + + qMUIItemView.let { + it.text = name + //带有值显示下方,例如详情 + it.orientation = QMUICommonListItemView.HORIZONTAL + it.textView.typeface = tf + if (accessoryType != -1) it.accessoryType = accessoryType + it.detailText = "" + it.setOnClickListener(onClickListener) + } + } + + fun showQMUICommonListItemView( + context: Context, + qMUIItemView: QMUICommonListItemView, + onClickListener: View.OnClickListener?, + name: String + ) { + showQMUICommonListItemView(context, qMUIItemView, onClickListener, name, -1) + } + + /** + * 设置NumberPicker相关属性 + */ + fun setNumberPickerAttributes( + context: Context, + numberPicker: NumberPicker, + min: Int = 0, + max: Int, + cValue: Int = 0, + tSize: Int = 0, + formatter: NumberPicker.Formatter? = null, + valueChangeListener: NumberPicker.OnValueChangeListener? = null, + //去掉上下2行线 + removeUpDownLine: Boolean = false, + loopSelector: Boolean = true + ) { + numberPicker.apply { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + selectionDividerHeight = AppUtils.dpToPx(1) + if (tSize > 0) { + textSize = QMUIDisplayHelper.sp2px(context, tSize).toFloat() + } + } + if (min >= 0) minValue = min + if (max >= 0) maxValue = max + if (cValue >= 0) value = cValue + //不可编辑 + descendantFocusability = NumberPicker.FOCUS_BLOCK_DESCENDANTS + //是否可以循环滑动 + wrapSelectorWheel = loopSelector + //去掉上下2行线 + if (removeUpDownLine) showDividers = LinearLayout.SHOW_DIVIDER_NONE + if (null != formatter) setFormatter(formatter) + if (null != valueChangeListener) setOnValueChangedListener(valueChangeListener) + } + } + + /** + * 监听2个关联 NumberPicker 不能同时为0,隐藏按钮 + */ + fun numberPickerLinkageValueNo0(cNumberPickerNewVal: Int, linkage: NumberPicker, btn: View) { + if (cNumberPickerNewVal == 0 && linkage.value == 0) { + if (btn.visibility == View.VISIBLE) { + btn.isEnabled = false + viewHideFadeInAnimation(btn, hideType = View.INVISIBLE) + } + } else { + if (btn.visibility == View.INVISIBLE) { + btn.isEnabled = true + viewShowFadeInAnimation(btn) + } + } + } + + /** + * 设置item分割线和水平线性布局 + */ + fun setRecyclerViewDecorationLinearLayoutManager( + context: Context, recyclerView: RecyclerView, adapter: RecyclerView.Adapter<*> + ) { + recyclerView.let { + it.layoutManager = LinearLayoutManager(context) + it.addItemDecoration(DividerItemDecoration(context, DividerItemDecoration.VERTICAL)) + it.adapter = adapter + } + } + + /** + * 设置垂直滑动布局的RecyclerView + */ + fun setRecyclerViewVerticalLinearLayout( + context: Context, + recyclerView: RecyclerView, + adapter: RecyclerView.Adapter<*>, + left: Int = 0, + right: Int = 0, + top: Int = 0, + bottom: Int = 0 + ) { + recyclerView.let { + it.layoutManager = LinearLayoutManager(context) + if (left > 0 || right > 0 || top > 0 || bottom > 0) it.addItemDecoration( + SpaceItemDecoration(left, right, top, bottom) + ) + it.adapter = adapter + } + } + + /** + * 设置水平滑动布局的RecyclerView + */ + fun setRecyclerViewHorizontalLinearLayout( + context: Context, + recyclerView: RecyclerView, + adapter: RecyclerView.Adapter<*>, + left: Int = 0, + right: Int = 0, + top: Int = 0, + bottom: Int = 0 + ) { + recyclerView.let { + it.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) + if (left > 0 || right > 0 || top > 0 || bottom > 0) it.addItemDecoration(object : + RecyclerView.ItemDecoration() { + override fun getItemOffsets( + outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State + ) { + val position = parent.getChildAdapterPosition(view) + if (left > 0 && position == 0) { + outRect.left = left + } + if (right > 0) { + outRect.right = right + } + if (top > 0) { + outRect.top = top + } + if (bottom > 0) { + outRect.bottom = bottom + } + } + }) + it.adapter = adapter + } + } + + fun setRecyclerViewFlexboxLayout( + context: Context, + recyclerView: RecyclerView, + adapter: RecyclerView.Adapter<*>, + left: Int = 0, + right: Int = 0, + top: Int = 0, + bottom: Int = 0 + ) { + recyclerView.let { + it.layoutManager = FlexboxLayoutManager(context) + if (left > 0 || right > 0 || top > 0 || bottom > 0) it.addItemDecoration( + SpaceItemDecoration(left, right, top, bottom) + ) + it.adapter = adapter + } + } + + /** + * 设置GridLayout布局的RecyclerView + * @param offset 控件的外边距,不使用屏幕宽度来计算 + */ + fun setRecyclerViewGridLayout( + context: Context, + recyclerView: RecyclerView, + adapter: RecyclerView.Adapter<*>, + spanCount: Int, + itemWidth: Int = AppUtils.dpToPx(80), + top: Int = 0, + bottom: Int = 0, + offset: Int = 0 + ) { + recyclerView.let { + it.layoutManager = GridLayoutManager(context, spanCount) + it.addItemDecoration( + GridItemDecoration( + context, spanCount, itemWidth, top, bottom, offset + ) + ) + it.adapter = adapter + } + } + + /** + * 设置输入框的错误显示 + */ + fun setInputViewErrorShow( + context: Context, viewGroup: ViewGroup, errorTextView: TextView + ) { + viewGroup.apply { + YoYo.with(Techniques.Shake).onStart { + background = ContextCompat.getDrawable( + context, R.drawable.shape8_red_border_white_bg + ) + errorTextView.setTextColor(ContextCompat.getColor(context, R.color.red_color2)) + viewShowFadeInAnimation(errorTextView, 800) + }.duration(50).playOn(this) + } + } + + /** + * 设置输入框的错误隐藏事件 + */ + fun setInputViewErrorHideClick( + context: Context, + viewGroup: ViewGroup, + errorView: View, + editText: EditText, + onTextChanged: (s: String) -> Unit = {} + ) { + viewGroup.apply { + editText.addTextChangedListener { + if (errorView.visibility == View.VISIBLE) { + background = ContextCompat.getDrawable( + context, R.drawable.shape8_gray_border_white_bg + ) + viewHideFadeInAnimation(errorView, 400) + } + onTextChanged(it.toString()) + } + } + } + + /** + *设置地图视图离底部重叠部分 + */ + fun setMapViewBottomMarginOverlap( + viewGroup: ViewGroup, bottomMargin: Int, overlapSpace: Int + ) { + val params = viewGroup.layoutParams + val space = bottomMargin - overlapSpace + when (params) { + is LinearLayout.LayoutParams -> { + if (params.bottomMargin == space) return + params.bottomMargin = space + } + + is RelativeLayout.LayoutParams -> { + if (params.bottomMargin == space) return + params.bottomMargin = space + } + + is FrameLayout.LayoutParams -> { + if (params.bottomMargin == space) return + params.bottomMargin = space + } + } + viewGroup.layoutParams = params + } + + /** + * 修改shape的圆角半径 + * @param radiusDp 半径大小,dp单位 + */ + fun setShapeRadius(view: View, radiusDp: Int) { + val stateListDrawable = view.background as StateListDrawable + val drawable = stateListDrawable.current + if (drawable is GradientDrawable) { + drawable.cornerRadius = QMUIDisplayHelper.dpToPx(radiusDp).toFloat() + } + } + + /** + * 显示自定义Dialog + */ + fun showDialog( + context: Context, + contentResId: Int, + okListener: BaseDialog.OnDialogOkListener, + title: String = "", + okTextResId: Int = 0, + cancelTextResId: Int = 0, + cancelListener: BaseDialog.OnDialogCancelListener? = null + ) { + try { + showDialog( + context, + context.getString(contentResId), + okListener, + title, + okTextResId, + cancelTextResId, + cancelListener + ) + } catch (e: Exception) { + LogUtil.e("showDialog异常:${e.message}") + } + } + + /** + * 显示自定义Dialog + */ + fun showDialog( + context: Context, + contentResId: String, + okListener: BaseDialog.OnDialogOkListener, + title: String = "", + okTextResId: Int = 0, + cancelTextResId: Int = 0, + cancelListener: BaseDialog.OnDialogCancelListener? = null + ) { + val myDialog = MyAlertDialog(context) + myDialog.show() + myDialog.setTipTitle(title) + myDialog.setContent(contentResId) + if (okTextResId != 0) { + myDialog.setOkText(okTextResId) + } + if (cancelTextResId != 0) { + myDialog.setCancelText(cancelTextResId) + } + if (null == cancelListener) { + myDialog.setOnDialogCancelListener(object : BaseDialog.OnDialogCancelListener { + override fun onCancelClick(dialog: BaseDialog<*>) { + dialog.dismiss() + } + }) + } else myDialog.setOnDialogCancelListener(cancelListener) + + myDialog.setOnDialogOkListener(okListener) + } + + /** + * 底部弹出菜单 + * @param allowDragDismiss 拖着空白地方可消失 + */ + fun showBottomSheetList( + context: Context, + onSheetItemClickListener: BottomListSheetBuilder.OnSheetItemClickListener?, + title: CharSequence = "", + gravityCenter: Boolean = true, + addCancelBtn: Boolean = true, + allowDragDismiss: Boolean = false + ): BottomListSheetBuilder { + val builder = BottomListSheetBuilder(context) + builder.setGravityCenter(gravityCenter).setAllowDrag(allowDragDismiss) + .setAddCancelBtn(addCancelBtn).setTitle(title) + .setCancelText(context.getString(R.string.txt_cancel)) + .setOnSheetItemClickListener(onSheetItemClickListener) + return builder + } + + /** + * 底部弹出界面 + * @param layoutId 要显示的布局 + * @param behaviorState 显示位置。可底部、居中 + */ + fun showBottomSheet( + context: Context, layoutId: Int, behaviorState: Int = 0 + ): BottomSheetDialog { + val view = View.inflate(context, layoutId, null) + val bottomSheetDialog = BottomSheetDialog(context) + bottomSheetDialog.setContentView(view) + //改变弹出位置方式 + if (behaviorState > 0) { + val behavior = BottomSheetBehavior.from(view.parent as View) + behavior.state = behaviorState + } + bottomSheetDialog.show() + return bottomSheetDialog + } + + /** + * 弹出底部日期 + * + * @param dateList 初始化datePicker显示日期 + * @param getResultCallback 点击ok确定回调 + */ + fun showBottomSheetDate( + context: Context, dateList: List, getResultCallback: GetResultCallback + ) { + val bottomSheetDialog = showBottomSheet(context, R.layout.sheet_dialog_select_date) + val delegate = bottomSheetDialog.delegate + + val datePicker = delegate.findViewById(R.id.bottom_sheet_date_picker) + datePicker!!.maxDate = Calendar.getInstance().timeInMillis + + if (dateList.size > 2) { + datePicker.updateDate(dateList[0].toInt(), dateList[1].toInt() - 1, dateList[2].toInt()) + } + + delegate.findViewById(R.id.bottom_sheet_date_ok)!!.setOnClickListener { + val month = Utils.fill2Digits(datePicker.month + 1) + val day = Utils.fill2Digits(datePicker.dayOfMonth) + val birthday = "${datePicker.year}-${month}-${day}" + getResultCallback.onResult(birthday) + bottomSheetDialog.dismiss() + } + delegate.findViewById(R.id.bottom_sheet_date_cancel)!! + .setOnClickListener { + bottomSheetDialog.dismiss() + } + } + + /** + * 跳转相册和拍照 + * @param count 可以选几张照片 + */ + fun goSelectPhoto(activity: FragmentActivity, requestCode: Int, count: Int = 1) { +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) + + XXPermissions.with(activity).permission(PermissionLists.getReadMediaImagesPermission()) + .permission(PermissionLists.getCameraPermission()) + .request(object : OnPermissionCallback { + override fun onGranted(permissions: MutableList, allGranted: Boolean) { + if (allGranted) { + LogUtil.e("获取READ_MEDIA_IMAGES权限成功") + EasyPhotos.createAlbum(activity, true, false, CoilEngine.instance) + .setFileProviderAuthority("${BuildConfig.APPLICATION_ID}.fileprovider") + .setCount(count).setPuzzleMenu(false).setCleanMenu(false) + .start(requestCode) + //activity.overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left) + } else { + LogUtil.e("获取READ_MEDIA_IMAGES权限,但部分权限未正常授予") + Toaster.show(R.string.txt_permissions_fail_tips) + } + } + + override fun onDenied( + permissions: MutableList, doNotAskAgain: Boolean + ) { + if (doNotAskAgain) { + LogUtil.e("READ_MEDIA_IMAGES被永久拒绝授权,请手动授予定位权限") + // 如果是被永久拒绝就跳转到应用权限系统设置页面 + XXPermissions.startPermissionActivity(activity, permissions) + } else { + LogUtil.e("获取READ_MEDIA_IMAGES权限失败") + Toaster.show(R.string.txt_permissions_fail_tips) + } + } + }) + } + + /** + * coil加载网络图片 + * @param transformations 四种变换:模糊变换(BlurTransformation)、 + * 圆形变换(CircleCropTransformation)、灰度变换(GrayscaleTransformation) + * 和圆角变换(RoundedCornersTransformation) + */ + fun imageLoadUrl( + imageView: ImageView, + url: String, + @DrawableRes errorImageResId: Int = R.drawable.icon_placeholder_image_svg, + vararg transformations: Transformation + ) { + imageView.load(url) { + //placeholder预置展位图 + placeholder(errorImageResId) + //失败时的错误占位图片 + error(errorImageResId) + //淡入淡出的动画时间 +// crossfade(2000) +// memoryCachePolicy(CachePolicy.ENABLED)//设置内存的缓存策略 +// diskCachePolicy(CachePolicy.ENABLED)//设置磁盘的缓存策略 +// networkCachePolicy(CachePolicy.ENABLED)//设置网络的缓存策略 +// OkHttpClient.Builder().cache(CoilUtils.createDefaultCache(context)) + transformations(transformations.toList()) + } + } + + + //属性动画持续时间,首页动画时间 + private val valueAnimatorDuration = 600L + + /** + * 设置数字变化动画,并设置值 + * @param stringFormat 字符串资源格式 + */ + fun viewNumberValueAnimator( + view: View, + startValue: Number, + endValueAny: Any, + stringFormat: String = "", + animatorDuration: Long = valueAnimatorDuration + ) { + setValueAnimator(startValue, endValueAny, animatorDuration) { + if (view is TextView) { + var str = "" + when (val value = it.animatedValue) { + is Int -> str = value.toString() + is Float -> str = Utils.formatDecimal(value, 2) + is Double -> str = Utils.formatDecimal(value, 2) + } + view.text = if (TextUtils.isEmpty(stringFormat)) str + else String.format(stringFormat, str) + } else if (view is ProgressBar) { + view.progress = it.animatedValue as Int + } + } + } + + /** + * 设置数值动画 + */ + fun setValueAnimator( + startValue: Number, + endValueAny: Any, + animatorDuration: Long = 1000, + interpolator: TimeInterpolator = DecelerateInterpolator(), //该插值器的变化率开始很快,然后减速。 + listener: ValueAnimator.AnimatorUpdateListener? = null + ): ValueAnimator? { + if (startValue == endValueAny) return null + + var valueAnimator: ValueAnimator? = null + var endValue = endValueAny + + if (endValueAny is String) { + endValue = try { + endValueAny.toInt() + } catch (e: Exception) { + endValueAny.toFloat() + } + } + + when (endValue) { + is Int -> valueAnimator = ValueAnimator.ofInt(startValue as Int, endValue) + is Long -> valueAnimator = ValueAnimator.ofInt(startValue as Int, endValue.toInt()) + is Float -> valueAnimator = ValueAnimator.ofFloat(startValue.toFloat(), endValue) + is Double -> valueAnimator = + ValueAnimator.ofFloat(startValue.toFloat(), endValue.toFloat()) + } + + if (null == valueAnimator) return valueAnimator + + valueAnimator.duration = animatorDuration + valueAnimator.interpolator = interpolator + listener?.apply { + valueAnimator.addUpdateListener(this) + } + valueAnimator.start() + + return valueAnimator + } + + /** + * 围绕自身中心旋转360度、淡化动画 + */ + fun viewAlphaAndRotationObjectAnimator(view: View, isLoop: Boolean = false): AnimatorSet { + val objectAnimator = ObjectAnimator.ofFloat(view, "alpha", 0f, 0.5f, 1f).apply { + duration = 500 + interpolator = LinearInterpolator() + if (isLoop) { + repeatMode = ValueAnimator.RESTART + repeatCount = ValueAnimator.INFINITE + } + } + val objectAnimator2 = ObjectAnimator.ofFloat(view, "rotation", 0f, 360f).apply { + duration = 1000 + interpolator = LinearInterpolator() + if (isLoop) { + repeatMode = ValueAnimator.RESTART + repeatCount = ValueAnimator.INFINITE + } + } + val animatorSet = AnimatorSet().apply { + play(objectAnimator).with(objectAnimator2) + start() + } + return animatorSet + } + + + /** + * 加载动画文件执行动画 + */ + fun viewLoadAnimation( + context: Context, + view: View, + animId: Int = R.anim.scale_enter, + durationMillis: Long = 2000, + startOffset: Long = 3000 + ): Animation { + val animation = AnimationUtils.loadAnimation(context, animId) + animation.duration = durationMillis + animation.startOffset = startOffset + view.startAnimation(animation) + return animation + } + + /** + * 控件淡入显示 + */ + fun viewShowFadeInAnimation(target: View, duration: Long = 350): YoYoString { + return YoYo.with(Techniques.FadeIn).duration(duration).onStart { + if (target.visibility == View.GONE || target.visibility == View.INVISIBLE) { + target.post { + target.visibility = View.VISIBLE + } + } + }.playOn(target) + } + + /** + * 控件淡出隐藏 + */ + fun viewHideFadeInAnimation(target: View, duration: Long = 350, hideType: Int = View.GONE) { + YoYo.with(Techniques.FadeOut).duration(duration).onEnd { + if (target.visibility == View.VISIBLE) target.visibility = hideType + }.playOn(target) + } + + /** + * 添加菜单数据 + */ + fun addMenuBean( + list: MutableList, + name: String, + value: String = "", + imageResId: Int = 0, + imageUrl: String = "", + colorRedId: Int = R.color.orange_color3, + isSwitch: Boolean = false, + @MultipleEntity menuType: Int = MultipleEntity.TEXT + ) { + val menuTxtBean = MenuTxtBean(name, value, menuType) + if (imageResId > 0) { + menuTxtBean.imageId = imageResId + } + if (!TextUtils.isEmpty(imageUrl)) { + menuTxtBean.imageUrl = imageUrl + } + if (isSwitch) { + menuTxtBean.isSwitch = isSwitch + } + menuTxtBean.colorRedId = colorRedId + list.add(menuTxtBean) + } + + /** + * 更新选中的状态item + */ + fun updateDataBeanOnlySelectedItem( + adapter: BaseRecyclerAdapter<*>, list: MutableList, position: Int + ) { + if (position > list.size - 1 || position < 0) return + val dataBean = list[position] + if (dataBean.selected) return + for (i in 0 until list.size) { + val data = list[i] + if (data.selected) { + data.selected = false + adapter.notifyItemChanged(i) + break + } + } + dataBean.selected = true + adapter.notifyItemChanged(position) + } + + /** + * 显示地图类型选择弹窗 + */ + fun getMapTypeDialog( + context: Context, + onItemClickListener: BaseRecyclerAdapter.OnItemClickListener? = null, + checkedChangeListener: CompoundButton.OnCheckedChangeListener? = null + ): SelectMapTypeDialog { + val mapType = Util.getMapTypeSp() + val typeList = mutableListOf() + DataBean().apply { + selected = mapType == ConstantInt.Type0 + imageId = R.drawable.icon_map_standard_type + name = context.getString(R.string.txt_standard) + typeList.add(this) + } + DataBean().apply { + selected = mapType == ConstantInt.Type1 + imageId = R.drawable.icon_map_satellite_type + name = context.getString(R.string.txt_satellite) + typeList.add(this) + } + val mapTypeAdapter = SelectMapTypeCardShadeAdapter(context, typeList).apply { + setOnItemClickListener(object : BaseRecyclerAdapter.OnItemClickListener { + override fun onItemClick(itemView: View?, pos: Int) { + updateDataBeanOnlySelectedItem(this@apply, typeList, pos) + onItemClickListener?.onItemClick(itemView, pos) + } + }) + } + return SelectMapTypeDialog(context, mapTypeAdapter, checkedChangeListener) + } + + /** + * 设置切换位置按钮图片 + */ + fun setMapSwitchLocationButtonImage(iv: ImageView, locationType: Int) { + viewShowFadeInAnimation(iv, 600) + when (locationType) { + ConstantInt.PetLocationType -> { + iv.setBackgroundResource(R.drawable.shape_yellow_circle_bg) + iv.setImageResource(R.drawable.icon_pet_foot_svg) + iv.setPadding(AppUtils.dpToPx(14)) + } + + ConstantInt.UserLocationType -> { + iv.setBackgroundResource(R.drawable.shape_white_circle_bg) + iv.setImageResource(R.drawable.icon_user_head_svg) + iv.setPadding(AppUtils.dpToPx(13)) + } + + ConstantInt.OtherLocationType -> { + iv.setBackgroundResource(R.drawable.shape_white_circle_bg) + iv.setImageResource(R.drawable.icon_pet_fence_svg) + iv.setPadding(AppUtils.dpToPx(13)) + } + } + } + + + /** + * 跳转隐私政策 + */ + fun goPrivacyPolicy(activity: Activity) { + val url = if (AppUtils.isChina()) { + "file:///android_asset/privacy_cn.html" + } else { + "file:///android_asset/privacy_en.html" + } + WebViewActivity.startWebViewActivity( + activity, activity.getString(R.string.txt_privacy_policy), url + ) + } + + /** + * 跳转服务协议 + */ + fun goServiceContract(activity: Activity) { + val url = if (AppUtils.isChina()) { + "file:///android_asset/terms_cn.html" + } else { + "file:///android_asset/terms_en.html" + } + WebViewActivity.startWebViewActivity( + activity, activity.getString(R.string.txt_service_contract), url + ) + } + + /** + * 设置显示隐藏密码 + */ + fun setShowAndHidePassword( + editText: EditText, hideShowImBtn: QMUIAlphaImageButton + ) { + editText.apply { + val filter = arrayOf(InputFilter.LengthFilter(60)) + filters = filter + transformationMethod = PasswordTransformationMethod.getInstance() + inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD + } + hideShowImBtn.apply { + visibility = View.VISIBLE + setImageResource(R.drawable.icon_hide_password_svg) + setChangeAlphaWhenPress(true) + } + } + + /** + * 显示隐藏密码事件 + */ + fun showAndHidePasswordClick( + editText: EditText, hideShowImBtn: QMUIAlphaImageButton, isShowPassword: Boolean + ) { + if (isShowPassword) { + hideShowImBtn.setImageResource(R.drawable.icon_show_password_svg) + editText.transformationMethod = HideReturnsTransformationMethod.getInstance() + } else { + hideShowImBtn.setImageResource(R.drawable.icon_hide_password_svg) + editText.transformationMethod = PasswordTransformationMethod.getInstance() + } + editText.setSelection(editText.text.toString().length) + } + + /** + * 设置EditText不可编辑,下拉状态 + */ + fun setEditTextDropSelectState(editText: EditText, hintResId: Int = R.string.txt_select) { + editText.apply { + isEnabled = false + layoutParams.width = RelativeLayout.LayoutParams.WRAP_CONTENT + setHint(hintResId) + } + } + + /** + * 设置已选择宠物显示 + */ + fun selectPetDialogShow( + context: Context, + petList: MutableList, + selectPos: Int, + petName: TextView, + petHead: ImageView + ) { + if (petList.size == 0) { + petName.text = context.getString(R.string.no_pet) + petHead.load(R.drawable.icon_dog_default_head_svg) + return + } + val pet = petList[selectPos] + savePetSP(pet, selectPos) + petName.text = pet.petName + setPetTypeHead(petHead, pet.imgurl, pet.petType) + viewAlphaAndRotationObjectAnimator(petHead) + } + + /** + * 保存宠物相关数据到本地 + */ + private fun savePetSP(petBean: PetBean, selectPos: Int) { + //保存已选宠物下标 + MMKVUtil.putInt(MMKVKey.PetSelectPosition, selectPos) + //保存当前设备的套餐类型,控制权限 + MMKVUtil.putInt(MMKVKey.MealType, petBean.mealType) + //保存当前宠物是否分享的 + MMKVUtil.putInt(MMKVKey.Shared, petBean.shared) + } + + /** + * ViewPager2配置信息 + */ + fun setViewPager2Config( + viewPager2: ViewPager2, + callback: ViewPager2.OnPageChangeCallback?, + pageLimit: Int = 0, + isCanSlide: Boolean = false + ) { + viewPager2.apply { + //禁止ViewPager滑动 + isUserInputEnabled = isCanSlide + if (pageLimit > 0) offscreenPageLimit = pageLimit + //滑动动画 +// it.setPageTransformer(CompositePageTransformer()) +// (getChildAt(0) as RecyclerView).layoutManager!!.isItemPrefetchEnabled = false + (getChildAt(0) as RecyclerView).let { + it.layoutManager?.isItemPrefetchEnabled = false + //滑动动画 +// itemAnimator = DefaultItemAnimator() + } + callback?.let { + registerOnPageChangeCallback(it) + } + } + } + + /** + * 获取围栏的类型图片 + */ + fun getFenceNameImageType(fenceNameType: Int): Int { + var fenceNameTypeImage = 0 + when (fenceNameType) { + 1 -> fenceNameTypeImage = R.drawable.icon_save_zone_type1_svg + 2 -> fenceNameTypeImage = R.drawable.icon_save_zone_type2_svg + 3 -> fenceNameTypeImage = R.drawable.icon_save_zone_type3_svg + 4 -> fenceNameTypeImage = R.drawable.icon_save_zone_type4_svg + 5 -> fenceNameTypeImage = R.drawable.icon_save_zone_type5_svg + 6 -> fenceNameTypeImage = R.drawable.icon_no_zone_type1_svg + 7 -> fenceNameTypeImage = R.drawable.icon_no_zone_type2_svg + 8 -> fenceNameTypeImage = R.drawable.icon_no_zone_type3_svg + } + return fenceNameTypeImage + } + + /** + * 设置宠物头像 + */ + fun setPetTypeHead(imageView: ImageView, imageUrl: String, type: Int) { + if (TextUtils.isEmpty(imageUrl)) { + val defaultHead = if (type == 2) R.drawable.icon_cat_default_head_svg + else R.drawable.icon_dog_default_head_svg + imageView.load(defaultHead) + } else { + imageLoadUrl(imageView, imageUrl) + } + } + + /** + * 操作提示框 + */ + fun operateDialogTips(context: Context, messageID: Int, okCallback: () -> Unit) { + showDialog(context, messageID, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + okCallback() + } + }, okTextResId = R.string.start_txt_yes) + } + + /** + * 手动连接蓝牙提示 + */ + fun bleConActionState( + baseActivity: BaseActivity<*>, + textView: TextView, + conState: Int, + gravity: Int = Gravity.BOTTOM + ) { + when (conState) { + ConState.CONNECTING, ConState.CONNECTING_MAC -> { + textView.text = baseActivity.getString(R.string.txt_connecting) + } + + else -> { + textView.text = baseActivity.getString(R.string.tracker_manage_set_ble_unconnect) + baseActivity.showToast(R.string.txt_ble_connect_fail, gravity = gravity) + } + } + textView.setBackgroundColor(ContextCompat.getColor(baseActivity, R.color.red_color4)) + } + + fun bleConStateToast( + baseActivity: BaseActivity<*>, conState: Int, gravity: Int = Gravity.BOTTOM + ) { + when (conState) { + ConState.CONNECTED -> baseActivity.showToast( + R.string.txt_connect_success, gravity = gravity + ) + + ConState.DISCONNECTED -> baseActivity.showToast( + R.string.txt_unconnect_no, gravity = gravity + ) + + else -> baseActivity.showToast(R.string.txt_connect_fail, gravity = gravity) + } + } + + /** + * 手动连接/断开设备 + */ + fun connectionBtn( + context: Context, btnString: String, mac: String, noConnectCallback: () -> Unit + ) { + if (btnString == context.getString(R.string.tracker_manage_set_ble_unconnect)) { + noConnectCallback() + } else if (btnString == context.getString(R.string.tracker_manage_set_ble_connect)) { + operateDialogTips(context, R.string.txt_disconnect_tips) { + SRBleUtil.instance.disconnectToMac(mac) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt new file mode 100644 index 0000000..cbaf77e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleCmdUtil.kt @@ -0,0 +1,298 @@ +package com.abbidot.tracker.util.bluetooth + +import android.text.TextUtils +import com.abbidot.tracker.bean.WiFiZoneBean +import java.util.TimeZone +import kotlin.experimental.xor + + +/** + *Created by .yzq on 2021/12/1/001. + * @link + * @description: 蓝牙cmd命令 + */ +class SRBleCmdUtil private constructor() { + + companion object { + val instance: SRBleCmdUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + SRBleCmdUtil() + } + + /** + * 写指令 + */ + const val CMD_WRITE = 1 + + /** + * 读指令 + */ + const val CMD_READ = 0 + } + + /** + * java 写法 + * + * public byte calcCrc8(byte[] data) { + byte crc = 0; + for (int j = 0; j < data.length; j++) { + crc ^= data[j]; + for (int i = 0; i < 8; i++) { + if ((crc & 0x01) != 0) { + crc = (byte) ((crc) >> 1); + crc ^= 0x8c; + } else { + crc = (byte) ((crc) >> 1); + } + } + } + return crc; + } + * + * CRC8 算法 + */ + private fun calcCrc8(data: ByteArray): Byte { + var crc: Byte = 0 + for (j in data.indices) { + crc = crc xor data[j] + for (i in 0..7) { + if (byteToInt(crc) and 0x01 != 0) { + crc = (byteToInt(crc) shr 1).toByte() + crc = crc xor 0x8c.toByte() + } else { + crc = (byteToInt(crc) shr 1).toByte() + } + } + } + return crc + } + + /** + * CRC8 算法 + */ + private fun getCrc8(data: ByteArray): Byte { + return calcCrc8(data) + } + + /** + * 长整型转换高低4个字节 + */ + fun longTo4ByteArray(value: Long): ByteArray { + val byte1 = (value shr 24 and 0xffL).toByte() + val byte2 = (value shr 16 and 0xffL).toByte() + val byte3 = (value shr 8 and 0xffL).toByte() + val byte4 = (value and 0xffL).toByte() + return byteArrayOf(byte1, byte2, byte3, byte4) + } + + /** + * 高低4个字节转换长整型 + */ + fun byte4ArrayToLong(data: ByteArray): Long { + if (data.size == 4) { + return (data[0].toLong() and 0xffL shl 24) or (data[1].toLong() and 0xffL shl 16) or (data[2].toLong() and 0xffL shl 8) or (data[3].toLong() and 0xffL) + } + return 0L + } + + /** + * 高低2个字节转换长整型 + */ + fun byte2ArrayToInt(data: ByteArray): Int { + if (data.size == 2) { + return (data[0].toInt() and 0xff shl 8) or (data[1].toInt() and 0xff) + } + return 0 + } + + /** + * 长整型转换高低2个字节 + */ + private fun longTo2ByteArray(value: Long): ByteArray { + val byte1 = (value shr 8 and 0xffL).toByte() + val byte2 = (value and 0xffL).toByte() + return byteArrayOf(byte1, byte2) + } + + /** + * byte转int + */ + fun byteToInt(byte: Byte): Int { + return byte.toInt() and 0xff + } + + /** + * 获取最终完整加密命令 + */ + private fun getCrc8Cmd(byteArray: ByteArray): ByteArray { + val crc8 = getCrc8(byteArray) + //后面追加 + return byteArray.plus(crc8) + } + + /** + * 绑定设备 + */ + fun bindDevice(): ByteArray { + val byteArray = byteArrayOf(0x01, CMD_WRITE.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 彩灯颜色设置/查询消息 + */ + fun ledColor(readOrWrite: Int, R: Int, G: Int, B: Int): ByteArray { + val byteArray = byteArrayOf(0x02, readOrWrite.toByte(), R.toByte(), G.toByte(), B.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 彩灯状态设置/查询消息 + */ + fun ledState(readOrWrite: Int, state: Int): ByteArray { + val byteArray = byteArrayOf(0x03, readOrWrite.toByte(), state.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 激活设备 + */ + fun activeDevice(): ByteArray { + val byteArray = byteArrayOf(0x05, CMD_WRITE.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 关机 + */ + fun shutdownDevice(): ByteArray { + val byteArray = byteArrayOf(0x04, CMD_WRITE.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 获取固件版本号 + */ + fun getFirmwareVersion(): ByteArray { + val byteArray = byteArrayOf(0x09, CMD_READ.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 获取WiFi列表 + */ + fun getWiFiZoneList(): ByteArray { + val byteArray = byteArrayOf(0x0f, CMD_READ.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * Mac地址转字节数组 + */ + private fun macToByte(wifiMac: String): ByteArray { + var byteArray = byteArrayOf() + if (TextUtils.isEmpty(wifiMac)) return byteArray + val macs = wifiMac.split(":") + for (c in macs) { + val hexValue = c.toInt(16) + byteArray = byteArray.plus(hexValue.toByte()) + } + return byteArray + } + + /** + * 4.16 设置WiFi 0x10 + */ + fun setWiFiZone(wiFiZoneBean: WiFiZoneBean): ByteArray { + wiFiZoneBean.apply { + var byteArray = byteArrayOf(0x10, CMD_WRITE.toByte()) + byteArray = byteArray.plus(macToByte(wifiMac)) + val lonArray = longTo4ByteArray((longitude * 1000000L).toLong()) + val latArray = longTo4ByteArray((latitude * 1000000L).toLong()) + byteArray = byteArray.plus(lonArray) + byteArray = byteArray.plus(latArray) + return getCrc8Cmd(byteArray) + } + } + + /** + * 4.17 删除WiFi 0x11 + */ + fun deleteWiFiZone(wifiMac: String): ByteArray { + var byteArray = byteArrayOf(0x11, CMD_WRITE.toByte()) + byteArray = byteArray.plus(macToByte(wifiMac)) + return getCrc8Cmd(byteArray) + } + + /** + * 设置时区 + */ + fun setTime(): ByteArray { + val currentTime = System.currentTimeMillis() / 1000 + val timeMillisByte = longTo4ByteArray(currentTime) + + val tz = TimeZone.getDefault() + //为了不出现负数,数值=实际时区+12 + val timeZone = tz.getOffset(System.currentTimeMillis()) / (1000 * 60 * 60) + 12 + + val byteArray = byteArrayOf( + 0x16, CMD_WRITE.toByte() + ).plus(timeMillisByte).plus((timeZone and 0xff).toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 4.23 参数同步提醒消息 0x017 + */ + fun setNotifySyncData(): ByteArray { + val byteArray = byteArrayOf(0x17, CMD_WRITE.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 启动/停止Live直播 + */ + fun setLiveSwitch(switch: Int, liveTime: Long): ByteArray { + var byteArray = byteArrayOf(0x06, CMD_WRITE.toByte(), switch.toByte()) + byteArray = byteArray.plus(longTo2ByteArray(liveTime)) + return getCrc8Cmd(byteArray) + } + + /** + * 4.32 设置可连接WIFI热点 0x21 + * @param authType wifi认证类型 + */ + fun setConnectWiFi(wifiMac: String, wifiPassword: String, authType: Int): ByteArray { + //20字节一个包,分包发送,前面4个字节为固定包头,后面16个字节为包的内容 + val totalPackage = 12 + wifiPassword.length - 4 + val packageNum = (totalPackage / 16) + 1 + var byteArray = byteArrayOf(0x21, CMD_WRITE.toByte(), packageNum.toByte(), 0x01) + byteArray = byteArray.plus(macToByte(wifiMac)) + byteArray = byteArray.plus(authType.toByte()) + byteArray = byteArray.plus(wifiPassword.length.toByte()) + if (!TextUtils.isEmpty(wifiPassword)) { + val passwordCharArray = wifiPassword.toCharArray() + for (pca in passwordCharArray) { + val b = pca.code.toByte() + byteArray = byteArray.plus(b) + } + } + return getCrc8Cmd(byteArray) + } + + /** + * 恢复出厂设置 + */ + fun factoryReset(): ByteArray { + val byteArray = byteArrayOf(0x19, CMD_WRITE.toByte()) + return getCrc8Cmd(byteArray) + } + + /** + * 设置设备工作状态 + */ + fun setWorkingStatus(writeAndRed: Int = CMD_WRITE, status: Int = 3): ByteArray { + val byteArray = byteArrayOf(0x1A, writeAndRed.toByte(), status.toByte()) + return getCrc8Cmd(byteArray) + } +} + diff --git a/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt new file mode 100644 index 0000000..8b1eae9 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/util/bluetooth/SRBleUtil.kt @@ -0,0 +1,449 @@ +package com.abbidot.tracker.util.bluetooth + +import android.annotation.SuppressLint +import android.app.Activity +import android.app.Application +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothManager +import android.content.Context +import android.content.Intent +import android.text.TextUtils +import androidx.core.app.ActivityCompat.startActivityForResult +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.bean.BleTrackDeviceBean +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.util.Util +import com.clj.fastble.BleManager +import com.clj.fastble.callback.BleGattCallback +import com.clj.fastble.callback.BleNotifyCallback +import com.clj.fastble.callback.BleRssiCallback +import com.clj.fastble.callback.BleWriteCallback +import com.clj.fastble.data.BleDevice +import com.clj.fastble.data.BleScanState +import com.clj.fastble.exception.BleException +import com.clj.fastble.utils.HexUtil +import com.hjq.toast.Toaster +import java.util.UUID + + +/** + *Created by .yzq on 2021/11/24/024. + * @link + * @description: ble蓝牙工具类 + */ +class SRBleUtil private constructor() { + + //track设备蓝牙的开头名字 + val trackDeviceBleStartName = "Pet_Tracker" + + //是否正在连接蓝牙设备 + var isBleConnecting = false + + //人为停止扫描设备,扫描中断 + var isScanInterrupt = false + + /** + * Bluetooth_Base_UUID定义为 00000000-0000-1000-8000-00805F9B34FB + * 若16 bit UUID为xxxx,转换为128 bit UUID为0000xxxx-0000-1000-8000-00805F9B34FB + */ + //发送消息给设备write + private val serviceUUID = UUID.fromString("6e400001-b5a3-f393-e0a9-e50e24dcca9e").toString() + private val writeCharacteristicUUID = + UUID.fromString("6e400002-b5a3-f393-e0a9-e50e24dcca9e").toString() + + //获取设备消息notify,read + private val notifyServiceUUID = serviceUUID + private val notifyCharacteristicUUID = + UUID.fromString("6e400003-b5a3-f393-e0a9-e50e24dcca9e").toString() + + //蓝牙上报notify + private val bleNotifyCharacteristicUUID = + UUID.fromString("6e400004-b5a3-f393-e0a9-e50e24dcca9e").toString() + + //日志上报notify + private val logNotifyCharacteristicUUID = + UUID.fromString("6e400005-b5a3-f393-e0a9-e50e24dcca9e").toString() + + //保存已连接成功设备的全局变量 + lateinit var mTrackDeviceList: MutableList + private var mContext: Context? = null + + //SRBleUtil单例模式 + companion object { + val instance: SRBleUtil by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { + SRBleUtil() + } + } + + + /* + val scanRuleConfig = BleScanRuleConfig.Builder() + // 只扫描指定的服务的设备,可选 + .setServiceUuids(serviceUuids) + // 只扫描指定广播名的设备,可选 + .setDeviceName(true, names) + // 只扫描指定mac的设备,可选 + .setDeviceMac(mac) + // 连接时的autoConnect参数,可选,默认false + .setAutoConnect(isAutoConnect) + // 扫描超时时间,可选,默认10秒 + .setScanTimeOut(10000).build() + BleManager.getInstance().initScanRule(scanRuleConfig) + */ + fun init(app: Application) { + mContext = app.applicationContext + BleManager.getInstance().init(app) + //是否显示框架内部日志,重连次数和重连时间间隔,以及操作超时时间。 + BleManager.getInstance().enableLog(AppUtils.isDebug()).operateTimeout = 5000 + mTrackDeviceList = mutableListOf() + } + + /** + * 判断当前Android设备是否支持BLE。Android 4.3以后系统中加入了蓝牙BLE的功能。 + */ + fun isSupportBle(): Boolean = BleManager.getInstance().isSupportBle + + /** + * 判断当前Android设备的蓝牙是否已经打开 + */ + fun isBlueEnable(context: Context): Boolean { +// BleManager.getInstance().isBlueEnable + // 获取BluetoothManager服务 + val bluetoothManager = + context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager + // 获取BluetoothAdapter实例 + val bluetoothAdapter = bluetoothManager.adapter + return bluetoothAdapter?.isEnabled ?: false + } + + /** + * 打开蓝牙。需要Manifest.permission.BLUETOOTH_CONNECT权限 + */ + @SuppressLint("MissingPermission") + fun openBluetooth(context: Context) { +// BleManager.getInstance().enableBluetooth() + val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) + context.startActivity(intent) + } + + /** + * 通过startActivityForResult引导界面引导用户打开蓝牙 + * 需要Manifest.permission.BLUETOOTH_CONNECT权限 + */ + fun goOpenBluetooth(activity: Activity, requestCode: Int) { + val intent = Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE) + startActivityForResult(activity, intent, requestCode, null) + } + + fun connect(bleDevice: BleDevice) { + BleManager.getInstance().connect(bleDevice, object : BleGattCallback() { + override fun onStartConnect() { + isBleConnecting = true + LogUtil.e("开始连接:${bleDevice.name}--${bleDevice.mac}") + } + + override fun onConnectFail(bleDevice: BleDevice?, exception: BleException?) { + isBleConnecting = false + if (null != bleDevice) { + LogUtil.e("连接失败:${bleDevice.name}--${bleDevice.mac}") + notifyDeviceState(bleDevice, ConState.CONNECTION_FAIL) + } + } + + override fun onConnectSuccess(bleDevice: BleDevice, gatt: BluetoothGatt, status: Int) { + LogUtil.e("连接成功:${bleDevice.name}--${bleDevice.mac}") + isBleConnecting = false + openNotify( + bleDevice, gatt, notifyServiceUUID, notifyCharacteristicUUID + ) + } + + override fun onDisConnected( + isActiveDisConnected: Boolean, + bleDevice: BleDevice, + gatt: BluetoothGatt, + status: Int + ) { + LogUtil.e("蓝牙断开${bleDevice.name},${bleDevice.mac},activeDisConnected=$isActiveDisConnected,status=$status") + notifyDeviceState(bleDevice, ConState.DISCONNECTED) + mContext?.apply { + if (isBlueEnable(this) && !isActiveDisConnected) { + connect(bleDevice) + } + } + } + }) + } + + /** + * 打开通道通知 + */ + fun openNotify( + bleDevice: BleDevice, gatt: BluetoothGatt, uuid_service: String, uuid_notify: String + ) { + BleManager.getInstance() + .notify(bleDevice, uuid_service, uuid_notify, object : BleNotifyCallback() { + override fun onNotifySuccess() { + LogUtil.e("onNotifySuccess:${bleDevice.name}--${bleDevice.mac}") + //绑定 + writeData(bleDevice, SRBleCmdUtil.instance.bindDevice()) + } + + override fun onNotifyFailure(exception: BleException?) { + LogUtil.e("onNotifyFailure--${bleDevice.mac},$exception") + //打开通道失败,断开连接重新连接 + gatt.disconnect() + connect(bleDevice) + } + + override fun onCharacteristicChanged(data: ByteArray?) { + val formatData = HexUtil.formatHexString(data, true) + LogUtil.e("设备响应指令,${bleDevice.mac}->" + formatData) + data?.apply { + val data0 = SRBleCmdUtil.instance.byteToInt(data[0]) + val data1 = SRBleCmdUtil.instance.byteToInt(data[1]) + val data2 = SRBleCmdUtil.instance.byteToInt(data[2]) + //绑定设备成功返回 + if (data0 == 0x01 && data1 == 0x01 && data2 == 0x00) { + LogUtil.e("${bleDevice.mac},绑定成功") + //同步时间 + writeData(bleDevice, SRBleCmdUtil.instance.setTime()) + } else if (data0 == 0x16 && data1 == 0x01 && data2 == 0x00) { + //同步时间成功返回 + LogUtil.e("${bleDevice.mac},同步时间成功") + openBleReportNotify( + bleDevice, notifyServiceUUID, bleNotifyCharacteristicUUID + ) + } else { + val deviceData = ReceiveDeviceData(bleDevice, data, bleDevice.mac) + XEventBus.post(EventName.DeviceReceiveData, deviceData) + } + } + } + }) + } + + /** + * 打开蓝牙上报通道 + */ + private fun openBleReportNotify( + bleDevice: BleDevice, uuid_service: String, uuid_notify: String + ) { + BleManager.getInstance() + .notify(bleDevice, uuid_service, uuid_notify, object : BleNotifyCallback() { + override fun onNotifySuccess() { +// Toaster.show("打开蓝牙上报通道成功") + LogUtil.e("打开蓝牙上报通道成功:${bleDevice.name}--${bleDevice.mac}") + openLogReportNotify(bleDevice, notifyServiceUUID, logNotifyCharacteristicUUID) + } + + override fun onNotifyFailure(exception: BleException?) { +// Toaster.show("打开蓝牙上报通道失败") + LogUtil.e("打开蓝牙上报通道失败:${bleDevice.mac},$exception") + openLogReportNotify(bleDevice, notifyServiceUUID, logNotifyCharacteristicUUID) + } + + override fun onCharacteristicChanged(data: ByteArray?) { + data?.apply { + val formatData = HexUtil.formatHexString(data, true) + LogUtil.e("接收蓝牙上报通道数据:${bleDevice.mac},$formatData") + val deviceData = ReceiveDeviceData(bleDevice, data, bleDevice.mac) + XEventBus.post(EventName.BleReport, deviceData) + } + } + }) + } + + /** + * 打开日志上报通道 + */ + private fun openLogReportNotify( + bleDevice: BleDevice, uuid_service: String, uuid_notify: String + ) { + BleManager.getInstance() + .notify(bleDevice, uuid_service, uuid_notify, object : BleNotifyCallback() { + override fun onNotifySuccess() { + LogUtil.e("打开日志上报通道成功:${bleDevice.name}--${bleDevice.mac}") + notifyDeviceState(bleDevice, ConState.CONNECTED) + } + + override fun onNotifyFailure(exception: BleException?) { + LogUtil.e("打开日志上报通道失败:${bleDevice.mac},$exception") + notifyDeviceState(bleDevice, ConState.CONNECTED) + } + + override fun onCharacteristicChanged(data: ByteArray?) { + data?.apply { +// val formatData = HexUtil.formatHexString(data, true) +// LogUtil.e("接收日志上报通道数据:${bleDevice.mac},$formatData") +// LogUtil.e(String(data, Charsets.UTF_8)) + val deviceData = ReceiveDeviceData(bleDevice, data, bleDevice.mac) + XEventBus.post(EventName.LogReport, deviceData) + } + } + }) + } + + /** + * 关闭通道通知 + */ + fun closeNotify(bleDevice: BleDevice, uuid_service: String, uuid_notify: String) { + BleManager.getInstance().stopNotify(bleDevice, uuid_service, uuid_notify) + } + + + /** + * 发送数据 + */ + fun writeData(bleDevice: BleDevice?, data: ByteArray) { + if (null == bleDevice) return + writeData(bleDevice, serviceUUID, writeCharacteristicUUID, data) + } + + private fun writeData( + bleDevice: BleDevice, uuidService: String, uuidWrite: String, data: ByteArray + ) { + LogUtil.d("send cmd:" + HexUtil.formatHexString(data, true)) + BleManager.getInstance() + .write(bleDevice, uuidService, uuidWrite, data, object : BleWriteCallback() { + override fun onWriteSuccess(current: Int, total: Int, justWrite: ByteArray?) { + LogUtil.e( + "writeData onWriteSuccess data:" + HexUtil.formatHexString(data, true) + ) + } + + override fun onWriteFailure(exception: BleException?) { + LogUtil.e( + "writeData onWriteFailure data:" + HexUtil.formatHexString( + data, true + ) + " , error = " + exception.toString() + ) + } + }) + } + + /** + * 通知设备状态 + */ + private fun notifyDeviceState(bleDevice: BleDevice, @ConState consState: Int) { + LogUtil.e("更新蓝牙连接状态consState=$consState") + var device: BleTrackDeviceBean? = null + + for (trackDevice in mTrackDeviceList) { + if (trackDevice.mac == bleDevice.mac) { + device = trackDevice + break + } + } + if (null == device) { + device = BleTrackDeviceBean() + device.mac = bleDevice.mac + } + device.conState = consState + device.bleDevice = bleDevice + + if (!mTrackDeviceList.contains(device)) { + mTrackDeviceList.add(device) + } + + XEventBus.post(EventName.ConnectDeviceState, device) + } + + /** + * 获取已连接指定的Mac设备 + */ + fun getConnectMacDevice(mac: String): BleTrackDeviceBean? { + var trackDeviceBean: BleTrackDeviceBean? = null + for (device in mTrackDeviceList) { + if (device.mac == mac && BleManager.getInstance().isConnected(mac)) { + trackDeviceBean = device + break + } + } + return trackDeviceBean + } + + /** + * 断开指定的蓝牙设备 + */ + fun disconnectToMac(mac: String) { + if (TextUtils.isEmpty(mac)) { + return + } + for (ble in mTrackDeviceList) { + if (mac == ble.mac) { + ble.bleDevice?.let { +// closeNotify(it, notifyServiceUUID, logNotifyCharacteristicUUID) +// closeNotify(it, notifyServiceUUID, bleNotifyCharacteristicUUID) + BleManager.getInstance().disconnect(it) + } + break + } + } + } + + /** + * 获取连接设备的实时信号 + */ + fun readConnectRssi(mac: String, callback: BleRssiCallback) { + if (TextUtils.isEmpty(mac)) { + return + } + for (ble in mTrackDeviceList) { + if (mac == ble.mac) { + ble.bleDevice?.let { + BleManager.getInstance().readRssi(it, callback) + } + break + } + } + } + + /** + * 发送参数同步提醒消息 0x017 + */ + fun isConnectBleSendNotifyData(mac: String) { + if (TextUtils.isEmpty(mac)) { + return + } + if (BleManager.getInstance().isConnected(mac)) { + val trackDeviceBean = getConnectMacDevice(mac) + trackDeviceBean?.let { + writeData( + it.bleDevice, SRBleCmdUtil.instance.setNotifySyncData() + ) + } + } + } + + /** + *发送设备开启关闭led蓝牙指令 + */ + fun setBleLedSwitch(context: Context, bleDevice: BleDevice?, mode: Int) { + //判断是否有蓝牙连接 + if (BleManager.getInstance().isConnected(bleDevice)) { + Util.checkBluetoothPermissionsEnabled(context, { + writeData( + bleDevice, SRBleCmdUtil.instance.ledState(SRBleCmdUtil.CMD_WRITE, mode) + ) + }) + } + } + + /** + * 取消蓝牙扫描 + */ + fun cancelBleScan(log: String = "") { + if (BleManager.getInstance().scanSate == BleScanState.STATE_SCANNING) { + isScanInterrupt = true + if (TextUtils.isEmpty(log)) LogUtil.e("正在扫描设备,取消蓝牙扫描") + else LogUtil.e(log) + BleManager.getInstance().cancelScan() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt new file mode 100644 index 0000000..3879482 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/AppSetViewModel.kt @@ -0,0 +1,43 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.bean.NotificationBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2024/9/26/026. + * @link + * @description: + */ +class AppSetViewModel : ViewModel() { + + val mGetNotificationLiveData = MutableLiveData>>() + val mSetNotificationLiveData = MutableLiveData>() + + /** + * 获取通知设置 + */ + fun getNotificationSet() { + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getNotificationSet(userId) + mGetNotificationLiveData.value = result + } + } + + /** + * 消息通知设置 + */ + fun setNotification(notificationType: Int, status: Int) { + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.setNotification(userId, notificationType, status) + mSetNotificationLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/ConnectionDeviceViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/ConnectionDeviceViewModel.kt new file mode 100644 index 0000000..1125adf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/ConnectionDeviceViewModel.kt @@ -0,0 +1,282 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import android.os.CountDownTimer +import android.text.TextUtils +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.ConState +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.database.MyDatabase +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager +import com.clj.fastble.callback.BleScanCallback +import com.clj.fastble.data.BleDevice +import com.clj.fastble.data.BleScanState +import com.clj.fastble.scan.BleScanRuleConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +/** + *Created by on 2022/5/26/026. + * @link + * @description:自动连接设备 + */ +class ConnectionDeviceViewModel : ViewModel() { + + private var mCountDownTimer: CountDownTimer? = null + + fun autoConnectBleDevice(context: Context) { + //判断蓝牙是否打开 + if (!SRBleUtil.instance.isBlueEnable(context)) { + LogUtil.e("automaticConnection,蓝牙没打开") + return + } + if (BleManager.getInstance().scanSate == BleScanState.STATE_SCANNING) { + LogUtil.e("automaticConnection,已经在扫描蓝牙,开始扫描失败") + return + } + viewModelScope.launch(Dispatchers.IO) { + val localDevices = MyDatabase.deviceDao() + .findAll(MyDatabase.DeviceTableName) as MutableList + LogUtil.e("查找本地ble设备有${localDevices.size}个") + if (localDevices.size == 0) { + return@launch + } + val scanTrackBle = mutableListOf() + val scanRuleConfig = BleScanRuleConfig.Builder().build() + BleManager.getInstance().initScanRule(scanRuleConfig) + BleManager.getInstance().scan(object : BleScanCallback() { + override fun onScanStarted(success: Boolean) { + SRBleUtil.instance.isScanInterrupt = false + LogUtil.e("开始扫描...$success") + } + + override fun onScanning(bleDevice: BleDevice?) { + LogUtil.d("onScanning....$bleDevice") + bleDevice?.apply { + if (null != name && name.contains(SRBleUtil.instance.trackDeviceBleStartName)) { + scanTrackBle.add(this) + } + } + } + + override fun onScanFinished(scanResultList: MutableList?) { + val interrupt = SRBleUtil.instance.isScanInterrupt + LogUtil.e("automaticConnection--扫描完成..中断扫描$interrupt,${scanResultList?.size}") + if (!interrupt) { + compareDevice(scanTrackBle, localDevices) + } + } + }) + } + } + + /** + * 从扫描到的设备中查找已添加的设备,并连接设备 + */ + private fun compareDevice( + scanResultList: MutableList, localDevices: MutableList + ) { + if (scanResultList.size > 0) { + var connected = false + viewModelScope.launch(Dispatchers.IO) { + //创建新的变量,防止别的蓝牙扫描(蓝牙找设备功能)改变scanResultList变量,导致异常 + val scanBleList = mutableListOf() + scanBleList.addAll(scanResultList) + for (device in localDevices) { + for (bleDevice in scanBleList) { + if (!TextUtils.isEmpty(bleDevice.name)) { + if (device.macId == bleDevice.mac) { + SRBleUtil.instance.connect(bleDevice) + connected = true + break + } + } + } + } + if (!connected) { + LogUtil.e("没有蓝牙设备可连") + SRBleUtil.instance.isBleConnecting = false +// XEventBus.post(EventName.ActionConDeviceState, ConState.DEVICE_NOT_FOUND) + } + } + } else { + LogUtil.e("蓝牙设备为0") + SRBleUtil.instance.isBleConnecting = false +// XEventBus.post(EventName.ActionConDeviceState, ConState.DEVICE_NOT_FOUND) + } + } + + /** + * 连接中倒计时20s变为未连接状态 + */ + fun connectingFailCountDown() { + mCountDownTimer = object : CountDownTimer(20 * 1000, 1000) { + override fun onTick(time: Long) { + } + + override fun onFinish() { + LogUtil.e("连接中倒计时20s变为未连接状态") + XEventBus.post(EventName.ActionConDeviceState, ConState.CONNECTION_FAIL) + } + }.start() + } + + /** + * 连接成功后取消倒计时 + */ + private fun cancelFailCountDown() { + mCountDownTimer?.cancel() + } + + /** + * 根据mac连接设备 + */ + fun connectDeviceToMac(activity: BaseActivity<*>, mac: String, showLoading: Boolean = true) { + if (TextUtils.isEmpty(mac)) { + return + } + if (SRBleUtil.instance.isBleConnecting) { + LogUtil.e("已经有设备在连接中...") + XEventBus.post(EventName.ActionConDeviceState, ConState.CONNECTING_MAC) + return + } + Util.checkBluetoothPermissionsEnabled(activity, { + SRBleUtil.instance.cancelBleScan("connectDeviceToMac,已经在扫描蓝牙,取消上次扫描") + if (showLoading) activity.showLoading(showLoading, R.string.txt_connecting, false) + viewModelScope.launch(Dispatchers.IO) { + //防止和自动连接设备,扫描完成后回调冲突 + if (SRBleUtil.instance.isScanInterrupt) { + delay(1000) + } + //只扫描指定的mac设备 + val scanRuleConfig = BleScanRuleConfig.Builder().setDeviceMac(mac).build() + BleManager.getInstance().initScanRule(scanRuleConfig) + BleManager.getInstance().scan(object : BleScanCallback() { + override fun onScanStarted(success: Boolean) { + LogUtil.e("开始扫描${mac}设备...") + SRBleUtil.instance.isScanInterrupt = false + XEventBus.post(EventName.ActionConDeviceState, ConState.CONNECTING_MAC) + } + + override fun onScanning(bleDevice: BleDevice?) { + LogUtil.d("onScanning....$bleDevice") + bleDevice?.apply { + if (bleDevice.mac == mac) BleManager.getInstance().cancelScan() + } + } + + override fun onScanFinished(scanResultList: MutableList?) { + val interrupt = SRBleUtil.instance.isScanInterrupt + if (interrupt) { + LogUtil.e("connectDeviceToMac--扫描完成..中断扫描") + } else { + activity.showLoading(false) + scanResultList?.let { + val size = scanResultList.size + LogUtil.e("扫描${mac}设备完成...$size") + if (size > 0) { + SRBleUtil.instance.connect(scanResultList[0]) + } else { + XEventBus.post( + EventName.ActionConDeviceState, ConState.DEVICE_NOT_FOUND + ) + } + } + } + } + }) + } + }) + } + + /** + * 根据外显id连接设备 + */ + fun connectDeviceToDeviceOutId( + activity: BaseActivity<*>, deviceOutId: String, showLoading: Boolean = true + ) { + if (TextUtils.isEmpty(deviceOutId)) { + return + } + var isHaveScan = false + Util.checkBluetoothPermissionsEnabled(activity, { + SRBleUtil.instance.cancelBleScan("connectDeviceToDeviceOutId,已经在扫描蓝牙,取消上次扫描") + activity.showLoading(showLoading, R.string.txt_search_device, false) + viewModelScope.launch(Dispatchers.IO) { + //防止和自动连接设备,扫描完成后回调冲突 + if (SRBleUtil.instance.isScanInterrupt) { + delay(1000) + } + val scanRuleConfig = BleScanRuleConfig.Builder().setScanTimeOut(10 * 1000).build() + BleManager.getInstance().initScanRule(scanRuleConfig) + BleManager.getInstance().scan(object : BleScanCallback() { + override fun onScanStarted(success: Boolean) { + LogUtil.e("开始扫描${deviceOutId}外显id设备...") + SRBleUtil.instance.isScanInterrupt = false + XEventBus.post(EventName.ActionConDeviceState, ConState.CONNECTING) + } + + override fun onScanning(bleDevice: BleDevice?) { + LogUtil.d("onScanning....$bleDevice") + bleDevice?.apply { + if (null != name && name.contains(SRBleUtil.instance.trackDeviceBleStartName)) { + val start = 45 + val end = start + 6 + val subArray = scanRecord.sliceArray(start until end) + val scanDeviceOutId = subArray.decodeToString() + if (scanDeviceOutId == deviceOutId) { + isHaveScan = true + BleManager.getInstance().cancelScan() + SRBleUtil.instance.connect(this) + } + } + } + } + + override fun onScanFinished(scanResultList: MutableList?) { + val interrupt = SRBleUtil.instance.isScanInterrupt + if (interrupt) { + LogUtil.e("connectDeviceToDeviceOutId--扫描完成..中断扫描") + } else { + LogUtil.e("扫描${deviceOutId}外显id设备 结束") + if (isHaveScan) { + activity.showLoading(showLoading, R.string.txt_connecting, false) + } else { + activity.showLoading(false) + XEventBus.post( + EventName.ActionConDeviceState, ConState.DEVICE_NOT_FOUND + ) + } + } + } + }) + } + }) + } + + /** + * 断开连接 + */ + fun disconnect(bleDevice: BleDevice?) { + bleDevice?.let { + BleManager.getInstance().disconnect(it) + } + } + + override fun onCleared() { + super.onCleared() + LogUtil.e("ConnectionDeviceViewModel,onCleared") + SRBleUtil.instance.isBleConnecting = false + SRBleUtil.instance.cancelBleScan("退出APP,取消扫描蓝") + cancelFailCountDown() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/CountDownTimerViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/CountDownTimerViewModel.kt new file mode 100644 index 0000000..6bd687d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/CountDownTimerViewModel.kt @@ -0,0 +1,43 @@ +package com.abbidot.tracker.vm + +import android.os.CountDownTimer +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +/** + *Created by .yzq on 2022/7/18/018. + * @link + * @description: 倒计时 + */ +class CountDownTimerViewModel : ViewModel() { + val mCountDowningLiveData = MutableLiveData() + val mCountDownEndLiveData = MutableLiveData() + private var mCountDownTimer: CountDownTimer? = null + + /** + * @param second 秒 + * 倒计时 + */ + fun startCountDown(second: Long, countDownInterval: Long = 1) { + stopCountDown() + mCountDownTimer = object : CountDownTimer(second * 1000, countDownInterval * 1000L) { + override fun onTick(millisUntilFinished: Long) { + val time = millisUntilFinished / 1000 + mCountDowningLiveData.value = time + } + + override fun onFinish() { + mCountDownEndLiveData.value = 0L + } + }.start() + } + + fun stopCountDown() { + mCountDownTimer?.cancel() + } + + override fun onCleared() { + super.onCleared() + stopCountDown() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/DataDetailViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/DataDetailViewModel.kt new file mode 100644 index 0000000..cc440e7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/DataDetailViewModel.kt @@ -0,0 +1,85 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.ActiveTimeBean +import com.abbidot.tracker.bean.HistoryDataBean +import com.abbidot.tracker.bean.SleepTimeBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/16/016. + * @link + * @description: + */ +class DataDetailViewModel : ViewModel() { + + val mActiveDetailLiveData = MutableLiveData>() + val mSleepDetailLiveData = MutableLiveData>() + val mHistoryDetailLive = MutableLiveData>>() + val mSetGoalLiveData = MutableLiveData>() + val mPetActivityInfoLiveData = MutableLiveData>() + + /** + * 运动时间详情 + */ + fun getActiveTimeByDay( + activity: BaseActivity<*>, deviceId: String, dayTime: String, petId: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getActiveTimeByDay(deviceId, dayTime, petId) + mActiveDetailLiveData.value = result + } + } + + /** + * 获取SleepTime数据 + */ + fun getSleepTimeByDay( + activity: BaseActivity<*>, + deviceId: String, + dayTime: String, + petId: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getSleepTimeByDay(deviceId, dayTime, petId) + mSleepDetailLiveData.value = result + } + } + + fun getHistoryByDay(activity: BaseActivity<*>, deviceId: String, fromTime: Long, toTime: Long) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getHistoryByDay(deviceId, fromTime, toTime) + mHistoryDetailLive.value = result + } + } + + /** + *设置运动目标 + * @param goalTime min + */ + fun setGoal(activity: BaseActivity<*>, deviceId: String, goalTime: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.setGoal(deviceId, goalTime) + mSetGoalLiveData.value = result + } + } + + /** + * 获取宠物活动统计 + */ + fun getPetActivityDetailInfo(activity: BaseActivity<*>, petId: String, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getPetActivityDetailInfo(petId, deviceId) + mPetActivityInfoLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/DataViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/DataViewModel.kt new file mode 100644 index 0000000..b1f91a8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/DataViewModel.kt @@ -0,0 +1,53 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.HomeDataBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/11/011. + * @link + * @description: + */ +class DataViewModel : ViewModel() { + + val mHomePetLiveData = MutableLiveData>>() + val mHomeDataLiveData = MutableLiveData>() + + /** + * 获取宠物数据 + */ + fun getHomeBindPetList(activity: BaseActivity<*>) { + activity.showLoading(true, cancelable = false) + getHomeBindPetList() + } + + /** + * 获取宠物数据 + */ + fun getHomeBindPetList() { + val userId = MMKVUtil.getString(MMKVKey.UserId) + viewModelScope.launch { + val result = NetworkApi.getHomeBindPetList(userId) + mHomePetLiveData.value = result + } + } + + /** + * 获取首页Data模块的设备数据 + */ + fun getHomeDeviceData(deviceId: String, dayTime: String, petId: String) { + viewModelScope.launch { + val result = NetworkApi.getDeviceData(deviceId, dayTime, petId) + mHomeDataLiveData.value = result + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/DeviceDFUViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/DeviceDFUViewModel.kt new file mode 100644 index 0000000..86c7194 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/DeviceDFUViewModel.kt @@ -0,0 +1,235 @@ +package com.abbidot.tracker.vm + +import android.app.Application +import android.content.Context +import android.os.Build +import android.text.TextUtils +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DFUStateBean +import com.abbidot.tracker.bean.FirmwareBean +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.service.DfuService +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.clj.fastble.BleManager +import com.clj.fastble.callback.BleScanCallback +import com.clj.fastble.data.BleDevice +import com.clj.fastble.data.BleScanState +import com.clj.fastble.scan.BleScanRuleConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import no.nordicsemi.android.dfu.DfuProgressListener +import no.nordicsemi.android.dfu.DfuServiceInitiator + +/** + *Created by .yzq on 2023/3/16/016. + * @link + * @description: + */ +class DeviceDFUViewModel(application: Application) : AndroidViewModel(application) { + + val mFirmwareInfoLiveData = MutableLiveData>() + val mDfuStateLiveData = MutableLiveData() + + //dfu成功 + val mDFUSuccessCode = 101 + + //dfu失败 + val mDFUFailCode = -2 + + //记下更新时间,不用频繁更新 + private var mUpdatePostTimeMillis = System.currentTimeMillis() + + + /** + * 获取固件信息 + */ + fun getFirmware(activity: BaseActivity<*>, isShowLoading: Boolean = true) { + activity.showLoading(isShowLoading) + viewModelScope.launch(Dispatchers.IO) { + val result = NetworkApi.getFirmware("GPS_Tracker_01") + mFirmwareInfoLiveData.postValue(result) + } + } + + /** + * 对比版本号是否可以更新 + */ + fun checkVersionUpdate(localVersion: String, serviceVersion: String): Boolean { + if (TextUtils.isEmpty(localVersion)) return false + val localVersionInt = (localVersion.replace(".", "").substring(1, 4)).toInt() + val serviceVersionInt = serviceVersion.replace(".", "").toInt() + LogUtil.e("对比固件版本localVersionInt=$localVersionInt,serviceVersionInt=$serviceVersionInt") + return serviceVersionInt > localVersionInt + } + + fun startDFU(context: Context, device: BleDevice, path: String) { + viewModelScope.launch { + val dFUStateBean = DFUStateBean(context.getString(R.string.txt_dfu_model)) + mDfuStateLiveData.value = dFUStateBean + //是否扫描到 + var isScan = false + val dfuCmd = byteArrayOf(0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66) + SRBleUtil.instance.writeData(device, dfuCmd) + if (BleScanState.STATE_IDLE != BleManager.getInstance().scanSate) BleManager.getInstance() + .cancelScan() + // 扫描超时时间,可选,默认10秒 + val scanRuleConfig = BleScanRuleConfig.Builder().setScanTimeOut(60000).build() + BleManager.getInstance().initScanRule(scanRuleConfig) + BleManager.getInstance().scan(object : BleScanCallback() { + override fun onScanStarted(success: Boolean) { + val stateBean = DFUStateBean(context.getString(R.string.txt_search_dfu)) + mDfuStateLiveData.value = stateBean + LogUtil.e("开始搜索DUF设备。。。。") + } + + override fun onScanning(bleDevice: BleDevice?) { + bleDevice?.apply { + LogUtil.e("搜索DUF设备中。。。已搜索到$name") + val deviceDFUName = "DfuTarg" +// val deviceDFUName = "xxx" + if (!TextUtils.isEmpty(name) && name.contains(deviceDFUName)) { + isScan = true + BleManager.getInstance().cancelScan() + dfu(context, bleDevice, path) + } + } + } + + override fun onScanFinished(scanResultList: MutableList?) { + LogUtil.e("搜索DUF设备结束。") + if (!isScan) { + val stateBean = + DFUStateBean(context.getString(R.string.txt_no_dfu), mDFUFailCode) + mDfuStateLiveData.value = stateBean + } + } + }) + } + } + + + private fun dfu(context: Context, bleDevice: BleDevice, path: String) { + val dfuServiceInitiator = DfuServiceInitiator(bleDevice.mac) + dfuServiceInitiator.setDeviceName(bleDevice.name) + dfuServiceInitiator.setKeepBond(false).setPacketsReceiptNotificationsEnabled(false) + .setPacketsReceiptNotificationsValue(10) + .setUnsafeExperimentalButtonlessServiceInSecureDfuEnabled(true).setZip(path) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + //Android O,通知栏问题 + DfuServiceInitiator.createDfuNotificationChannel(context) + } + dfuServiceInitiator.start(context, DfuService::class.java) + } + + val mDfuProgressListener = object : DfuProgressListener { + + private val context = getApplication() + + /**设备正在连接 */ + override fun onDeviceConnecting(deviress: String) { + val stateBean = DFUStateBean(context.getString(R.string.txt_connect_device)) + mDfuStateLiveData.value = stateBean + LogUtil.e("DfuProgressListener -->> onDeviceConnecting,dfu开始连接设备") + } + + /** + * 设备连接了 1 + * @param deviceAddress + */ + override fun onDeviceConnected(deviceAddress: String) { + val stateBean = DFUStateBean(context.getString(R.string.txt_device_connect_success)) + mDfuStateLiveData.value = stateBean + LogUtil.e("DfuProgressListener -->> dfu连接设备成功 onDeviceConnected") + } + + /**升级准备开始 + * 2 + */ + override fun onDfuProcessStarting(deviceAddress: String) { + val stateBean = DFUStateBean(context.getString(R.string.txt_upgrade_start)) + mDfuStateLiveData.value = stateBean + LogUtil.e("DfuProgressListener -->> onDfuProcessStarting") + } + + /**更新进度开始 + * 3 + */ + override fun onDfuProcessStarted(deviceAddress: String) { + LogUtil.e("DfuProgressListener -->> onDfuProcessStarted") + } + + override fun onEnablingDfuMode(deviceAddress: String) { + LogUtil.e("DfuProgressListener -->> onEnablingDfuMode") + } + + /**升级中的回调 */ + override fun onProgressChanged( + deviceAddress: String, + percent: Int, + speed: Float, + avgSpeed: Float, + currentPart: Int, + partsTotal: Int + ) { + val currentTimeMillis = System.currentTimeMillis() + //500ms更新发送数据,不用频繁更新 + if (currentTimeMillis - mUpdatePostTimeMillis > 500 || percent == 100) { + mUpdatePostTimeMillis = currentTimeMillis + val dFUStateBean = + DFUStateBean(context.getString(R.string.txt_upgrading) + " ", percent) + mDfuStateLiveData.value = dFUStateBean + } + } + + /**固件验证-未用 */ + override fun onFirmwareValidating(deviceAddress: String) { + LogUtil.e("DfuProgressListener -->> onFirmwareValidating") + } + + /**设备正在断开 + * 4 + */ + override fun onDeviceDisconnecting(deviceAddress: String) { + val dFUStateBean = DFUStateBean(context.getString(R.string.txt_unconnect_no)) + mDfuStateLiveData.value = dFUStateBean + LogUtil.e("DfuProgressListener -->> onDeviceDisconnecting") + } + + /**设备断开了 + * 5 + */ + override fun onDeviceDisconnected(deviceAddress: String) { + val stateBean = DFUStateBean(context.getString(R.string.txt_unconnect)) + mDfuStateLiveData.value = stateBean + LogUtil.e("DfuProgressListener -->> onDeviceDisconnected") + } + + /**升级完成 + * 6 + */ + override fun onDfuCompleted(deviceAddress: String) { + val stateBean = DFUStateBean(context.getString(R.string.txt_upgrade_success)) + mDfuStateLiveData.value = stateBean + LogUtil.e("DfuProgressListener -->> onDfuCompleted") + val state = + DFUStateBean(context.getString(R.string.txt_reconnect_device), mDFUSuccessCode) + mDfuStateLiveData.value = state + } + + override fun onDfuAborted(deviceAddress: String) { + LogUtil.e("DfuProgressListener -->> onDfuAborted") + } + + /**升级失败 */ + override fun onError(deviceAddress: String, error: Int, errorType: Int, message: String) { + LogUtil.e("DfuProgressListener -->> onError") + val stateBean = DFUStateBean(context.getString(R.string.txt_upgrade_fail), mDFUFailCode) + mDfuStateLiveData.value = stateBean + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/DownLoadFileViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/DownLoadFileViewModel.kt new file mode 100644 index 0000000..15b8eb2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/DownLoadFileViewModel.kt @@ -0,0 +1,170 @@ +package com.abbidot.tracker.vm + +import android.app.DownloadManager +import android.content.Context +import android.content.Context.DOWNLOAD_SERVICE +import android.net.Uri +import android.text.TextUtils +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DFUStateBean +import com.abbidot.tracker.util.FileUtil +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import java.io.File + + +/** + *Created by .yzq on 2023/3/22/022. + * @link + * @description:通知中心显示下载文件 + */ +class DownLoadFileViewModel : ViewModel() { + + val mDownloadProgressLiveData = MutableLiveData() + private var mDownloadManager: DownloadManager? = null + private var mDownloadId: Long = 0L + private var isLoop = true + + fun downLoadFile(context: Context, fileName: String, downloadUrl: String) { + if (TextUtils.isEmpty(downloadUrl)) { + return + } + //获取缓存的file目录context.getExternalFilesDir() + val file = File(context.getExternalFilesDir(FileUtil.DFU_DIRECTORY_NAME), fileName) + //文件存在就删除 + if (file.exists()) { + file.delete() + } + mDownloadManager = context.getSystemService(DOWNLOAD_SERVICE) as DownloadManager + val uri: Uri = Uri.parse(downloadUrl) + val request = DownloadManager.Request(uri) + //设置文件类型 + request.setMimeType("zip/*") + //在通知栏中显示 + //在下载进行中时显示,在下载完成后就不显示了。可以设置如下三个值: + // VISIBILITY_HIDDEN 下载UI不会显示,也不会显示在通知中,如果设置该值,需要声明android.permission.DOWNLOAD_WITHOUT_NOTIFICATION + // VISIBILITY_VISIBLE 当处于下载中状态时,可以在通知栏中显示;当下载完成后,通知栏中不显示 + // VISIBILITY_VISIBLE_NOTIFY_COMPLETED 当处于下载中状态和下载完成时状态,均在通知栏中显示 + // VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION 只在下载完成时显示在通知栏中。 + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN) + request.setTitle(fileName) + request.setDescription("正在下载") + + //设置为可被媒体扫描器找到 +// request.allowScanningByMediaScanner() + //设置公共目录,希望被其他的应用共享,比如音乐文件 +// request. setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName) + + //设置自定义目录,软件卸载后,下载的文件将随着卸载全部被删除 + request.setDestinationInExternalFilesDir(context, FileUtil.DFU_DIRECTORY_NAME, fileName) + + //注册广播,监听下载状态 +// mIntentFilter = IntentFilter() +// mIntentFilter!!.addAction(DownloadManager.ACTION_DOWNLOAD_COMPLETE) +// mIntentFilter!!.addAction(DownloadManager.ACTION_NOTIFICATION_CLICKED) +// context.registerReceiver(receiver, mIntentFilter) + + // 将下载请求放入队列 + mDownloadId = mDownloadManager!!.enqueue(request) + isLoop = true + updateProgress(context) + } + + private fun updateProgress(context: Context) { + viewModelScope.launch(Dispatchers.IO) { + while (isLoop) { + val query = DownloadManager.Query() + val cursor = mDownloadManager!!.query(query.setFilterById(mDownloadId)) + if (cursor.moveToFirst()) { + var path = "" + val bytesDownloaded = cursor.getFloat( + cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR) + ) + val bytesTotal = cursor.getFloat( + cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES) + ) + val progress = (bytesDownloaded / bytesTotal) * 100 + LogUtil.d("下载进度 bytesDownloaded=$bytesDownloaded 总文件大小:bytesTotal=$bytesTotal") + LogUtil.d(">>>下载进度$progress") + val columnStatus = + cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) + if (columnStatus == DownloadManager.STATUS_SUCCESSFUL) { + // 成功后取消循环 + isLoop = false + val localUri = cursor.getString( + cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) + ) + val uri = Uri.parse(localUri) + LogUtil.i(localUri) + path = uri.path.toString() + LogUtil.i(path) + } + val dfuStateBean = DFUStateBean( + context.getString(R.string.txt_down_firmware), + progress.toInt(), + path + ) + mDownloadProgressLiveData.postValue(dfuStateBean) + } + cursor.close() + //延时循环 + delay(500) + } + } + } + + /** + * 广播接受者,接收下载状态 + */ +// private val receiver = object : BroadcastReceiver() { +// override fun onReceive(context: Context, intent: Intent) { +// val action = intent.action +// if (action.equals(DownloadManager.ACTION_DOWNLOAD_COMPLETE)) { +// val downloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) +// //检查下载状态 +// checkDownloadStatus(downloadId) +// } else if (action.equals(DownloadManager.ACTION_NOTIFICATION_CLICKED)) { +// Toast.makeText(context, "用户点击了通知栏", Toast.LENGTH_SHORT).show(); +// } +// +// } + /** + * 检查下载状态 + */ +// private fun checkDownloadStatus(downloadId: Long) { +// val query = DownloadManager.Query() +// query.setFilterById(downloadId) //筛选下载任务,传入任务ID,可变参数 +// val cursor = mDownloadManager!!.query(query) +// val status: Int +// if (cursor.moveToFirst()) { +// status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) +// when (status) { +// DownloadManager.STATUS_PAUSED -> LogUtil.i(">>>下载暂停") +// DownloadManager.STATUS_PENDING -> LogUtil.i(">>>下载延迟") +// DownloadManager.STATUS_RUNNING -> LogUtil.i(">>>正在下载")// 此处无法监听到 +// DownloadManager.STATUS_SUCCESSFUL -> LogUtil.i(">>>下载完成") +// DownloadManager.STATUS_FAILED -> LogUtil.i(">>>下载失败") +// } +// } +// val bytesDownload = cursor.getLong( +// cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) +// val totalSize = +// cursor.getLong(cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) +// } +// } + + + override fun onCleared() { + super.onCleared() + mDownloadManager?.apply { + //用于删除下载,如果下载中则取消下载, 同时会删除下载文件和记录 + remove(mDownloadId) + } + isLoop = false + } +} diff --git a/app/src/main/java/com/abbidot/tracker/vm/FamilyViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/FamilyViewModel.kt new file mode 100644 index 0000000..431cd9c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/FamilyViewModel.kt @@ -0,0 +1,86 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.FamilyBean +import com.abbidot.tracker.bean.FamilyPetBean +import com.abbidot.tracker.bean.InviteInfoBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/11/011. + * @link + * @description: + */ +class FamilyViewModel : ViewModel() { + + val mInviteLiveData = MutableLiveData>() + val mFamilyListLiveData = MutableLiveData>() + val mFamilyRemoveLiveData = MutableLiveData>() + val mInviteInfoLiveData = MutableLiveData>() + + fun invite(activity: BaseActivity<*>, familyBean: FamilyBean) { + activity.showLoading(true) + viewModelScope.launch { + familyBean.apply { + val result = NetworkApi.invite( + userId, + userName, + sendEmail, + deviceId, + acceptEmail, + relation, + avatar, + petId, + petName + ) + mInviteLiveData.value = result + } + } + } + + fun inviteCN( + fragment: BaseFragment<*>, userId: String, userName: String, deviceId: String, phone: String + ) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.inviteCN(userId, userName, deviceId, phone) + mInviteLiveData.value = result + } + } + + + fun getFamilies(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getFamilies(deviceId) + mFamilyListLiveData.value = result + } + } + + fun removeFamily(activity: BaseActivity<*>, familyId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.removeFamily(familyId) + mFamilyRemoveLiveData.value = result + } + } + + /** + * 获取设备邀请信息 + */ + fun getDeviceInviteInfo(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val email = MMKVUtil.getString(MMKVKey.Email) + val result = NetworkApi.getDeviceInviteInfo(email) + mInviteInfoLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/FeedbackViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/FeedbackViewModel.kt new file mode 100644 index 0000000..650b0af --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/FeedbackViewModel.kt @@ -0,0 +1,35 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/24/024. + * @link + * @description: + */ +class FeedbackViewModel : ViewModel() { + + val mFeedbackResultLiveData = MutableLiveData>() + + /** + *用户意见反馈 + * type=反馈问题类型 type 1为APP 2为设备 3其它 + */ + fun feedback(activity: BaseActivity<*>, + type: Int, + userId: String, + content: String, + imgurls: String, + email: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.feedback(type, userId, content, imgurls, email) + mFeedbackResultLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/FencesManageViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/FencesManageViewModel.kt new file mode 100644 index 0000000..905d6e1 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/FencesManageViewModel.kt @@ -0,0 +1,150 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseDialog +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.bean.GetFencesBean +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.util.ViewUtil +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/1/001. + * @link + * @description: + */ +class FencesManageViewModel : ViewModel() { + + val mFencesAddLiveData = MutableLiveData>() + val mDeleteFenceLiveData = MutableLiveData>() + val mUpdateFenceLiveData = MutableLiveData>() + val mFencesLiveData = MutableLiveData>() + + /** + *获取围栏列表 + */ + fun getFenceList(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getFenceList(deviceId) + mFencesLiveData.value = result + } + } + + fun addCircleFence(activity: BaseActivity<*>, fencesBean: FencesBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.addCircleFence(fencesBean) + mFencesAddLiveData.value = result + } + } + + fun addRectFence(activity: BaseActivity<*>, fencesBean: FencesBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.addRectFence(fencesBean) + mFencesAddLiveData.value = result + } + } + + fun addPolygonFence(activity: BaseActivity<*>, fencesBean: FencesBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.addPolygonFence(fencesBean) + mFencesAddLiveData.value = result + } + } + + fun updateCircleFence( + activity: BaseActivity<*>, fencesBean: FencesBean + ) { + activity.showLoading(true) + fencesBean.apply { + viewModelScope.launch { + val result = NetworkApi.updateCircleFence( + deviceId, + deviceServiceId, + name, + address, + imgurl, + isOn, + radius, + longitudeCenter, + latitudeCenter, + fenceNameType, + fenceType, + fenceId, + ancillaryLatitude, + ancillaryLongitude, + ancillaryOtherLatitude, + ancillaryOtherLongitude + ) + mUpdateFenceLiveData.value = result + } + } + } + + fun updateRectFence( + activity: BaseActivity<*>, fencesBean: FencesBean + ) { + activity.showLoading(true) + fencesBean.apply { + viewModelScope.launch { + val result = NetworkApi.updateRectFence( + deviceId, + deviceServiceId, + name, + address, + imgurl, + isOn, + fenceNameType, + fenceType, + fenceId, + longitudeA, + latitudeA, + longitudeB, + latitudeB, + longitudeC, + latitudeC, + longitudeD, + latitudeD, + longitudeCenter, + latitudeCenter, + longDistance, + shortDistance + ) + mUpdateFenceLiveData.value = result + } + } + } + + /** + * 更新多边形围栏 + */ + fun updatePolygonFence(activity: BaseActivity<*>, fencesBean: FencesBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.updatePolygonFence(fencesBean) + mUpdateFenceLiveData.value = result + } + } + + fun deleteFence(activity: BaseActivity<*>, fenceId: String, deviceServiceId: String) { + ViewUtil.instance.showDialog( + activity, R.string.txt_delete_tips, object : BaseDialog.OnDialogOkListener { + override fun onOkClick(dialog: BaseDialog<*>) { + dialog.dismiss() + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.removeFence(fenceId, deviceServiceId) + mDeleteFenceLiveData.value = result + } + } + }, okTextResId = R.string.txt_sure + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/FencesMapViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/FencesMapViewModel.kt new file mode 100644 index 0000000..934b0ca --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/FencesMapViewModel.kt @@ -0,0 +1,279 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModel +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.FencesBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.ui.fragment.map.baidumap.BaseBaiduMapFragment +import com.abbidot.tracker.ui.fragment.map.googlemap.BaseGoogleMapFragment +import com.google.android.gms.maps.model.LatLng + +/** + *Created by .yzq on 2022/3/2/002. + * @link + * @description: + */ +class FencesMapViewModel : ViewModel() { + + /** + * 获取围栏间最大距离 + */ +// fun getFencesDistance(fencesBean: FencesBean): Double { +// var distance = 0.0 +// fencesBean.apply { +// when (fenceShapeType) { +// ConstantInt.CircleShapeType -> distance = radius +// ConstantInt.RectangleShapeType -> distance = minOf(shortDistance, longDistance) +// ConstantInt.PolygonShapeType -> { +// val distanceA = Util.measureLatLonDistance( +// latitudeCenter, longitudeCenter, latitudeA, longitudeA +// ) +// val distanceB = Util.measureLatLonDistance( +// latitudeCenter, longitudeCenter, latitudeB, longitudeB +// ) +// val distanceC = Util.measureLatLonDistance( +// latitudeCenter, longitudeCenter, latitudeC, longitudeC +// ) +// val distanceD = Util.measureLatLonDistance( +// latitudeCenter, longitudeCenter, latitudeD, longitudeD +// ) +// val distanceE = Util.measureLatLonDistance( +// latitudeCenter, longitudeCenter, latitudeE, longitudeE +// ) +// val distanceF = Util.measureLatLonDistance( +// latitudeCenter, longitudeCenter, latitudeF, longitudeF +// ) +// distance = +// minOf(distanceA, distanceB, distanceC, distanceD, distanceE, distanceF) +// } +// } +// } +// +// if (distance == 0.0) distance = 150.0 +// return distance +// } + + /** + * 设置地图围栏数据 + */ + fun setFencesData( + context: Context, fencesList: MutableList?, mapFragment: Fragment + ) { + if (mapFragment is BaseGoogleMapFragment) { + mapFragment.clearFenceList() + } else if (mapFragment is BaseBaiduMapFragment) { + mapFragment.clearFenceList() + } + + if (null == fencesList) return + if (fencesList.size == 0) return + + val saveFillColor = ContextCompat.getColor(context, R.color.select_color4) + val saveStrokeColor = ContextCompat.getColor(context, R.color.select_color2) + val noGoFillColor = ContextCompat.getColor(context, R.color.red_color3) + val noGoStrokeColor = ContextCompat.getColor(context, R.color.red_color2) + + fencesList.forEach { + if (it.isOn == ConstantInt.Open) { + if (mapFragment is BaseGoogleMapFragment) { + if (it.fenceShapeType == ConstantInt.CircleShapeType) { + if (it.fenceType == ConstantInt.SafeZone) mapFragment.addCircleFence( + LatLng(it.latitudeCenter, it.longitudeCenter), + it.radius.toDouble(), + saveStrokeColor, + saveFillColor + ) + else mapFragment.addCircleFence( + LatLng(it.latitudeCenter, it.longitudeCenter), + it.radius.toDouble(), + noGoStrokeColor, + noGoFillColor + ) + } else if (it.fenceShapeType == ConstantInt.RectangleShapeType) { + if (it.fenceType == ConstantInt.SafeZone) mapFragment.addRectFence( + it, saveStrokeColor, saveFillColor + ) + else mapFragment.addRectFence( + it, noGoStrokeColor, noGoFillColor + ) + } else if (it.fenceShapeType == ConstantInt.PolygonShapeType) { + if (it.fenceType == ConstantInt.SafeZone) mapFragment.addPolygonFence( + it, saveStrokeColor, saveFillColor + ) + else mapFragment.addPolygonFence( + it, noGoStrokeColor, noGoFillColor + ) + } + } else if (mapFragment is BaseBaiduMapFragment) { + if (it.fenceShapeType == ConstantInt.CircleShapeType) { + if (it.fenceType == ConstantInt.SafeZone) mapFragment.addCircleFence( + it.latitudeCenter, + it.longitudeCenter, + it.radius.toDouble(), + saveStrokeColor, + saveFillColor + ) + else mapFragment.addCircleFence( + it.latitudeCenter, + it.longitudeCenter, + it.radius.toDouble(), + noGoStrokeColor, + noGoFillColor + ) + } else if (it.fenceShapeType == ConstantInt.RectangleShapeType) { + if (it.fenceType == ConstantInt.SafeZone) mapFragment.addRectFence( + it, saveStrokeColor, saveFillColor + ) + else mapFragment.addRectFence( + it, noGoStrokeColor, noGoFillColor + ) + } else if (it.fenceShapeType == ConstantInt.PolygonShapeType) { + if (it.fenceType == ConstantInt.SafeZone) mapFragment.addPolygonFence( + it, saveStrokeColor, saveFillColor + ) + else mapFragment.addPolygonFence( + it, noGoStrokeColor, noGoFillColor + ) + } + } + } + } + } + + /** + * 设置地图虚实线围栏和图片类型 + */ + fun setImageFencesData( + context: Context, + fencesList: MutableList?, + mapFragment: Fragment, + currentFencesId: String = "", + isSatelliteMap: Boolean = false + ) { + if (mapFragment is BaseGoogleMapFragment) { + mapFragment.clearAllMarker() + } else if (mapFragment is BaseBaiduMapFragment) { + mapFragment.clearAllMarker() + } + + if (null == fencesList) return + if (fencesList.size == 0) return + + val saveFillColor = ContextCompat.getColor(context, R.color.select_color4) + var saveStrokeColor = ContextCompat.getColor(context, R.color.select_color) + val noGoFillColor = ContextCompat.getColor(context, R.color.red_color3) + val noGoStrokeColor = ContextCompat.getColor(context, R.color.red_color2) + val closeFillColor = ContextCompat.getColor(context, R.color.black5) + val closeStrokeColor = ContextCompat.getColor(context, R.color.black20) + + if (isSatelliteMap) saveStrokeColor = ContextCompat.getColor(context, R.color.select_color2) + + fencesList.forEach { + //是否画虚线 + var isDashed = false + val imageTypeResId = if (it.fenceId == currentFencesId) { + isDashed = true + if (it.fenceType == ConstantInt.NoGoZone) R.drawable.icon_fences_zone_gps_red_svg + else if (isSatelliteMap) R.drawable.icon_fences_zone_gps_green_svg + else R.drawable.icon_fences_zone_gps_svg + } else getFencesImageTypeResId( + it.fenceNameType + ) + val strokeColor: Int + val fillColor: Int + if (it.isOn == ConstantInt.Close) { + strokeColor = closeStrokeColor + fillColor = closeFillColor + } else if (it.fenceType == ConstantInt.SafeZone) { + strokeColor = saveStrokeColor + fillColor = saveFillColor + } else { + strokeColor = noGoStrokeColor + fillColor = noGoFillColor + } + + if (mapFragment is BaseGoogleMapFragment) { + if (it.fenceShapeType == ConstantInt.CircleShapeType) { + val centerLatLng = LatLng(it.latitudeCenter, it.longitudeCenter) + mapFragment.addCircleImageFence( + centerLatLng, + it.radius.toDouble(), + imageTypeResId, + strokeColor, + fillColor, + isDashed + ) + } else if (it.fenceShapeType == ConstantInt.RectangleShapeType) { + if (it.fenceId == currentFencesId) { + mapFragment.addRectImageEditFence( + it, imageTypeResId, strokeColor, fillColor + ) + } else { + mapFragment.addRectImageFence( + it, imageTypeResId, strokeColor, fillColor, false + ) + } + } else if (it.fenceShapeType == ConstantInt.PolygonShapeType) { + if (it.fenceId == currentFencesId) { + mapFragment.addPolygonImageEditFence( + it, imageTypeResId, strokeColor, fillColor + ) + } else { + mapFragment.addPolygonImageFence( + it, imageTypeResId, strokeColor, fillColor, false + ) + } + } + } else if (mapFragment is BaseBaiduMapFragment) { + if (it.fenceShapeType == ConstantInt.CircleShapeType) { + mapFragment.addCircleDashedAndImageFence( + it.latitudeCenter, + it.longitudeCenter, + it.radius.toDouble(), + imageTypeResId, + strokeColor, + fillColor + ) + } else if (it.fenceShapeType == ConstantInt.RectangleShapeType) { + if (it.fenceId == currentFencesId) { + mapFragment.addRectDashedAndImageEditFence( + it, imageTypeResId, strokeColor, fillColor + ) + } else { + mapFragment.addRectDashedAndImageFence( + it, imageTypeResId, strokeColor, fillColor + ) + } + } else if (it.fenceShapeType == ConstantInt.PolygonShapeType) { + if (it.fenceId == currentFencesId) { + mapFragment.addPolygonDashedAndImageEditFence( + it, imageTypeResId, strokeColor, fillColor + ) + } else { + mapFragment.addPolygonDashedAndImageFence( + it, imageTypeResId, strokeColor, fillColor + ) + } + } + } + } + } + + private fun getFencesImageTypeResId(type: Int): Int { + var imageTypeResId = 0 + when (type) { + 1 -> imageTypeResId = R.drawable.icon_save_zone_type1_small_svg + 2 -> imageTypeResId = R.drawable.icon_save_zone_type2_small_svg + 3 -> imageTypeResId = R.drawable.icon_save_zone_type3_small_svg + 4 -> imageTypeResId = R.drawable.icon_save_zone_type4_small_svg + 5 -> imageTypeResId = R.drawable.icon_save_zone_type5_small_svg + 6 -> imageTypeResId = R.drawable.icon_no_zone_type1_small_svg + 7 -> imageTypeResId = R.drawable.icon_no_zone_type2_small_svg + 8 -> imageTypeResId = R.drawable.icon_no_zone_type3_small_svg + } + return imageTypeResId + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/FindBleDeviceViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/FindBleDeviceViewModel.kt new file mode 100644 index 0000000..90337be --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/FindBleDeviceViewModel.kt @@ -0,0 +1,292 @@ +package com.abbidot.tracker.vm + +import android.app.Service +import android.media.AudioAttributes +import android.media.AudioManager +import android.media.SoundPool +import android.os.CountDownTimer +import android.text.TextUtils +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import com.abbidot.tracker.widget.MyBluetoothAnimView +import com.abbidot.tracker.widget.MySignalView +import com.abbidot.tracker.widget.SignalRadarView +import com.clj.fastble.BleManager +import com.clj.fastble.callback.BleRssiCallback +import com.clj.fastble.callback.BleScanCallback +import com.clj.fastble.data.BleDevice +import com.clj.fastble.data.BleScanState +import com.clj.fastble.exception.BleException +import com.clj.fastble.scan.BleScanRuleConfig +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + + +/** + *Created by .yzq on 2023/9/20/020. + * @link + * @description: + */ +class FindBleDeviceViewModel : ViewModel() { + + //音量管理器 + private lateinit var mAudioManager: AudioManager + + //记录手机原来的音量,便于还原 + private var mPhoneOriginalVolume = 0 + var mMaxVolume = 0 + + //设置播放的当前时间 + private var mSetPlayTimeMillis = 0L + + private var mSoundPool: SoundPool? = null + private var mSoundID = 0 + private var mStreamID = 0 + + //播放的速度 + private var mSetPlaySpeed = 0.5f + + private var isStartFindBleDevice = false + + private var mCountDownTimer: CountDownTimer? = null + + fun startScanFind( + activity: BaseActivity<*>, + mac: String, + signalView: MySignalView, + radoView: SignalRadarView, + bluetoothView: MyBluetoothAnimView, + showLoading: Boolean = false + ) { + if (TextUtils.isEmpty(mac)) { + return + } + if (!SRBleUtil.instance.isBlueEnable(activity)) { + activity.showToast(R.string.txt_ble_enable) + return + } + SRBleUtil.instance.cancelBleScan("FindBleDeviceViewModel--->已经在扫描蓝牙,取消上次扫描") + if (showLoading) activity.showLoading(true) + + //扫描超时时间,可选,默认10秒,若小于0,则时间无限,一直扫猫,但可以用BLeManger.getInstance().cancelScan()来中止扫描 + val scanTimeOut = 0L + //扫描到设备的时间,多久没扫描到设备 + var currentScanTimeMillis = 0L + var speed = 0.5f + + viewModelScope.launch(Dispatchers.IO) { + + isStartFindBleDevice = true + mAudioManager = activity.getSystemService(Service.AUDIO_SERVICE) as AudioManager + mPhoneOriginalVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_MUSIC) + mMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC) + setVolume(0) + + createSoundPoolIfNeeded(activity) + + if (BleManager.getInstance().isConnected(mac)) { + LogUtil.e("是已连接的设备,直接获取信号") + activity.runOnUiThread { + countDownReadConnectRssi { + SRBleUtil.instance.readConnectRssi(mac, object : BleRssiCallback() { + override fun onRssiFailure(exception: BleException?) { + LogUtil.e("FindBleDeviceViewModel--->获取连接设备信号失败...") + } + + override fun onRssiSuccess(rssi: Int) { + LogUtil.e("FindBleDeviceViewModel--->获取连接设备信号onRssiSuccess$rssi...") + speed = getPlaySpeed(rssi) + val value = getSignalValue(rssi) + signalView.setValue(value) + bluetoothView.setValue(value) + radoView.setValue(value) + playAudio(speed) + } + + }) + } + } + return@launch + } + //只扫描指定的mac设备 + val scanRuleConfig = + BleScanRuleConfig.Builder().setDeviceMac(mac).setScanTimeOut(scanTimeOut).build() + BleManager.getInstance().initScanRule(scanRuleConfig) + BleManager.getInstance().scan(object : BleScanCallback() { + override fun onScanStarted(success: Boolean) { + LogUtil.e("FindBleDeviceViewModel--->开始扫描${mac}设备...") + } + + override fun onScanning(bleDevice: BleDevice?) { + bleDevice?.let { + LogUtil.e("FindBleDeviceViewModel--->onScanning...${it.mac},${it.rssi}") + } + } + + override fun onLeScan(bleDevice: BleDevice?) { + bleDevice?.let { + if (it.mac == mac) { + currentScanTimeMillis = System.currentTimeMillis() + LogUtil.e("FindBleDeviceViewModel--->onLeScan...${it.mac},${it.rssi}") + speed = getPlaySpeed(it.rssi) + val value = getSignalValue(it.rssi) + signalView.setValue(value) + bluetoothView.setValue(value) + radoView.setValue(value) + } + //10s没扫描到设备 + if (System.currentTimeMillis() - currentScanTimeMillis > 10 * 1000) { + LogUtil.e("FindBleDeviceViewModel--->10s没扫描到设备") + speed = getPlaySpeed(-200) + val value = getSignalValue(-200) + signalView.setValue(value) + bluetoothView.setValue(value) + radoView.setValue(value) + currentScanTimeMillis = System.currentTimeMillis() + } + playAudio(speed) + } + } + + override fun onScanFinished(scanResultList: MutableList?) { + } + }) + } + } + + /** + * 每隔2s执行一次请求 + */ + private fun countDownReadConnectRssi(readConnectRssi: () -> Unit) { + mCountDownTimer = object : CountDownTimer(2 * 1000, 1000) { + override fun onTick(time: Long) { + } + + override fun onFinish() { + readConnectRssi() + countDownReadConnectRssi(readConnectRssi) + } + }.start() + } + + fun setVolume(volume: Int) { + //显示设置音量大小的操作条 flag=AudioManager.FLAG_SHOW_UI显示设置音量的ui + mAudioManager.setStreamVolume(AudioManager.STREAM_MUSIC, volume, 0) + } + + /** + * 创建SoundPool ,注意 api 等级 + */ + private fun createSoundPoolIfNeeded(activity: BaseActivity<*>) { + if (mSoundPool == null) { + val audioAttributes = AudioAttributes.Builder().setUsage(AudioAttributes.USAGE_MEDIA) + .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC).build(); + + mSoundPool = + SoundPool.Builder().setMaxStreams(5).setAudioAttributes(audioAttributes).build() + + // 设置加载完成监听 + mSoundPool?.setOnLoadCompleteListener { _, _, status -> + LogUtil.e("音频文件加载完成,status=$status") + } + val fd = activity.assets.openFd("find_device.mp3") + mSoundID = mSoundPool?.load(fd, 1)!! + } + } + + /** + * 播放声音 + */ + private fun playAudio(speed: Float) { + val currentTimeMillis = System.currentTimeMillis() + //1秒更新下播放速度 + if (currentTimeMillis - mSetPlayTimeMillis > 1000) { + mSoundPool?.apply { + if (mStreamID == 0) { + mSetPlaySpeed = speed + mStreamID = play( + mSoundID, mMaxVolume.toFloat(), mMaxVolume.toFloat(), 1, -1, speed + ) + } else { + if (mSetPlaySpeed != speed) { + setRate(mStreamID, speed) + mSetPlaySpeed = speed + } + } + } + mSetPlayTimeMillis = currentTimeMillis + } + } + + /** + * 获取要设置播放速度大小 + * 信号强 0dBm~-50dBm 信号中 -50dBm~-70dBm 信号弱 -70dBm~dBm + *低功耗蓝牙分四级 + * 4: 0~-60 + * 3: -60~-70 + * 2: -70~-80 + * 1: -80~ + */ + private fun getPlaySpeed(rssi: Int): Float { + return when (getSignalValue(rssi)) { + 4 -> { + 2.0f + } + + 3 -> { + 1.5f + } + + 2 -> { + 1.0f + } + + 1 -> { + 0.75f + } + + else -> { + 0.5f + } + } + } + + private fun getSignalValue(rssi: Int): Int { + return if (rssi >= -50) { + 4 + } else if (rssi >= -70) { + 3 + } else if (rssi >= -90) { + 2 + } else if (rssi >= -100) { + 1 + } else { + 0 + } + } + + /** + * 停止查找设备 + */ + fun stopFindDevice() { + if (!isStartFindBleDevice) return + SRBleUtil.instance.cancelBleScan("停止查找设备取消扫描蓝牙") + mSoundPool?.apply { + if (mStreamID > 0) stop(mStreamID) + release() + } + //还原手机原来的音量 + setVolume(mPhoneOriginalVolume) + isStartFindBleDevice = false + mCountDownTimer?.cancel() + } + + override fun onCleared() { + super.onCleared() + stopFindDevice() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/GeoCoderViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/GeoCoderViewModel.kt new file mode 100644 index 0000000..fa2b42a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/GeoCoderViewModel.kt @@ -0,0 +1,192 @@ +package com.abbidot.tracker.vm + +import android.text.TextUtils +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.util.LonAndLatUtil +import com.baidu.mapapi.model.LatLng +import com.baidu.mapapi.search.geocode.GeoCodeResult +import com.baidu.mapapi.search.geocode.GeoCoder +import com.baidu.mapapi.search.geocode.OnGetGeoCoderResultListener +import com.baidu.mapapi.search.geocode.ReverseGeoCodeOption +import com.baidu.mapapi.search.geocode.ReverseGeoCodeResult +import com.mapbox.geojson.Point +import com.mapbox.search.ResponseInfo +import com.mapbox.search.ReverseGeoOptions +import com.mapbox.search.SearchCallback +import com.mapbox.search.SearchEngine +import com.mapbox.search.SearchEngineSettings +import com.mapbox.search.common.AsyncOperationTask +import com.mapbox.search.result.SearchResult + + +/** + *Created by .yzq on 2022/6/27/027. + * @link + * @description: 地理编码(mapbox地图 和 百度地图) + */ +class GeoCoderViewModel : ViewModel() { + + var mLat = 0.0 + var mLon = 0.0 + var mAddress = "" + val mLatLonAddressLiveData = MutableLiveData() + + //Mapbox反向地理编码 + private var mMapboxSearchRequestTask: AsyncOperationTask? = null + private var mSearchEngine: SearchEngine? = null + + //百度地图反向地理编码 + private var mBaiduMapGeoCoder: GeoCoder? = null + + //是否显示详细地址信息 + private var isShowDetailAddress = true + + /** + * 反地理编码 + * 使用原生gps(WGS84)坐标 + * @param showDetailAddress 是否显示最详细的地址 + */ + fun getLatLonAddress(latitude: Double, longitude: Double, showDetailAddress: Boolean = true) { + + isShowDetailAddress = showDetailAddress + + //相同经纬度不需要反地理编码 + if (mLon == longitude && mLat == latitude) { + mLatLonAddressLiveData.value = mAddress + return + } + mLon = longitude + mLat = latitude + + if (AppUtils.isChina(AppUtils.SWITCH_MAP_TYPE)) { + baiduMapReverseGeocoder(latitude, longitude) + } else { + mapBoxReverseGeocoder(latitude, longitude) + } + } + + /** + * Mapbox反向地理编码 + */ + private fun mapBoxReverseGeocoder(latitude: Double, longitude: Double) { + if (null == mSearchEngine) { + mSearchEngine = + SearchEngine.createSearchEngineWithBuiltInDataProviders(SearchEngineSettings()) + } + mMapboxSearchRequestTask?.cancel() + mMapboxSearchRequestTask = mSearchEngine?.search(ReverseGeoOptions( + center = Point.fromLngLat(longitude, latitude) + ), object : SearchCallback { + override fun onError(e: Exception) { + LogUtil.e(" Mapbox反地理编码error,${e.message}") +// mLatLonAddressLiveData.value = "" + baiduMapReverseGeocoder(latitude, longitude) + } + + override fun onResults( + results: List, responseInfo: ResponseInfo + ) { + LogUtil.e(results.toString()) + if (results.isNotEmpty()) { + val searchResult = results[0] + var address = "" + searchResult.address?.let { + if (!TextUtils.isEmpty(it.street)) { + address += "${it.street} " + } +// if (!TextUtils.isEmpty(it.houseNumber)) { +// address += "${it.houseNumber} " +// } + if (!TextUtils.isEmpty(it.locality)) { + address += "${it.locality} " + } + if (!TextUtils.isEmpty(it.place)) { + address += "${it.place} " + } + if (isShowDetailAddress && !TextUtils.isEmpty(it.region)) { + address += "${it.region} " + } + if (isShowDetailAddress && !TextUtils.isEmpty(it.country)) { + address += "${it.country}" + } + } +// address = address?.replace("null", "") + mAddress = address + LogUtil.e("Mapbox反地理编码:$mAddress") + mLatLonAddressLiveData.value = mAddress + } else { +// mLatLonAddressLiveData.value = ""- + baiduMapReverseGeocoder(latitude, longitude) + } + } + }) + } + + /** + * 百度地图反向地理编码 + */ + private fun baiduMapReverseGeocoder(latitude: Double, longitude: Double) { + if (null == mBaiduMapGeoCoder) { + mBaiduMapGeoCoder = GeoCoder.newInstance() + } + mBaiduMapGeoCoder?.destroy() + mBaiduMapGeoCoder!!.setOnGetGeoCodeResultListener(object : OnGetGeoCoderResultListener { + override fun onGetGeoCodeResult(p0: GeoCodeResult?) { + } + + override fun onGetReverseGeoCodeResult(result: ReverseGeoCodeResult?) { + if (null == result || result.error != com.baidu.mapapi.search.core.SearchResult.ERRORNO.NO_ERROR) { + //没有找到检索结果 + return + } else { + var address = "" + //行政区号 +// val adCode = result.cityCode + result.addressDetail.apply { + if (isShowDetailAddress && !TextUtils.isEmpty(province)) { + address += province + } + if (!TextUtils.isEmpty(city)) { + address += city + } + if (!TextUtils.isEmpty(district)) { + address += district + } + if (!TextUtils.isEmpty(town)) { + address += town + } + if (!TextUtils.isEmpty(street)) { + address += street + } + if (!TextUtils.isEmpty(streetNumber)) { + address += streetNumber + } + LogUtil.e("百度地图反向地理编码详细地址,${streetNumber},${town},${street},$result") + } + //详细地址 +// val address = result.address + mAddress = address + mLatLonAddressLiveData.value = mAddress + } + } + }) + val convertLonLat = LonAndLatUtil.convertFromWGS84ToGCJ02(latitude, longitude) + mBaiduMapGeoCoder!!.reverseGeoCode( + ReverseGeoCodeOption().location(LatLng(convertLonLat[0], convertLonLat[1])) + // 设置是否返回新数据 默认值0不返回,1返回 + .newVersion(1) + // POI召回半径,允许设置区间为0-1000米,超过1000米按1000米召回。默认值为1000 + .radius(500) + ) + } + + override fun onCleared() { + super.onCleared() + //取消反地理编码搜索 + mBaiduMapGeoCoder?.destroy() + mMapboxSearchRequestTask?.cancel() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/LogBleReportViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/LogBleReportViewModel.kt new file mode 100644 index 0000000..418f73a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/LogBleReportViewModel.kt @@ -0,0 +1,110 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.bean.ReceiveDeviceData +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.util.FileUtil +import com.abbidot.tracker.util.Util +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock +import java.io.File +import java.io.FileOutputStream +import java.io.IOException + + +/** + *Created by .yzq on 2022/3/11/011. + * @link + * @description:设备日志上报 + */ +class LogBleReportViewModel : ViewModel() { + + val mUploadLogLiveData = MutableLiveData>() + val mUploadDeviceLogLiveData = MutableLiveData>() + + //线程同步锁 No upload + private val mMutex = Mutex() + + + /** + * 处理日志上报的数据 + */ + fun dealLogReportData(context: Context, receiveData: ReceiveDeviceData) { + viewModelScope.launch(Dispatchers.IO) { + mMutex.withLock { + val fileName = "${receiveData.mac}.txt" + val file = File( + FileUtil.getDiskCacheDirectory(context, FileUtil.LOG_DIRECTORY_NAME), fileName + ) + try { + // 追加创建文件输出流 + val fos = FileOutputStream(file, true) + // 将数据写入文件 + fos.write(receiveData.data) + // 关闭文件输出流 + fos.close() + } catch (e: IOException) { + e.printStackTrace() + } + } + } + } + + /** + * 设备日志上传 + */ + fun uploadLog(context: Context, mac: String) { + //是否有网络功能 + if (QMUIDisplayHelper.hasInternet(context)) { + viewModelScope.launch { + mMutex.withLock { + try { + val fileName = "$mac.txt" + val file = File( + FileUtil.getDiskCacheDirectory(context, FileUtil.LOG_DIRECTORY_NAME), + fileName + ) + if (file.exists()) { + val newFileName = "${mac}_${System.currentTimeMillis()}.txt" + val newFile = File( + FileUtil.getDiskCacheDirectory( + context, FileUtil.LOG_DIRECTORY_NAME + ), newFileName + ) + //文件重命名 + file.renameTo(newFile) + val result = + NetworkApi.uploadLog(Util.uploadFile(newFile.path, "file/txt")) + mUploadLogLiveData.value = result + } else { + LogUtil.e("${fileName}日志文件不存在") + } + } catch (e: Exception) { + LogUtil.e("日志文件上传错误:${e.message}") + } + } + } + } + } + + /** + * 上传设备日志 + */ + fun uploadDeviceLog( + deviceOutId: String, uploadTime: String, macID: String, logFileUrl: String + ) { + viewModelScope.launch { + mMutex.withLock { + val result = NetworkApi.uploadDeviceLog(deviceOutId, uploadTime, macID, logFileUrl) + mUploadDeviceLogLiveData.value = result + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/LoginViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/LoginViewModel.kt new file mode 100644 index 0000000..05f0e09 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/LoginViewModel.kt @@ -0,0 +1,27 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/2/15/015. + * @link + * @description: + */ +class LoginViewModel:ViewModel() { + + val mLoginLiveData=MutableLiveData>() + + fun login(activity: BaseActivity<*>, email: String, password: String){ + activity.showLoading(true) + viewModelScope.launch { + val result=NetworkApi.login(email, password) + mLoginLiveData.value=result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/MapLiveViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/MapLiveViewModel.kt new file mode 100644 index 0000000..37a212a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/MapLiveViewModel.kt @@ -0,0 +1,74 @@ +package com.abbidot.tracker.vm + +import android.os.CountDownTimer +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.LiveDataBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +class MapLiveViewModel : ViewModel() { + private var mCountDownTimer: CountDownTimer? = null + val mMapLiveLiveData = MutableLiveData>() + val mMapLiveStatusLiveData = MutableLiveData>() + + //获取直播经纬度的时间戳,只有获取到数据,时间戳才会变化 + var mGetPetLivePointTimeStamp = System.currentTimeMillis() / 1000 + + //刷新数据时间秒 + val mRefreshTime = 10 + + /** + * 打开关闭直播 + */ + fun setupDeviceLiveStatus(activity: BaseActivity<*>, deviceId: String, liveState: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.setupDeviceLiveStatus(deviceId, liveState) + mMapLiveStatusLiveData.value = result + } + } + + /** + * 直播宠物轨迹 + */ + fun getPetLivePoint(deviceId: String) { + viewModelScope.launch { + val result = NetworkApi.getPetLivePoint(deviceId, mGetPetLivePointTimeStamp) + mMapLiveLiveData.value = result + } + countDownGetData(deviceId) + } + + /** + * 每隔10s执行一次请求 + */ + private fun countDownGetData(deviceId: String) { + stopGetData() + mCountDownTimer = object : CountDownTimer(mRefreshTime * 1000L, 1000) { + override fun onTick(time: Long) { + } + + override fun onFinish() { + getPetLivePoint(deviceId) + } + }.start() + } + + /** + * 停止获取数据 + */ + fun stopGetData() { + if (null != mCountDownTimer) { + mCountDownTimer!!.cancel() + mCountDownTimer = null + } + } + + override fun onCleared() { + super.onCleared() + stopGetData() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/MapViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/MapViewModel.kt new file mode 100644 index 0000000..2112f6e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/MapViewModel.kt @@ -0,0 +1,378 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import android.os.CountDownTimer +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.recyclerview.widget.RecyclerView +import com.abbidot.baselibrary.list.BaseRecyclerAdapter +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.util.Util +import com.abbidot.tracker.util.bluetooth.SRBleUtil +import kotlinx.coroutines.launch + +class MapViewModel : ViewModel() { + + val mMapDeviceLiveData = MutableLiveData>() + private var mCountDownTimer: CountDownTimer? = null + + //几分钟倒计时(min) + private var mRefreshDataMin = 0.5f + private var mDeviceId = "" + + //警告动画是否结束 + private var isWarningAnimEnd = true + + fun getMapDeviceStatus(deviceId: String, isNeedCountDown: Boolean = true) { + viewModelScope.launch { + stopGetData() + val result = NetworkApi.getMapDeviceStatus(deviceId) + mMapDeviceLiveData.value = result + } + mDeviceId = deviceId + if (isNeedCountDown) countDownGetData(deviceId) + } + + /** + * 更新刷新时间 + */ + fun updateMillisInFuture(refreshMin: Float) { + if (refreshMin <= 0 || mRefreshDataMin == refreshMin) return + mRefreshDataMin = refreshMin + countDownGetData(mDeviceId) + } + + /** + * 每隔mMillisInFuture分钟执行一次请求 + */ + private fun countDownGetData(deviceId: String) { + stopGetData() + mCountDownTimer = object : CountDownTimer((mRefreshDataMin * 60 * 1000L).toLong(), 1000) { + override fun onTick(time: Long) { + } + + override fun onFinish() { + getMapDeviceStatus(deviceId) + } + }.start() + } + + /** + * 设置设备默认状态 + */ + fun addDeviceDefaultStateData(context: Context, deviceStateList: MutableList) { + DataBean().apply { + imageId = R.drawable.icon_map_signal + menuType = ConstantInt.Close + name = "LTE" + value = + context.getString(R.string.tracker_manage_set_network) + ":" + context.getString(R.string.txt_offline) + deviceStateList.add(this) + } + DataBean().apply { + imageId = R.drawable.icon_map_gps + menuType = ConstantInt.Close + name = context.getString(R.string.tracker_manage_set_gps) + value = + context.getString(R.string.tracker_manage_set_gps) + ":" + context.getString(R.string.txt_no_signal) + deviceStateList.add(this) + } + DataBean().apply { + imageId = R.drawable.icon_map_zone + menuType = ConstantInt.Close + name = context.getString(R.string.txt_wifi) + value = + context.getString(R.string.txt_smart_power_zone) + ":" + context.getString(R.string.txt_outside) + deviceStateList.add(this) + } + DataBean().apply { + imageId = 0 + state = -1 + menuType = ConstantInt.Close + value = String.format(context.getString(R.string.tracker_manage_set_battery), "0") + name = "N/A" + deviceStateList.add(this) + } + } + + /** + * 设置设备状态和告警信息显示 + */ + fun setDeviceStateAndWarningData( + context: Context, + petBean: PetBean?, + mapDeviceBean: MapDeviceBean, + deviceStateList: MutableList, + deviceStateAdapter: BaseRecyclerAdapter, + deviceMsgList: MutableList, + deviceMsgAdapter: BaseRecyclerAdapter, + rvMsg: RecyclerView, + isLive: Boolean = false + ) { + mapDeviceBean.apply { + deviceMsgList.clear() + //设备关机 + if (powerSwitch == ConstantInt.Close) { +// setNoState(context, deviceStateList) + //警告状态 + DataBean().apply { + val timeString = + Utils.formatTime(updateTime * 1000, Utils.DATE_FORMAT_PATTERN_EN6) + value = context.getString(R.string.txt_tracker_power_off) + " $timeString" + deviceMsgList.add(this) + } + } + //设备充电 + else if (powerSwitch == ConstantInt.Type3) { +// setDeviceState(context, mapDeviceBean, deviceStateList) + + //警告状态 + DataBean().apply { + value = context.getString(R.string.txt_on_charging) + deviceMsgList.add(this) + } + } + //设备休眠 + else if (powerSwitch == ConstantInt.Type2) { +// setDeviceState(context, mapDeviceBean, deviceStateList) +// setNoState(context, deviceStateList) + + //警告状态 + DataBean().apply { + value = context.getString(R.string.txt_in_sleep_mode) + deviceMsgList.add(this) + } + } + //判断是否连接了蓝牙 + else if (null != petBean && null != SRBleUtil.instance.getConnectMacDevice(petBean.macID)) { + //网络无信号 +// if (lteSignal <= ConstantInt.NoSignal) { +// setNoState(context, deviceStateList) +// } else { +// setDeviceState(context, mapDeviceBean, deviceStateList) +// } + val nearby = + String.format(context.getString(R.string.txt_is_nearby), petBean.petName) + //警告状态 + DataBean().apply { + value = nearby + state = ConstantInt.Type1 + deviceMsgList.add(this) + } + } + //网络无信号/ 分钟后无上报所有绿点取消显示 + else if (lteSignal <= ConstantInt.NoSignal || Util.isTimeoutReport(updateTime)) { +// setNoState(context, deviceStateList) + + //警告状态 + DataBean().apply { + value = context.getString(R.string.map_current_no_signal) + deviceMsgList.add(this) + } + } else { + //警告状态 + setWaringData(context, mapDeviceBean, deviceMsgList, isLive) + } + setDeviceState(context, mapDeviceBean, deviceStateList) + + deviceStateAdapter.notifyItemRangeChanged(0, deviceStateList.size) + deviceMsgAdapter.setData(deviceMsgList) + deviceMsgAdapter.notifyDataSetChanged() +// if (deviceMsgList.size > 0) { +// if (isWarningAnimEnd) { +// YoYo.with(Techniques.FadeOut).onStart { +// isWarningAnimEnd = false +// }.onEnd { +// isWarningAnimEnd = true +// }.delay(29 * 1000).duration(3000).playOn(rvMsg) +// } +// } + } + } + + /** + * 设置没有的状态 + */ + private fun setNoState(context: Context, deviceStateList: MutableList) { + deviceStateList[0].apply { + menuType = ConstantInt.Close + value = + context.getString(R.string.tracker_manage_set_network) + ":" + context.getString(R.string.txt_offline) + } + deviceStateList[1].apply { + menuType = ConstantInt.Close + value = + context.getString(R.string.tracker_manage_set_gps) + ":" + context.getString(R.string.txt_no_signal) + } + deviceStateList[2].apply { + menuType = ConstantInt.Close + value = + context.getString(R.string.txt_smart_power_zone) + ":" + context.getString(R.string.txt_outside) + } + deviceStateList[3].apply { + menuType = ConstantInt.Close + state = -1 + value = String.format( + context.getString(R.string.tracker_manage_set_battery), "--" + ) + name = "N/A" + } + } + + /** + * 设置设备状态 + */ + private fun setDeviceState( + context: Context, mapDeviceBean: MapDeviceBean, deviceStateList: MutableList + ) { + mapDeviceBean.let { + //是否超时上报 + val isTimeoutReport = Util.isTimeoutReport(it.updateTime) + deviceStateList[0].apply { + menuType = ConstantInt.Open + val lteValue = + if (isTimeoutReport || it.lteSignal == ConstantInt.NoSignal || it.powerSwitch == ConstantInt.Type0 || it.powerSwitch == ConstantInt.Type2) { + menuType = ConstantInt.Close + context.getString(R.string.txt_offline) + } else { + context.getString(R.string.txt_online) + } + value = context.getString(R.string.tracker_manage_set_network) + ":$lteValue" + } + deviceStateList[1].apply { + menuType = ConstantInt.Open + val gpsValue = + if (isTimeoutReport || it.gpsSignal == ConstantInt.NoSignal || it.powerSwitch == ConstantInt.Type0 || it.powerSwitch == ConstantInt.Type2 || it.inWifiZone == ConstantInt.Type1) { + menuType = ConstantInt.Close + context.getString(R.string.txt_no_signal) + } else if (it.gpsSignal > ConstantInt.WeakSignal) { + context.getString(R.string.txt_strong_signal) + } else { + context.getString(R.string.txt_weak_signal) + } + value = context.getString(R.string.tracker_manage_set_gps) + ":$gpsValue" + } + deviceStateList[2].apply { + menuType = ConstantInt.Open + val wifiZoneValue = when (it.inWifiZone) { + ConstantInt.Open -> context.getString(R.string.txt_inside) + + ConstantInt.Type2 -> { + menuType = ConstantInt.Close + context.getString(R.string.txt_not_set) + } + + else -> { + menuType = ConstantInt.Close + context.getString(R.string.txt_outside) + } + } + value = context.getString(R.string.txt_smart_power_zone) + ":$wifiZoneValue" + } + deviceStateList[3].apply { + if (isTimeoutReport || it.lteSignal == ConstantInt.NoSignal || it.powerSwitch == ConstantInt.Type0 || it.powerSwitch == ConstantInt.Type2) { + menuType = ConstantInt.Close +// state = -1 +// value = +// String.format(context.getString(R.string.tracker_manage_set_battery), "--") +// name = "N/A" + } else { + menuType = if (it.batteryLevel > ConstantInt.LowBattery20) ConstantInt.Open + else ConstantInt.Close + } + state = Utils.calculatePowerLevel(it.batteryLevel) + value = String.format( + context.getString(R.string.tracker_manage_set_battery), "${it.batteryLevel}" + ) + name = "${it.batteryLevel}%" + } + } + } + + /** + * 设置警告状态 + */ + private fun setWaringData( + context: Context, + mapDeviceBean: MapDeviceBean, + deviceMsgList: MutableList, + isLive: Boolean + ) { + mapDeviceBean.let { + if (!isLive && it.inWifiZone == ConstantInt.Type1) { + DataBean().apply { + value = context.getString(R.string.txt_in_power_zone) + state = ConstantInt.Type1 + deviceMsgList.add(this) + } + } else if (it.batteryLevel <= ConstantInt.LowBattery10) { + DataBean().apply { + value = String.format(context.getString(R.string.txt_battery_less_than), "10") + deviceMsgList.add(this) + } + } else if (it.batteryLevel <= ConstantInt.LowBattery20) { + DataBean().apply { + value = String.format(context.getString(R.string.txt_battery_less_than), "20") + deviceMsgList.add(this) + } + } else if (it.batteryLevel <= ConstantInt.LowBattery30) { + DataBean().apply { + value = String.format(context.getString(R.string.txt_battery_less_than), "30") + deviceMsgList.add(this) + } + } else if (it.gpsSignal <= ConstantInt.WeakSignal) { + DataBean().apply { + value = context.getString(R.string.txt_weak_gps_signal) + deviceMsgList.add(this) + } + } else { + if (!isLive) { + if (it.inNoGoZone == ConstantInt.BadZone) { + DataBean().apply { + value = context.getString(R.string.txt_in_no_go_zone) + deviceMsgList.add(this) + } + } else if (it.inSafeZone == ConstantInt.BadZone) { + DataBean().apply { + value = context.getString(R.string.txt_out_save_zone) + deviceMsgList.add(this) + } + } +// else if (it.inWifiZone == ConstantInt.Type0) { +// DataBean().apply { +// value = context.getString(R.string.txt_out_power_zone) +// deviceMsgList.add(this) +// } +// } + else { + + } + } else { + + } + } + } + } + + /** + * 停止获取数据 + */ + fun stopGetData() { + if (null != mCountDownTimer) { + mCountDownTimer!!.cancel() + mCountDownTimer = null + } + } + + override fun onCleared() { + super.onCleared() + stopGetData() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/PetInfoViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/PetInfoViewModel.kt new file mode 100644 index 0000000..c255bb2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/PetInfoViewModel.kt @@ -0,0 +1,412 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import android.text.TextUtils +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DataBean +import com.abbidot.tracker.bean.LabelBean +import com.abbidot.tracker.bean.PetAllInfoBean +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2024/5/27/027. + * @link + * @description: 宠物信息相关 + */ +class PetInfoViewModel : ViewModel() { + + val mPetLabelsLiveData = MutableLiveData>>() + val mAddPetLiveData = MutableLiveData>() + val mUpdatePetInfoLiveData = MutableLiveData>() + val mPetInfoLiveData = MutableLiveData>() + val mPetAllInfoLiveData = MutableLiveData>() + + fun getPetLabels(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getPetLabels() + mPetLabelsLiveData.value = result + } + } + + fun getPetAllInfo(activity: BaseActivity<*>, petId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getPetAllInfo(petId) + mPetAllInfoLiveData.value = result + } + } + + fun getPetInfo(activity: BaseActivity<*>, petId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getPetInfo(petId) + mPetInfoLiveData.value = result + } + } + + fun addPet(activity: BaseActivity<*>, petBean: PetBean) { + activity.showLoading(true) + petBean.apply { + viewModelScope.launch { + val result = NetworkApi.addPet( + userId, + petName, + breed, + gender, + petType, + birthdayDate, + imgurl, + weight, + height, + labels + ) + mAddPetLiveData.value = result + } + } + } + + fun updatePetInfo(activity: BaseActivity<*>, petBean: PetBean) { + activity.showLoading(true) + petBean.apply { + viewModelScope.launch { + val result = NetworkApi.updatePetInfo( + userId, + petName, + breed, + gender, + petType, + birthdayDate, + petId, + imgurl, + weight, + height, + labels + ) + mUpdatePetInfoLiveData.value = result + } + } + } + + + /** + * 多个标签,隔开的字符串转数组 + */ + fun getLabelData(label: String): MutableList { + val labelList = mutableListOf() + if (!TextUtils.isEmpty(label)) { + val labels = label.split(",") + if (labels.isNotEmpty()) { + labels.forEach { str -> + val labelBean = LabelBean(str) + labelList.add(labelBean) + } + } + } + return labelList + } + + /** + * 获取宠物类型名称 + */ + fun getTypeName(context: Context, type: Int): String { + return when (type) { + ConstantInt.DogPetType -> { + context.getString(R.string.txt_dog) + } + + ConstantInt.CatPetType -> { + context.getString(R.string.txt_cat) + } + + else -> { + context.getString(R.string.txt_other) + } + } + } + + /** + * 获取宠物类型名称 + */ + fun getSexName(context: Context, sex: Int): String { + return when (sex) { + ConstantInt.Man -> { + context.getString(R.string.pet_manage_gender_male) + } + + else -> { + context.getString(R.string.pet_manage_gender_female) + } + } + } + + /** + * 获取勋章 + */ + fun getBadges( + context: Context, createDate: String, showShort: Boolean = false, shortCount: Int = 4 + ): MutableList { + LogUtil.e("勋章时间:$createDate") + val toTimestamp = Utils.stringToTimestamp(createDate) + val diffTimestamp = if (TextUtils.isEmpty(createDate)) System.currentTimeMillis() + else System.currentTimeMillis() - toTimestamp + val dayTimestamp = 24 * 60 * 60 * 1000L + val monthTimestamp = 30 * dayTimestamp + val yearTimestamp = 12 * monthTimestamp + val list = mutableListOf() + var unlockNum = 0 + + DataBean().apply { + unlockNum++ + name = context.getString(R.string.txt_fresh_paws) + imageId = R.drawable.icon_fresh_paws_unlock_svg + //解锁状态 + state = ConstantInt.Open + //勋章解锁时间 + value = Utils.stringToDate(createDate, resultFormat = Utils.DATE_FORMAT_PATTERN_EN3) + //勋章解锁说明 + unit = context.getString(R.string.txt_fresh_paws_dec) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= dayTimestamp) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 1), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_day_1_devotee_dec1) + R.drawable.icon_day_1_devotee_unlock_svg + } else { + unit = context.getString(R.string.txt_day_1_devotee_dec2) + R.drawable.icon_day_1_devotee_lock_svg + } + name = context.getString(R.string.txt_day_1_devotee) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (3 * dayTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 3), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_newbie_navigator_dec1) + R.drawable.icon_newbie_navigator_unlock_svg + } else { + unit = context.getString(R.string.txt_newbie_navigator_dec2) + R.drawable.icon_newbie_navigator_lock_svg + } + name = context.getString(R.string.txt_newbie_navigator) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (7 * dayTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 7), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_furry_friend_dec1) + R.drawable.icon_furry_friend_unlock_svg + } else { + unit = context.getString(R.string.txt_furry_friend_dec2) + R.drawable.icon_furry_friend_lock_svg + } + name = context.getString(R.string.txt_furry_friend) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (15 * dayTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 15), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_pawtential_pal_dec1) + R.drawable.icon_pawtential_pal_unlock_svg + } else { + unit = context.getString(R.string.txt_pawtential_pal_dec2) + R.drawable.icon_pawtential_pal_lock_svg + } + name = context.getString(R.string.txt_pawtential_pal) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= monthTimestamp) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_month_mate_dec1) + R.drawable.icon_month_mate_unlock_svg + } else { + unit = context.getString(R.string.txt_month_mate_dec2) + R.drawable.icon_month__mate_lock_svg + } + name = context.getString(R.string.txt_month_mate) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (3 * monthTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 3), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_tailwag_explorer_dec1) + R.drawable.icon_tailwag_explorer_unlock_svg + } else { + unit = context.getString(R.string.txt_tailwag_explorer_dec2) + R.drawable.icon_tailwag_explorer_lock_svg + } + name = context.getString(R.string.txt_tailwag_explorer) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (6 * monthTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 6), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_pawsome_partner_dec1) + R.drawable.icon_pawsome_partner_unlock_svg + } else { + unit = context.getString(R.string.txt_pawsome_partner_dec2) + R.drawable.icon_pawsome_partner_lock_svg + } + name = context.getString(R.string.txt_pawsome_partner) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= yearTimestamp) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 12), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_1_year_hugger_dec1) + R.drawable.icon_1_year_hugger_unlock_svg + } else { + unit = context.getString(R.string.txt_1_year_hugger_dec2) + R.drawable.icon_1_year_hugger_lock_svg + } + name = context.getString(R.string.txt_1_year_hugger) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (18 * monthTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 18), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_purrfect_partner_dec1) + R.drawable.icon_purrfect_partner_unlock_svg + } else { + unit = context.getString(R.string.txt_purrfect_partner_dec2) + R.drawable.icon_purrfect_partner_lock_svg + } + name = context.getString(R.string.txt_purrfect_partner) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (2 * yearTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 24), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_2_year_companion_dec1) + R.drawable.icon_2_year_companion_unlock_svg + } else { + unit = context.getString(R.string.txt_2_year_companion_dec2) + R.drawable.icon_2_year_companion_lock_svg + } + name = context.getString(R.string.txt_2_year_companion) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (30 * monthTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 30), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_faithful_friend_dec1) + R.drawable.icon_socialite_unlock_svg + } else { + unit = context.getString(R.string.txt_faithful_friend_dec2) + R.drawable.icon_socialite_lock_svg + } + name = context.getString(R.string.txt_faithful_friend) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (3 * yearTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 36), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_3_year_harmony_dec1) + R.drawable.icon_3_year_harmony_unlock_svg + } else { + unit = context.getString(R.string.txt_3_year_harmony_dec2) + R.drawable.icon_3_year_harmony_lock_svg + } + name = context.getString(R.string.txt_3_year_harmony) + list.add(this) + } + + DataBean().apply { + imageId = if (diffTimestamp >= (5 * yearTimestamp)) { + unlockNum++ + state = ConstantInt.Open + value = Utils.formatTime( + Utils.getAfterHowTimestamp(toTimestamp, 30 * 60), Utils.DATE_FORMAT_PATTERN_EN3 + ) + unit = context.getString(R.string.txt_furr_timer_friend_dec1) + R.drawable.icon_furr_timer_friend_unlock_svg + } else { + unit = context.getString(R.string.txt_furr_timer_friend_dec2) + R.drawable.icon_furr_timer_friend_lock_svg + } + name = context.getString(R.string.txt_furr_timer_friend) + list.add(this) + } + + if (showShort) { + return if (unlockNum > shortCount) { + list.subList(unlockNum - shortCount, unlockNum) + } else { + list.subList(0, 4) + } + } + + return list + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/PetRelationViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/PetRelationViewModel.kt new file mode 100644 index 0000000..34bda5d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/PetRelationViewModel.kt @@ -0,0 +1,78 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.PetBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/3/4/004. + * @link + * @description: 宠物绑定,解绑操作 + */ +class PetRelationViewModel : ViewModel() { + + val mUnbindPetLiveData = MutableLiveData>() + val mUnbindPetListLiveData = MutableLiveData>>() + val mBindPetLiveData = MutableLiveData>() + val mDeletePetLiveData = MutableLiveData>() + val mAssignOtherPetLiveData = MutableLiveData>() + + fun bindPet(activity: BaseActivity<*>, deviceId: String, petId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.bindPet(userId, deviceId, petId) + mBindPetLiveData.value = result + } + } + + fun unbindPet(activity: BaseActivity<*>, deviceId: String, petId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.unbindPet(userId, deviceId, petId) + mUnbindPetLiveData.value = result + } + } + + fun deletePet(activity: BaseActivity<*>, petId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.deletePet(userId, petId) + mDeletePetLiveData.value = result + } + } + + /** + * 获取未绑定的设备宠物列表 + */ + fun getUnbindPetList(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getUnbindPetList(userId) + mUnbindPetListLiveData.value = result + } + } + + /** + * 获取未绑定的宠物设备列表 + */ + fun assignOtherPet( + activity: BaseActivity<*>, deviceId: String, originPetId: String, assignPetId: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.assignOtherPet(userId, deviceId, originPetId, assignPetId) + mAssignOtherPetLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/ScreenshotsViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/ScreenshotsViewModel.kt new file mode 100644 index 0000000..515f9c8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/ScreenshotsViewModel.kt @@ -0,0 +1,156 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import android.graphics.Bitmap +import android.view.View +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.util.FileUtil +import com.abbidot.tracker.util.ImageUtil +import com.baidu.mapapi.map.BaiduMap +import com.google.android.gms.maps.GoogleMap +import id.zelory.compressor.Compressor +import id.zelory.compressor.constraint.default +import id.zelory.compressor.constraint.destination +import id.zelory.compressor.saveBitmap +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File + +/** + *Created by .yzq on 2022/4/15/015. + * @link + * @description:截屏 + */ +class ScreenshotsViewModel : ViewModel() { + + val mMapSnapshotPathLiveData = MutableLiveData() + + /** + * 截屏谷歌地图围栏图片,并压缩返回文件路径 + */ + fun fencesGoogleMapSnapshotImage(activity: BaseActivity<*>, + googleMap: GoogleMap, + view: View, + fileName: String = "${System.currentTimeMillis()}.png") { + googleMap.snapshot { + if (null != it) { + fencesMapSnapshotImage(activity, it, view, fileName) + } + } + } + + /** + * 截屏谷歌地图围栏图片,并压缩返回文件路径 + */ + fun fencesBaiduMapSnapshotImage(activity: BaseActivity<*>, + baiduMap: BaiduMap, + view: View, + fileName: String = "${System.currentTimeMillis()}.png") { + baiduMap.snapshot { + if (null != it) { + fencesMapSnapshotImage(activity, it, view, fileName) + } + } + } + + /** + * 创建围栏保存地图截屏 + */ + private fun fencesMapSnapshotImage(activity: BaseActivity<*>, + bitmap: Bitmap, + view: View, + fileName: String = "${System.currentTimeMillis()}.png") { + activity.showLoading(true) + viewModelScope.launch(Dispatchers.IO) { + val file = + File(FileUtil.getDiskCacheDirectory(activity, FileUtil.SCREENSHOT_DIRECTORY_NAME), + fileName) + + //获取圆圈view的图片 + var viewBitmap = ImageUtil.viewToBitmap(view) + //压缩图片尺寸大小 + viewBitmap = + ImageUtil.zoomImg(viewBitmap, viewBitmap.width / 3 * 2, viewBitmap.height / 3 * 2) + + //合成图片 + var newBitmap = ImageUtil.combineBitmap(bitmap, viewBitmap) + + //从原位图中指定坐标点(x,y)开始,从中挖取宽width、高height的一块出来,创建新的Bitmap对象 + //截取谷歌地图中间图片 + val height = newBitmap.height / 6 * 2 + newBitmap = Bitmap.createBitmap(newBitmap, 0, height, newBitmap.width, + newBitmap.height - height * 2) + + //保存Bitmap图片 +// val out = FileOutputStream(file) +// if (newBitmap.compress(Bitmap.CompressFormat.PNG, 100, out)) { +// out.flush() +// out.close() +// } + + //保存Bitmap图片 + saveBitmap(newBitmap, file) + + //压缩图片 + val compressedImageFile = Compressor.compress(activity, File(file.path)) + mMapSnapshotPathLiveData.postValue(compressedImageFile.path) + } + } + + /** + * 历史记录,保存地图截屏 + */ + private fun saveMapSnapshotBitmap(context: Context, bitmap: Bitmap, fileName: String) { + viewModelScope.launch { + withContext(Dispatchers.IO) { + val imageName = "${fileName}_${FileUtil.DATA_HISTORY_FILE_NAME}" + val file = File(FileUtil.getDiskCacheDirectory(context, + FileUtil.SCREENSHOT_DIRECTORY_NAME), imageName) + val height = bitmap.height / 7 * 2 + val newBitmap = Bitmap.createBitmap(bitmap, 0, height, bitmap.width, height) + //保存Bitmap图片 + saveBitmap(newBitmap, file) + //压缩图片,将图像文件压缩到特定目标文件 + val compressedImageFile = Compressor.compress(context, File(file.path)) { + default() + //保存到指定文件 + destination(file) + } + XEventBus.post(EventName.HistoryDataSnapshot, compressedImageFile) + } + } + } + + /** + * 获取历史记录详情谷歌地图的截屏 + */ + fun getHistoryDataGoogleMapSnapshotImage(context: Context, + googleMap: GoogleMap, + fileName: String) { + googleMap.snapshot { + if (null != it) { + saveMapSnapshotBitmap(context, it, fileName) + } + } + } + + /** + * 获取历史记录详情百度地图的截屏 + */ + fun getHistoryDataBaiduMapSnapshotImage(context: Context, + baiduMap: BaiduMap, + fileName: String) { + //发起截图请求 + baiduMap.snapshot { + if (null != it) { + saveMapSnapshotBitmap(context, it, fileName) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/SocketSendMessageViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/SocketSendMessageViewModel.kt new file mode 100644 index 0000000..4d66527 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/SocketSendMessageViewModel.kt @@ -0,0 +1,43 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.util.SocketUtilManage +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock + +/** + *Created by .yzq on 2025/4/21/021. + * @link + * @description: + */ +class SocketSendMessageViewModel : ViewModel() { + + //线程同步锁 + private val mMutex = Mutex() + private var mSocketUtilManage: SocketUtilManage? = null + + /** + * 发送一个的消息, + */ + fun sendCmd( + socketUtilManage: SocketUtilManage, mac: String, msgID: Byte, msgData: MutableList + ) { + viewModelScope.launch(Dispatchers.IO) { + mMutex.withLock { + mSocketUtilManage = socketUtilManage.apply { + sendMessage(mac, msgID, msgData) + } + delay(2000) + } + } + } + + override fun onCleared() { + super.onCleared() + mSocketUtilManage?.destroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/SubscriptionManageViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/SubscriptionManageViewModel.kt new file mode 100644 index 0000000..5982c7c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/SubscriptionManageViewModel.kt @@ -0,0 +1,116 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.bean.OrderBean +import com.abbidot.tracker.bean.PackageBean +import com.abbidot.tracker.bean.PackageTypeBean +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2024/9/4/004. + * @link + * @description: + */ +class SubscriptionManageViewModel : ViewModel() { + + val mSubscriptionsOrderLiveData = MutableLiveData>>() + val mPackageTypeLiveData = MutableLiveData>>() + val mPackageListLiveData = MutableLiveData>>() + val mCommitRefundListLiveData = MutableLiveData>() + val mUpdateStatusListLiveData = MutableLiveData>() + val mSubscriptionsHistoryLiveData = + MutableLiveData>>() + val mReactivationFeeLiveData = MutableLiveData>() + val mRefundLiveData = MutableLiveData>() + + fun getSubscriptionsOrder() { + viewModelScope.launch { + val useId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getSubscriptionsOrder(useId) + mSubscriptionsOrderLiveData.value = result + } + } + + fun getPackageType() { + viewModelScope.launch { + val result = NetworkApi.getPackageType() + mPackageTypeLiveData.value = result + } + } + + fun getPackageList(planTypeName: String) { + viewModelScope.launch { + val result = NetworkApi.getPackageList(planTypeName) + mPackageListLiveData.value = result + } + } + + fun getSubscriptionsHistory(deviceId: String) { + viewModelScope.launch { + val useId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getSubscriptionsHistory(useId, deviceId) + mSubscriptionsHistoryLiveData.value = result + } + } + + fun commitRefund( + orderId: String, + chargeId: String, + amount: Int, + subscriptionId: String, + planName: String, + refundDescription: String, + deviceId: String, + deviceOutId: String, + payType: Int + ) { + viewModelScope.launch { + val result = NetworkApi.commitRefund( + "USD", + orderId, + chargeId, + amount, + subscriptionId, + planName, + refundDescription, + deviceId, + deviceOutId, + payType + ) + mCommitRefundListLiveData.value = result + } + } + + /** + * 退款 + */ + fun refund(orderNum: String) { + viewModelScope.launch { + val result = NetworkApi.refund(orderNum) + mRefundLiveData.value = result + } + } + + /** + * 更新订阅状态 状态 0是取消 1使其变为正常 + */ + fun updateSubscriptionStatus(subscriptionId: String, status: Int) { + viewModelScope.launch { + val result = NetworkApi.updateSubscriptionStatus(subscriptionId, status) + mUpdateStatusListLiveData.value = result + } + } + + fun getReactivationFee(orderId: String) { + viewModelScope.launch { + val result = NetworkApi.getReactivationFee(orderId) + mReactivationFeeLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/SubscriptionPayViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/SubscriptionPayViewModel.kt new file mode 100644 index 0000000..7e54b1a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/SubscriptionPayViewModel.kt @@ -0,0 +1,75 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.rsa.RSA +import com.abbidot.tracker.bean.CreditCardBean +import com.abbidot.tracker.bean.PayResultBean +import com.abbidot.tracker.bean.StripeSubscriptionPriceBean +import com.abbidot.tracker.bean.SubscriptionsOrderBean +import com.abbidot.tracker.retrofit2.NetworkApi +import com.google.gson.Gson +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2024/9/11/011. + * @link + * @description: + */ +class SubscriptionPayViewModel : ViewModel() { + + val mSubscriptionCustomerLiveData = MutableLiveData>() + val mCreateStripeOrderLiveData = MutableLiveData>() + val mCreatePaypalOrderLiveData = MutableLiveData>() + + /** + * Stripe支付创建消费用户 + */ + fun createCustomer(card: CreditCardBean) { +// activity.showLoading(true) + viewModelScope.launch(Dispatchers.IO) { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val userName = MMKVUtil.getString(MMKVKey.UserName) + val email = MMKVUtil.getString(MMKVKey.Email) + + val dates = card.expirationDate.split("/") + val cardParamsMap = mutableMapOf() + cardParamsMap["month"] = dates[0] + cardParamsMap["year"] = dates[1] + cardParamsMap["cvc"] = card.cvv + cardParamsMap["cardName"] = card.cardName + cardParamsMap["cardnum"] = card.cardNumber + val cardJso = Gson().toJson(cardParamsMap) + val b = + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofuJbnkwyF7SWg3eRadSYT+ttfHN+CUmCzH0WW1EWTUQOG+01Nv8ux+UJUlxbZA2X7959Et/+VKE03+4OY0/26dlgkROMlXCUnzsucuaoHW+9whGi6xklRZrDLi22QBae/8XV1YDEU2vfYXtcvwf66J0t+bRIhGytQUZvUrL7OEaAHvwJU0QwCy3wQGWcCgSkPpDruIe4ar19iZMleE+BdPGIfKfVdPvWFls63g1eMFECb8F9zN6ydGTCvlqvx/4gfDuFQnF+JuTSre8euSpTRTQn/Q0s3K6KiROVcAYCqDW6cKxNUr2ha1C9ZFxMws9C0qhx5wYzfSlIjFthPC1qwIDAQAB" + + val rsaKey = RSA.encrypt(cardJso, b) + LogUtil.e(cardJso) + val result = NetworkApi.createCustomer( + rsaKey, userId, userName, email + ) + mSubscriptionCustomerLiveData.postValue(result) + } + } + + fun createStripeOrder(subscriptionsOrder: SubscriptionsOrderBean) { + viewModelScope.launch { + LogUtil.e("$subscriptionsOrder") + val result = NetworkApi.createStripeOrder(subscriptionsOrder) + mCreateStripeOrderLiveData.value = result + } + } + + fun createPaypalOrder(subscriptionsOrder: SubscriptionsOrderBean) { + viewModelScope.launch { + LogUtil.e("$subscriptionsOrder") + val result = NetworkApi.createPaypalOrder(subscriptionsOrder) + mCreatePaypalOrderLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/TakePhotoAndCompressViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/TakePhotoAndCompressViewModel.kt new file mode 100644 index 0000000..ad16fa3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/TakePhotoAndCompressViewModel.kt @@ -0,0 +1,104 @@ +package com.abbidot.tracker.vm + +import android.content.Context +import android.text.TextUtils +import androidx.activity.ComponentActivity +import androidx.activity.result.ActivityResultLauncher +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.ui.common.MyCropImageContract +import com.abbidot.tracker.util.FileUtil.Companion.size +import com.huantansheng.easyphotos.models.album.entity.Photo +import id.zelory.compressor.Compressor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File + +/** + *Created by .yzq on 2022/3/23/023. + * @link + * @description:拍照和选取照片后,裁剪、压缩图片返回 + */ +class TakePhotoAndCompressViewModel : ViewModel() { + + private lateinit var mCropImageActivityResultLauncher: ActivityResultLauncher + val mPhotoPathLiveData = MutableLiveData>() + + /** + * 适用于:选了照片马上压缩的 + */ + fun dealCompressPhoto(context: Context, resultPhotos: ArrayList) { + val size = resultPhotos.size + if (size > 0) { + val fileList = mutableListOf() + viewModelScope.launch(Dispatchers.IO) { + LogUtil.d("一共选了多少张照片:$size") + for (i in 0 until size) { + val photo = resultPhotos[i] + val file = File(photo.path) + //压缩图片 + val compressedImageFile = Compressor.compress(context, file) + fileList.add(compressedImageFile) + + //打印第一张照片日志 + if (i == 0) { + LogUtil.d("第一张:${photo},size=${photo.size}byte") + LogUtil.d(compressedImageFile.path) + LogUtil.d("size=${compressedImageFile.length()}") + } + } + mPhotoPathLiveData.postValue(fileList) + } + } + } + + /** + * 放在OnCreate()中注册 + */ + fun registerCropImageActivityResult( + componentActivity: ComponentActivity, aspectRatioX: Int = 1, aspectRatioY: Int = 1 + ) { + mCropImageActivityResultLauncher = componentActivity.registerForActivityResult( + MyCropImageContract( + aspectRatioX, aspectRatioY + ) + ) { result -> + if (result.isSuccessful) { + // use the returned uri + val uriContent = result.uriContent + val uriFilePath = result.getUriFilePath(componentActivity) // optional usage + LogUtil.e("裁剪图片成功----------->$uriContent\n$uriFilePath") + compressImage(componentActivity, uriFilePath) + } + } + } + + /** + * 跳转裁剪图片并且压缩 + */ + fun goCropAndCompressImage(resultPhotos: ArrayList?) { + if (null == resultPhotos || resultPhotos.size == 0) return + val photo = resultPhotos[0] + mCropImageActivityResultLauncher.launch(photo) + } + + /** + * 压缩某张图片 + */ + private fun compressImage(context: Context, path: String?) { + if (null == path || TextUtils.isEmpty(path)) return + val fileList = mutableListOf() + viewModelScope.launch(Dispatchers.IO) { + val file = File(path) + //压缩图片 + val compressedImageFile = Compressor.compress(context, file) + LogUtil.e( + "裁剪图片压缩前:size=${file.size()},path=${file.path} \n压缩后:size=${compressedImageFile.size()},path=${compressedImageFile.path}" + ) + fileList.add(compressedImageFile) + mPhotoPathLiveData.postValue(fileList) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/TrackerInfoViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/TrackerInfoViewModel.kt new file mode 100644 index 0000000..ad0bd96 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/TrackerInfoViewModel.kt @@ -0,0 +1,68 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DeviceBean +import com.abbidot.tracker.bean.DeviceDetailBean +import com.abbidot.tracker.bean.MapDeviceBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/2/16/016. + * @link + * @description:设备相关信息 + */ +class TrackerInfoViewModel : ViewModel() { + + val getDeviceListLiveData = MutableLiveData>>() + val mUnbindTrackerListLiveData = MutableLiveData>>() + val mDeviceDetailLiveData = MutableLiveData>() + val mTrackerInfoLiveData = MutableLiveData>() + + /** + * 获取设备列表 + * @param type type=1 是自己的 0是显示授权 + */ + fun getDeviceList(activity: BaseActivity<*>, type: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getDeviceList(MMKVUtil.getString(MMKVKey.UserId), type) + getDeviceListLiveData.value = result + } + } + + + fun getDeviceDetail(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getDeviceDetail(deviceId) + mDeviceDetailLiveData.value = result + } + } + + + fun getPetTrackerInfo(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(false) + viewModelScope.launch { + val result = NetworkApi.getPetTrackerInfo(deviceId) + mTrackerInfoLiveData.value = result + } + } + + /** + * 获取未绑定的宠物设备列表 + */ + fun getUnbindTrackerList(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getUnboundDevices(userId) + mUnbindTrackerListLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/TrackerSetViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/TrackerSetViewModel.kt new file mode 100644 index 0000000..47041a7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/TrackerSetViewModel.kt @@ -0,0 +1,109 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.DeviceDetailBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/2/26/026. + * @link + * @description:设备设置相关 + */ +class TrackerSetViewModel : ViewModel() { + val mAddBindDeviceLiveData = MutableLiveData>() + val mLedSwitchLiveData = MutableLiveData>() + val mTurnoffLiveData = MutableLiveData>() + val mReportIntervalLiveData = MutableLiveData>() + val mSetLiveTimeLiveData = MutableLiveData>() + val mUnbindDeviceLiveData = MutableLiveData>() + val mAssignOtherTrackerLiveData = MutableLiveData>() + + /** + * 设置ledSwitch开关 + */ + fun setLedSwitch(activity: BaseActivity<*>?, deviceInfoId: String, ledSwitch: Int) { + activity?.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.setLedSwitch(deviceInfoId, ledSwitch) + mLedSwitchLiveData.value = result + } + } + + /** + * 关闭设备 + */ + fun turnOff(activity: BaseActivity<*>, deviceInfoId: String, turnOff: Int = 0) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.turnoff(deviceInfoId, turnOff) + mTurnoffLiveData.value = result + } + } + + /** + * 设置设备上报下发时间 + */ + fun reportInterval(activity: BaseActivity<*>, deviceInfoId: String, gnssInterval: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.reportInterval(deviceInfoId, gnssInterval) + mReportIntervalLiveData.value = result + } + } + + /** + * 设置设备的直播时间 + */ + fun setLiveTime(activity: BaseActivity<*>, deviceId: String, liveTime: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.setLiveTime(deviceId, liveTime) + mSetLiveTimeLiveData.value = result + } + } + + /** + * 解绑删除设备 + */ + fun unbindDevice(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.unbindDevice(userId, deviceId) + mUnbindDeviceLiveData.value = result + } + } + + /** + * 绑定设备 + */ + fun bindDevice(activity: BaseActivity<*>, deviceOutId: String) { + activity.showLoading(true) + val userId = MMKVUtil.getString(MMKVKey.UserId) + viewModelScope.launch { + val result = NetworkApi.bindDevice(userId, deviceOutId) + mAddBindDeviceLiveData.value = result + } + } + + /** + * 关联其他宠物/设备 + */ + fun assignOtherPetTracker( + activity: BaseActivity<*>, petId: String, originDeviceId: String, assignDeviceId: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = + NetworkApi.assignOtherPetTracker(userId, petId, originDeviceId, assignDeviceId) + mAssignOtherTrackerLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/UserProfileViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/UserProfileViewModel.kt new file mode 100644 index 0000000..1bdb70b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/UserProfileViewModel.kt @@ -0,0 +1,165 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.base.BaseFragment +import com.abbidot.tracker.bean.CountryCodeBean +import com.abbidot.tracker.bean.UserBean +import com.abbidot.tracker.bean.UserInfoBean +import com.abbidot.tracker.retrofit2.NetworkApi +import com.abbidot.tracker.util.Util +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2022/2/14/014. + * @link + * @description:注册ViewModel + */ +class UserProfileViewModel : ViewModel() { + + val mGetEmailCodeLiveData = MutableLiveData>() + val mValidateCodeLiveData = MutableLiveData>() + private val mRegisterLiveData = MutableLiveData>() + val mCountryCodeLiveData = MutableLiveData>>() + val mRegisterV2LiveData = MutableLiveData>() + val mUpdateUserInfoLiveData = MutableLiveData>() + val mUploadImageLiveData = MutableLiveData>() + val mUserInfoLiveData = MutableLiveData>() + val mUserLogoffLiveData = MutableLiveData>() + val mUpdateMeasureUnitLiveData = MutableLiveData>() + + /** + * 获取邮箱验证码 + * @param type 验证码类型 1是注册 2是忘记密码 3修改邮箱 + */ + fun getEmailCode(activity: BaseActivity<*>, email: String, type: Int) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.requestVerificationCode(email, type) + mGetEmailCodeLiveData.value = result + } + } + + /** + * 验证码验证 + */ + fun validateCode(fragment: BaseFragment<*>, email: String, emailCode: String, type: Int) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.validateCode(email, emailCode, type) + mValidateCodeLiveData.value = result + } + } + + /** + * 注册 + */ + fun register(fragment: BaseFragment<*>, email: String, password: String, userName: String) { + fragment.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.register(email, password, userName) + mRegisterLiveData.value = result + } + } + + /** + * 获取国家代码号 + */ + fun getCountryCodes(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getCountryCodes() + mCountryCodeLiveData.value = result + } + } + + /** + * 注册/创建用户 + */ + fun registerV2( + activity: BaseActivity<*>, + email: String, + password: String, + userName: String, + countryCode: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.registerV2(email, password, userName, countryCode) + mRegisterV2LiveData.value = result + } + } + + /** + * 更新用户信息 + */ + fun updateUserInfo( + activity: BaseActivity<*>, + email: String, + imgurl: String, + countryCode: String, + username: String + ) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.updateUser(email, userId, imgurl, countryCode, username) + mUpdateUserInfoLiveData.value = result + } + } + + /** + * 上传头像 + */ + fun uploadImage(activity: BaseActivity<*>, pathImg: String) { + activity.runOnUiThread { + activity.showLoading(true) + } + viewModelScope.launch { + val result = NetworkApi.uploadImage(Util.uploadFile(pathImg)) + mUploadImageLiveData.value = result + } + } + + + /** + * 获取用户信息 + */ + fun getUserInfo(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.getUserInfo(userId) + mUserInfoLiveData.value = result + } + } + + + /** + * 用户注销 + */ + fun logoff(activity: BaseActivity<*>) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.logoff(userId) + mUserLogoffLiveData.value = result + } + } + + /** + *修改单位 + */ + fun updateMeasureUnit(activity: BaseActivity<*>, measurementUnit: Int) { + activity.showLoading(true) + viewModelScope.launch { + val userId = MMKVUtil.getString(MMKVKey.UserId) + val result = NetworkApi.updateMeasureUnit(userId, measurementUnit) + mUpdateMeasureUnitLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/vm/WiFiZoneViewModel.kt b/app/src/main/java/com/abbidot/tracker/vm/WiFiZoneViewModel.kt new file mode 100644 index 0000000..c17586c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/vm/WiFiZoneViewModel.kt @@ -0,0 +1,83 @@ +package com.abbidot.tracker.vm + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.abbidot.tracker.base.BaseActivity +import com.abbidot.tracker.bean.WiFiZoneBean +import com.abbidot.tracker.bean.WiFiZoneListBean +import com.abbidot.tracker.retrofit2.NetworkApi +import kotlinx.coroutines.launch + +/** + *Created by .yzq on 2024/6/5/005. + * @link + * @description: + */ +class WiFiZoneViewModel : ViewModel() { + + val mWiFiZoneListLiveData = MutableLiveData>() + val mAddWiFiZoneLiveData = MutableLiveData>() + val mUpdateWiFiZoneLiveData = MutableLiveData>() + val mDeleteWiFiZoneLiveData = MutableLiveData>() + + /** + *添加wifi区域 + */ + fun addWifiZone(activity: BaseActivity<*>, wiFiZoneBean: WiFiZoneBean) { + activity.showLoading(true) + viewModelScope.launch { + wiFiZoneBean.apply { + val result = NetworkApi.addWifiZone( + deviceId, wifiZoneName, address, wifiMac, wifiName, longitude, latitude, type + ) + mAddWiFiZoneLiveData.value = result + } + } + } + + /** + *更新wifi区域 + */ + fun updateWifiZone(activity: BaseActivity<*>, wiFiZoneBean: WiFiZoneBean) { + activity.showLoading(true) + viewModelScope.launch { + wiFiZoneBean.apply { + val result = NetworkApi.updateWifiZone( + wifiZoneId, + deviceId, + wifiZoneName, + address, + wifiMac, + wifiName, + longitude, + latitude, + type + ) + mUpdateWiFiZoneLiveData.value = result + } + } + } + + /** + *获取wifi区域列表 + */ + fun deleteWifiZone(activity: BaseActivity<*>, wiFiZoneBean: WiFiZoneBean) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.removeWifiZone(wiFiZoneBean.wifiZoneId) + mDeleteWiFiZoneLiveData.value = result + } + } + + /** + *获取wifi区域列表 + */ + fun getWifiZoneList(activity: BaseActivity<*>, deviceId: String) { + activity.showLoading(true) + viewModelScope.launch { + val result = NetworkApi.getWifiZoneList(deviceId) + mWiFiZoneListLiveData.value = result + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/BarChartV2View.kt b/app/src/main/java/com/abbidot/tracker/widget/BarChartV2View.kt new file mode 100644 index 0000000..82a093d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/BarChartV2View.kt @@ -0,0 +1,217 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.abs + + +/** + *Created by .yzq on 2023/12/29/029. + * @link + * @description: + */ +class BarChartV2View : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + //每个圆柱的宽度 + private var mBarWidth = 0f + + //圆柱之间的间距 + private var mBarSpace = 0f + private var mRadius = 0f + private var mData: MutableList? = null + private lateinit var mPaint: Paint + private lateinit var mTextPaint: Paint + private lateinit var mMaxPaint: Paint + + private val mXAxis = mutableListOf("0:00", "4:00", "8:00", "12:00", "16:00", "20:00", "24:00") + private var mYAxis = mutableListOf(20, 40, 60, 80, 100, 120) + + //每个数值1所需要、占用的高度 + private var mValueHeight = 0f + private var mMaxYValue = 120 + private var mMaxValue = 0 + + //配合动画,每次画百分几 + private var mAnimatedPercent = 0f + + //动画时长(毫秒) + private val mAnimatorDuration = 1500L + + private var mAxisTextColor = 0 + private var mBarValueColor = 0 + private var mBarMaxValueColor = 0 + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context + mBarWidth = QMUIDisplayHelper.dpToPx(18).toFloat() + mRadius = QMUIDisplayHelper.dpToPx(8).toFloat() + + mPaint = Paint().apply { + color = mBarValueColor + } + mTextPaint = Paint().apply { + color = mAxisTextColor + textSize = QMUIDisplayHelper.sp2px(mContext, 10).toFloat() + } + mMaxPaint = Paint().apply { + color = mBarMaxValueColor + } + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + mAxisTextColor = ContextCompat.getColor(context, R.color.select_color3) + mBarValueColor = ContextCompat.getColor(context, R.color.dark_color) + mBarMaxValueColor = ContextCompat.getColor(context, R.color.select_color2) + return + } + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BarChartV2View) + mAxisTextColor = typedArray.getColor( + R.styleable.BarChartV2View_bar_v2_axis_text_color, + ContextCompat.getColor(context, R.color.select_color3) + ) + mBarValueColor = typedArray.getColor( + R.styleable.BarChartV2View_bar_v2_value_color, + ContextCompat.getColor(context, R.color.dark_color) + ) + mBarMaxValueColor = typedArray.getColor( + R.styleable.BarChartV2View_bar_v2_max_value_color, + ContextCompat.getColor(context, R.color.select_color2) + ) + typedArray.recycle() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas?.apply { + drawAxis(this) + startDraw(this) + } + } + + + private fun drawAxis(canvas: Canvas) { + val fontMetrics = mTextPaint.fontMetrics // 获取字体指标信息 + val textHeight = abs((fontMetrics.top - fontMetrics.bottom))// 计算文本高度 + + val xSize = mXAxis.size + val xTxtWith = mTextPaint.measureText(mXAxis[xSize - 1]) + val xSpace = (mWidth - textHeight / 2 - xTxtWith * xSize) / (xSize - 1) + for (i in 0 until xSize) { + canvas.drawText( + mXAxis[i], xSpace * i + xTxtWith * i + textHeight / 2, mHeight.toFloat(), mTextPaint + ) + } + + val ySize = mYAxis.size + val ySpace = (mHeight - textHeight * ySize) / (ySize + 1) + for (i in 0 until ySize) { + canvas.drawText( + mYAxis[i].toString(), + 0f, + mHeight - (ySpace * (i + 1)) - textHeight * (i + 1), + mTextPaint + ) + } + } + + private fun startDraw(canvas: Canvas) { + mData?.apply { + if (size > 0) { + //因有xy轴的文字,重新计算柱状图的长宽 + val ySize = mYAxis.size + val yTxtWith = mTextPaint.measureText(mYAxis[ySize - 1].toString()) + val fontMetrics = mTextPaint.fontMetrics // 获取字体指标信息 + val textHeight = abs((fontMetrics.top - fontMetrics.bottom))// 计算文本高度 + val yHeight = mHeight - textHeight / 2 - QMUIDisplayHelper.dpToPx(3) + val xWith = mWidth - yTxtWith + mValueHeight = yHeight / mMaxYValue + mBarSpace = ((xWith - mBarWidth * size) / size) + + for (i in 0 until size) { + val value = this[i] + if (value <= 0) continue + val left = (mBarSpace * (i + 1) + i * mBarWidth) + yTxtWith + val top = (yHeight - value * mValueHeight) + val right = (mBarSpace * (i + 1) + mBarWidth * (i + 1)) + yTxtWith + + //防止动画从x轴原点以下开始出现 + val newTop = with(top / mAnimatedPercent) { + if (this > yHeight) yHeight + else if (this == 0f || isNaN()) yHeight - yHeight * mAnimatedPercent + else this + } + if (value == mMaxValue) { + canvas.drawRoundRect( + left, newTop, right, yHeight, mRadius, mRadius, mMaxPaint + ) + } else { + canvas.drawRoundRect( + left, newTop, right, yHeight, mRadius, mRadius, mPaint + ) + } + + } + } + } + } + + fun setData(data: MutableList) { + val size = data.size + mMaxValue = 0 + for (i in 0 until size) { + if (data[i] > mMaxValue) { + mMaxValue = data[i] + } + } + if (mMaxValue > 60) { + mMaxYValue = 120 + mYAxis = mutableListOf(20, 40, 60, 80, 100, 120) + } else { + mMaxYValue = 60 + mYAxis = mutableListOf(10, 20, 30, 40, 50, 60) + } + mData = data + + ViewUtil.instance.setValueAnimator(0f, 100f, mAnimatorDuration) { + mAnimatedPercent = it.animatedValue as Float / 100 + postInvalidate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/BatteryView.kt b/app/src/main/java/com/abbidot/tracker/widget/BatteryView.kt new file mode 100644 index 0000000..c97c4c4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/BatteryView.kt @@ -0,0 +1,160 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseView + +/** + *Created by .yzq on 2024/10/11/011. + * @link + * @description:电池图标 + */ +class BatteryView : BaseView { + + private val mMaxValue = 4 + private var mValueWith = 0f + + //圆形矩形的半径 + private var mRectCircleRadius = 0f + + //椭圆的半径 + private var mHalfCircleRadius = 0f + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + override fun init(context: Context, attrs: AttributeSet?) { + super.init(context, attrs) + mWidth = AppUtils.dpToPx(24) + mHeight = AppUtils.dpToPx(13) + mRectCircleRadius = AppUtils.dpToPx(3f) + mHalfCircleRadius = AppUtils.dpToPx(3f) + mValueWith = AppUtils.dpToPx(2f) + + mPaint.apply { + color = ContextCompat.getColor(mContext, R.color.data_black_color) + strokeWidth = mStrokeWidth + strokeCap = Paint.Cap.ROUND + style = Paint.Style.STROKE + //开启抗锯齿 + flags = Paint.ANTI_ALIAS_FLAG + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + setMeasuredDimension(mWidth, mHeight) + } + + override fun startDraw(canvas: Canvas) { + canvas.apply { + val rectLeft = mStrokeWidth / 2 + val rectRight = mWidth - mStrokeWidth / 2 - mHalfCircleRadius + val rectTop = mStrokeWidth / 2 + val rectBottom = mHeight - mStrokeWidth / 2 + + val arcLeft = mWidth - mStrokeWidth / 2 - mHalfCircleRadius * 2 + val arcTop = mHeight / 4 + mStrokeWidth / 2 + val arcRight = mWidth - mStrokeWidth / 2 + val arcBottom = mHeight / 4 * 3 - mStrokeWidth / 2 + + when (mValue) { + -1 -> { + mPaint.color = ContextCompat.getColor(mContext, R.color.data_black_color) + drawRoundRect( + rectLeft, + rectTop, + rectRight, + rectBottom, + mRectCircleRadius, + mRectCircleRadius, + mPaint + ) + drawArc( + arcLeft, arcTop, arcRight, arcBottom, -90f, 180f, false, mPaint + ) + + val sX = mWidth - mStrokeWidth * 2 - mHalfCircleRadius + val line1StartX = sX / 6 * 2 + mStrokeWidth * 2 + val line1StartY = (mHeight - mStrokeWidth * 2) / 4 + mStrokeWidth + val line1StopX = sX / 6 * 4 + mStrokeWidth + val line1StopY = (mHeight - mStrokeWidth * 2) / 4 * 3 + mStrokeWidth + mPaint.color = ContextCompat.getColor(mContext, R.color.select_color3) + drawLine( + line1StartX, line1StartY, line1StopX, line1StopY, mPaint + ) + drawLine( + line1StartX, line1StopY, line1StopX, line1StartY, mPaint + ) + } + + 0 -> { + mPaint.color = ContextCompat.getColor(mContext, R.color.yellow_red_color2) + drawRoundRect( + rectLeft, + rectTop, + rectRight, + rectBottom, + mRectCircleRadius, + mRectCircleRadius, + mPaint + ) + drawArc( + arcLeft, arcTop, arcRight, arcBottom, -90f, 180f, false, mPaint + ) + } + + else -> { + mPaint.color = ContextCompat.getColor(mContext, R.color.data_black_color) + drawRoundRect( + rectLeft, + rectTop, + rectRight, + rectBottom, + mRectCircleRadius, + mRectCircleRadius, + mPaint + ) + drawArc( + arcLeft, arcTop, arcRight, arcBottom, -90f, 180f, false, mPaint + ) + val valueWidth = mWidth - mStrokeWidth * 2 - mHalfCircleRadius + val valueSpace = (valueWidth - mValueWith * mMaxValue) / (mMaxValue + 3) + mPaint.color = ContextCompat.getColor(mContext, R.color.select_color) + for (i in 0 until mValue) { + val lineX = mStrokeWidth + 3 * valueSpace + mValueWith * i + valueSpace * i + drawLine( + lineX, + mHeight / 4f + mStrokeWidth, + lineX, + mHeight / 4f * 3 - mStrokeWidth, + mPaint + ) + } + } + } + } + } + + fun setValue(value: Int) { + mValue = value + invalidate() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/BrowserView.java b/app/src/main/java/com/abbidot/tracker/widget/BrowserView.java new file mode 100644 index 0000000..13fc333 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/BrowserView.java @@ -0,0 +1,411 @@ +package com.abbidot.tracker.widget; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.net.http.SslError; +import android.os.Build; +import android.text.InputType; +import android.util.AttributeSet; +import android.view.ViewGroup; +import android.webkit.JsPromptResult; +import android.webkit.JsResult; +import android.webkit.SslErrorHandler; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebResourceError; +import android.webkit.WebResourceRequest; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.view.ContextThemeWrapper; + +import com.abbidot.tracker.R; +import com.abbidot.tracker.base.BaseActivity; +import com.abbidot.tracker.constant.GetResultCallback; +import com.abbidot.tracker.dialog.DialogUtils; +import com.abbidot.tracker.util.Util; +import com.abbidot.tracker.util.ViewUtil; +import com.hjq.permissions.XXPermissions; +import com.hjq.permissions.permission.PermissionLists; + +/** + * desc : 基于 WebView 封装 + */ +public final class BrowserView extends WebView { + + public BrowserView(Context context) { + this(context, null); + } + + public BrowserView(Context context, AttributeSet attrs) { + this(context, attrs, android.R.attr.webViewStyle); + } + + public BrowserView(Context context, AttributeSet attrs, int defStyleAttr) { + this(getFixedContext(context), attrs, defStyleAttr, 0); + } + + @SuppressLint("SetJavaScriptEnabled") + public BrowserView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + WebSettings settings = getSettings(); + // 允许文件访问 + settings.setAllowFileAccess(true); + // 允许网页定位 + settings.setGeolocationEnabled(true); + // 允许保存密码 + //settings.setSavePassword(true); + // 开启 JavaScript + settings.setJavaScriptEnabled(true); + // 允许网页弹对话框 + settings.setJavaScriptCanOpenWindowsAutomatically(true); + // 加快网页加载完成的速度,等页面完成再加载图片 + settings.setLoadsImagesAutomatically(true); + // 本地 DOM 存储(解决加载某些网页出现白板现象) + settings.setDomStorageEnabled(true); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + // 解决 Android 5.0 上 WebView 默认不允许加载 Http 与 Https 混合内容 + settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); + } + + // 不显示滚动条 + setVerticalScrollBarEnabled(false); + setHorizontalScrollBarEnabled(false); + } + + /** + * 修复原生 WebView 和 AndroidX 在 Android 5.x 上面崩溃的问题 + *

+ * doc:https://stackoverflow.com/questions/41025200/android-view-inflateexception-error + * -inflating-class-android-webkit-webview + */ + public static Context getFixedContext(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { + // 这种写法返回的 Context 是 ContextImpl,而不是 Activity 或者 ContextWrapper + // 为什么不用 ContextImpl,因为使用 ContextImpl 获取不到 Activity 对象,而 ContextWrapper 可以 + // return context.createConfigurationContext(new Configuration()); + // 如果使用 ContextWrapper 还是导致崩溃,因为 Resources 对象冲突了 + // return new ContextWrapper(context); + // 如果使用 ContextThemeWrapper 就没有问题,因为它重写了 getResources 方法,返回的是一个新的 Resources 对象 + return new ContextThemeWrapper(context, context.getTheme()); + } + return context; + } + + /** + * 处理谷歌提交应用 SSL Error Handler错误 + * + * @param activity + * @param handler + * @param error + */ + public void setOnReceivedSslError(Activity activity, SslErrorHandler handler, SslError error) { + String message = "SSL Certificate error."; + switch (error.getPrimaryError()) { + case SslError.SSL_UNTRUSTED: + message = "The certificate authority is not trusted."; + break; + case SslError.SSL_EXPIRED: + message = "The certificate has expired."; + break; + case SslError.SSL_IDMISMATCH: + message = "The certificate Hostname mismatch."; + break; + case SslError.SSL_NOTYETVALID: + message = "The certificate is not yet valid."; + break; + case SslError.SSL_DATE_INVALID: + message = "The date of the certificate is invalid"; + break; + case SslError.SSL_INVALID: + default: + message = "A generic error occurred"; + break; + } + message += " Do you want to continue anyway?"; + + ViewUtil.Companion.getInstance() + .showDialog(activity, "SSL Certificate Error", myAlertDialog -> { + myAlertDialog.dismiss(); + handler.proceed(); + }, "", R.string.txt_sure, R.string.txt_cancel, myAlertDialog -> { + myAlertDialog.dismiss(); + handler.cancel(); + }); + } + + /** + * 获取当前的 url + * + * @return 返回原始的 url,因为有些url是被WebView解码过的 + */ + @Override + public String getUrl() { + String originalUrl = super.getOriginalUrl(); + // 避免开始时同时加载两个地址而导致的崩溃 + if (originalUrl != null) { + return originalUrl; + } + return super.getUrl(); + } + + @Override + public void onResume() { + super.onResume(); + resumeTimers(); + } + + @Override + public void onPause() { + super.onPause(); + pauseTimers(); + } + + public void onDestroy() { + ((ViewGroup) getParent()).removeView(this); + //清除历史记录 + clearHistory(); + //停止加载 + stopLoading(); + //加载一个空白页 + loadUrl("about:blank"); + setBrowserChromeClient(null); + setBrowserViewClient(null); + //移除WebView所有的View对象 + removeAllViews(); + //销毁此的WebView的内部状态 + destroy(); + } + + /** + * 已过时,推荐使用 {@link BrowserViewClient} + */ + @Deprecated + @Override + public void setWebViewClient(WebViewClient client) { + super.setWebViewClient(client); + } + + public void setBrowserViewClient(BrowserViewClient client) { + super.setWebViewClient(client); + } + + /** + * 已过时,推荐使用 {@link BrowserChromeClient} + */ + @Deprecated + @Override + public void setWebChromeClient(WebChromeClient client) { + super.setWebChromeClient(client); + } + + public void setBrowserChromeClient(BrowserChromeClient client) { + super.setWebChromeClient(client); + } + + public static class BrowserViewClient extends WebViewClient { + + /** + * 同名 API 兼容 + */ + @TargetApi(Build.VERSION_CODES.M) + @Override + public void onReceivedError(WebView view, WebResourceRequest request, + WebResourceError error) { + if (request.isForMainFrame()) { + onReceivedError(view, error.getErrorCode(), error.getDescription() + .toString(), request.getUrl().toString()); + } + } + + @Override + public void onReceivedError(WebView view, int errorCode, String description, + String failingUrl) { + super.onReceivedError(view, errorCode, description, failingUrl); + } + + // @Override + // public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) { + // // 注意一定要去除这行代码,否则设置无效。 + // //super.onReceivedSslError(view, handler, error); + // // Android默认的处理方式 + // //handler.cancel(); + // // 接受所有网站的证书 + // handler.proceed(); + // } + + /** + * 同名 API 兼容 + */ + @TargetApi(Build.VERSION_CODES.N) + @Override + public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { + return shouldOverrideUrlLoading(view, request.getUrl().toString()); + } + + /** + * 跳转到其他链接 + */ + @Override + public boolean shouldOverrideUrlLoading(WebView view, final String url) { + String scheme = Uri.parse(url).getScheme(); + if (scheme != null) { + scheme = scheme.toLowerCase(); + } + if ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)) { + view.loadUrl(url); + } + // 已经处理该链接请求 + return true; + } + } + + public static class BrowserChromeClient extends WebChromeClient { + + private final BrowserView mWebView; + + public BrowserChromeClient(BrowserView view) { + mWebView = view; + if (mWebView == null) { + throw new IllegalArgumentException("are you ok?"); + } + } + + /** + * 网页弹出警告框 + */ + @Override + public boolean onJsAlert(WebView view, String url, String message, JsResult result) { + ViewUtil.Companion.getInstance() + .showDialog(mWebView.getContext(), message, myAlertDialog -> { + myAlertDialog.dismiss(); + result.confirm(); + }, "", R.string.txt_sure, R.string.txt_cancel, myAlertDialog -> { + myAlertDialog.dismiss(); + result.cancel(); + }); + return true; + } + + /** + * 网页弹出确定取消框 + */ + @Override + public boolean onJsConfirm(WebView view, String url, String message, JsResult result) { + ViewUtil.Companion.getInstance() + .showDialog(mWebView.getContext(), message, myAlertDialog -> { + myAlertDialog.dismiss(); + result.confirm(); + }, "", R.string.txt_sure, R.string.txt_cancel, myAlertDialog -> { + myAlertDialog.dismiss(); + result.cancel(); + }); + + return true; + } + + /** + * 网页弹出输入框 + */ + @Override + public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, + JsPromptResult result) { + // DialogUtils.Companion.showEditTextDialog(mWebView.getContext(), message, defaultValue + // , any -> { + // String str = any.toString(); + // result.confirm(str); + // }, InputType.TYPE_CLASS_TEXT, 100); + // + DialogUtils.Companion.showEditTextDialog(mWebView.getContext(), message, defaultValue + , new GetResultCallback() { + @Override + public void onRequestError(@Nullable String exceptionCode) { + } + + @Override + public void onResult(@NonNull Object any) { + String str = any.toString(); + result.confirm(str); + } + }, InputType.TYPE_CLASS_TEXT, 100); + return true; + } + + /** + * 网页弹出选择文件请求(测试地址:https://app.xunjiepdf.com/jpg2pdf/、http://www.script-tutorials + * .com/demos/199/index.html) + * + * @param callback 文件选择回调 + * @param params 文件选择参数 + */ + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + @Override + public boolean onShowFileChooser(WebView webView, ValueCallback callback, + FileChooserParams params) { + Util.Companion.checkPermissions(mWebView.getContext(), + new Util.RequestPermissionCallback() { + @Override + public void onRequestPermissionSuccess() { + openSystemFileChooser((BaseActivity) (mWebView.getContext()), callback, params); + } + + @Override + public void onRequestPermissionFail() { + callback.onReceiveValue(null); + XXPermissions.startPermissionActivity((BaseActivity) (mWebView.getContext()), + PermissionLists.getWriteExternalStoragePermission(), + PermissionLists.getReadExternalStoragePermission()); + } + }, "网页弹出选择文件请求", PermissionLists.getWriteExternalStoragePermission(), + PermissionLists.getReadExternalStoragePermission()); + + return true; + } + + /** + * 打开系统文件选择器 + */ + private void openSystemFileChooser(BaseActivity activity, ValueCallback callback, + FileChooserParams params) { + Intent intent = params.createIntent(); + String[] mimeTypes = params.getAcceptTypes(); + if (mimeTypes != null && mimeTypes.length > 0 && mimeTypes[0] != null && !"".equals(mimeTypes[0])) { + // 设置要过滤的文件类型 + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes); + } + // 设置是否是多选模式 + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, + params.getMode() == FileChooserParams.MODE_OPEN_MULTIPLE); + // activity.startActivityForResult(Intent.createChooser(intent, params.getTitle()), + // (resultCode, data) -> { + // Uri[] uris = null; + // if (resultCode == Activity.RESULT_OK && data != null) { + // Uri uri = data.getData(); + // if (uri != null) { + // // 如果用户只选择了一个文件 + // uris = new Uri[]{uri}; + // } else { + // // 如果用户选择了多个文件 + // ClipData clipData = data.getClipData(); + // if (clipData != null) { + // uris = new Uri[clipData.getItemCount()]; + // for (int i = 0; i < clipData.getItemCount(); i++) { + // uris[i] = clipData.getItemAt(i).getUri(); + // } + // } + // } + // } + // // 不管用户最后有没有选择文件,最后还是调用 onReceiveValue,如果没有调用就会导致网页再次上传无响应 + // callback.onReceiveValue(uris); + // }); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/CircleCountDownView.kt b/app/src/main/java/com/abbidot/tracker/widget/CircleCountDownView.kt new file mode 100644 index 0000000..e3e69bf --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/CircleCountDownView.kt @@ -0,0 +1,147 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.os.CountDownTimer +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R + +/** + *Created by .yzq on 2023/3/3/003. + * @link + * @description: 倒计时圆形 + */ +class CircleCountDownView : AppCompatImageView { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + private lateinit var mPaint: Paint + + //倒计时默认30秒 + private var mCountDownTimer: CountDownTimer? = null + private var mSweepAngle: Float = 360f + + //倒计时变化的速率,越小越快 + private var mSpeed = 100L + + private var mOnCircleCountDownCallback: OnCircleCountDownCallback? = null + + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, + defStyleAttr) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + //开启抗锯齿 + mPaint = Paint(Paint.ANTI_ALIAS_FLAG) + + if (null == attrs) { + mPaint.color = ContextCompat.getColor(mContext, R.color.btn_yellow_color) + mPaint.alpha = 255 / 2 + } else { + val obtainStyledAttributes = + mContext.obtainStyledAttributes(attrs, R.styleable.CircleCountDownView) + mPaint.color = obtainStyledAttributes.getColor(R.styleable.CircleCountDownView_color, + ContextCompat.getColor(mContext, R.color.btn_yellow_color)) + mPaint.alpha = + obtainStyledAttributes.getInt(R.styleable.CircleCountDownView_colorAlpha, 255 / 2) + obtainStyledAttributes.recycle() + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + LogUtil.e("onMeasure") + mWidth = measuredWidth + mHeight = measuredHeight +// val widthMode = MeasureSpec.getMode(widthMeasureSpec) +// val heightMode = MeasureSpec.getMode(heightMeasureSpec) +// if (widthMode == MeasureSpec.AT_MOST) { +// background?.apply { +// mWidth = this.minimumWidth +// } +// } +// if (heightMode == MeasureSpec.AT_MOST) { +// mHeight = this.minimumHeight +// } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + drawArc(0f, 0f, mWidth.toFloat(), mHeight.toFloat(), 270f, -mSweepAngle, true, mPaint) + } + } + + fun startCountDown(second: Int) { + LogUtil.d("倒计时开始") + stopCountDown() + if (second <= 0) { + //不需要扇形背景 + mSweepAngle = 0f + invalidate() + LogUtil.e("CircleCountDownView,时间秒不能小于等于0") + return + } + mSweepAngle = 360f + mSpeed = if (second < (5 * 60)) { + 80L + } else { + 180L + } + val quantity = mSweepAngle / second + mCountDownTimer = object : CountDownTimer(second * 1000L, mSpeed) { + override fun onTick(millisUntilFinished: Long) { + val time = millisUntilFinished / 1000f + mSweepAngle = quantity * time + postInvalidate() + } + + override fun onFinish() { + LogUtil.d("倒计时结束") + mSweepAngle = 360f + invalidate() + mOnCircleCountDownCallback?.countDownCompleteCallback() + } + } + mCountDownTimer?.apply { + start() + mOnCircleCountDownCallback?.startCountDownCallback() + } + } + + fun stopCountDown() { + mCountDownTimer?.apply { + LogUtil.d("倒计时停止") + cancel() + mSweepAngle = 360f + invalidate() + } + } + + fun setOnCircleCountDownCallback(onCircleCountDownCallback: OnCircleCountDownCallback) { + mOnCircleCountDownCallback = onCircleCountDownCallback + } + + interface OnCircleCountDownCallback { + fun startCountDownCallback() + fun countDownCompleteCallback() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/ColorPickerRectangle.java b/app/src/main/java/com/abbidot/tracker/widget/ColorPickerRectangle.java new file mode 100644 index 0000000..9758d7a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/ColorPickerRectangle.java @@ -0,0 +1,196 @@ +package com.abbidot.tracker.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; + +import androidx.appcompat.widget.AppCompatImageView; + +import com.abbidot.baselibrary.util.LogUtil; +import com.abbidot.tracker.R; + + +/** + * Created by lenovo on 2019/2/15. + * desc: 选色器 + * author: lli + */ +public class ColorPickerRectangle extends AppCompatImageView implements View.OnTouchListener { + + private Bitmap bitmap; + //当前显示选中小圆半径 + private final float mSmallRadius; + private float mSelectCircleX = 0; + private float mSelectCircleY = 0; + + private Bitmap bp;//色轮图片 + private int bw, bh;//色轮图片的尺寸 + private float x, y, radio; + private OnColorSelectListener onColorSelectListener; + private final Paint paint; + + public ColorPickerRectangle(Context context) { + this(context, null); + } + + public ColorPickerRectangle(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerRectangle(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null); + + bp = BitmapFactory.decodeResource(context.getResources(), R.drawable.bg_color); + + bw = bp.getWidth(); + bh = bp.getHeight(); + + x = bw / 2f; + y = bh / 2f; + radio = Math.min(x, y); + + setImageBitmap(bp); + + setOnTouchListener(this); + + paint = new Paint(); + paint.setStrokeWidth(10); + paint.setColor(Color.RED); + paint.setAntiAlias(true); + + setClickable(true); + + bitmap = BitmapFactory.decodeResource(context.getResources(), + R.drawable.ico_color_selector); + mSmallRadius = Math.min(bitmap.getWidth() / 2f, bitmap.getHeight() / 2f); + } + + // float lastX = 0; + // float lastY = 0; + @Override + public boolean onTouch(View v, MotionEvent event) { + float xx = event.getX(); + float yy = event.getY(); + + if (!inCircle(xx, yy)) { + return false; + } + + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + //父节点不拦截子节点,解决父控件是滑动控件的冲突 + v.getParent().requestDisallowInterceptTouchEvent(true); + break; + case MotionEvent.ACTION_MOVE: + break; + case MotionEvent.ACTION_UP: + //父节点拦截子节点 + v.getParent().requestDisallowInterceptTouchEvent(false); + if (onColorSelectListener != null) { + onColorSelectListener.onColorSelect(getColor(xx, yy)); + } + break; + } + x = xx; + y = yy; + invalidate(); + + return true; + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + // x = getMeasuredWidth() / 2f; + // y = getMeasuredWidth() / 2f; + // radio = Math.min(x, y); + LogUtil.Companion.e("onMeasure"); + } + + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // canvas.drawPoint(x, y, paint); + + //第一次进来默认不显示选中的小圆圈 + if (mSelectCircleX == 0 && mSelectCircleY == 0) { + mSelectCircleX = x - mSmallRadius; + mSelectCircleY = y - mSmallRadius; + canvas.drawBitmap(bitmap, 0, 0, paint); + } else + canvas.drawBitmap(bitmap, x - mSmallRadius, y - mSmallRadius, paint); + } + + private int getColor(float x, float y) { + + this.x = x; + this.y = y; + + int w = getWidth(); + int h = getHeight(); + + int dx = (int) ((x / w) * bw); + int dy = (int) ((y / h) * bh); + + int color = Color.BLACK; + + try { + color = bp.getPixel(dx, dy); + } catch (Exception e) { + e.printStackTrace(); + } + return color; + } + + private boolean inCircle(float x, float y) { + float cx = getWidth() / 2f; + float cy = getHeight() / 2f; + float d = + (float) Math.abs(Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy))) + mSmallRadius; + return d <= radio; + } + + public interface OnColorSelectListener { + public void onColorSelect(int color); + } + + public OnColorSelectListener getOnColorSelectListener() { + return onColorSelectListener; + } + + public void setOnColorSelectListener(OnColorSelectListener onColorSelectListener) { + this.onColorSelectListener = onColorSelectListener; + } + + /** + * 回收Bitmap内存 + */ + public void recycle() { + + if (bitmap != null) { + if (!bitmap.isRecycled()) { + bitmap.recycle(); + } + bitmap = null; + } + + if (bp != null) { + if (!bp.isRecycled()) { + bp.recycle(); + } + bp = null; + } + } + +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/CreditCardView.kt b/app/src/main/java/com/abbidot/tracker/widget/CreditCardView.kt new file mode 100644 index 0000000..14a3a29 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/CreditCardView.kt @@ -0,0 +1,156 @@ +package com.abbidot.tracker.widget + +import android.animation.Animator +import android.animation.AnimatorSet +import android.animation.ObjectAnimator +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.animation.AccelerateDecelerateInterpolator +import android.widget.FrameLayout +import android.widget.RelativeLayout +import coil.load +import com.abbidot.tracker.R +import com.abbidot.tracker.databinding.ViewCreditCardLayoutBinding +import com.abbidot.tracker.util.Util + + +/** + *Created by .yzq on 2022/4/6/006. + * @link + * @description: + */ +class CreditCardView : FrameLayout { + + private lateinit var mViewBinding: ViewCreditCardLayoutBinding + + //信用卡前面的view + private lateinit var mFrontView: RelativeLayout + + //信用卡后面的view + private lateinit var mBehindView: RelativeLayout + + //信用卡类型图片 + private var mCardLogoImageId = 0 + + //动画执行时间 + private val mDuration = 350L + + constructor(context: Context) : super(context) { + init(context) + } + + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + + private fun init(context: Context) { +// val inflater = +// getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater + + val view = inflate(context, R.layout.view_credit_card_layout, this) + mViewBinding = ViewCreditCardLayoutBinding.bind(view) + + mBehindView = mViewBinding.layoutBehindCreditCard.root + mFrontView = mViewBinding.layoutFrontCreditCard.root + } + + + fun setCardNumber(numberStr: String) { + val formatCardNumber = Util.formatCardNumber(numberStr) + mCardLogoImageId = Util.getCreditCardType(numberStr) + + mViewBinding.layoutFrontCreditCard.apply { + creditCardNumberText.text = formatCardNumber + creditCardLogoImg.load(mCardLogoImageId) + } + } + + fun setCardholderName(cardholderName: String) { + mViewBinding.layoutFrontCreditCard.frontCardHolderName.text = cardholderName + } + + fun setCardExpiry(cardExpiry: String) { + mViewBinding.layoutFrontCreditCard.frontCardExpiry.text = cardExpiry + } + + fun setCVV(cvv: String) { + mViewBinding.layoutBehindCreditCard.apply { + backCardCvv.text = cvv + backCardLogoImg.load(mCardLogoImageId) + } + } + + + /** + * 翻转背面动画 + */ + fun showBehindAnimation() { + if (mBehindView.visibility == View.VISIBLE) return + val animatorStart = ObjectAnimator.ofFloat(this, "rotationY", 0f, -90f) + + val animatorEnd = ObjectAnimator.ofFloat(this, "rotationY", 90f, 0f) + animatorEnd.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) { + mFrontView.visibility = View.GONE + mBehindView.visibility = View.VISIBLE + mViewBinding.layoutBehindCreditCard.backCardLogoImg.load(mCardLogoImageId) + } + + override fun onAnimationEnd(animation: Animator) { + + } + + override fun onAnimationCancel(animation: Animator) { + } + + override fun onAnimationRepeat(animation: Animator) { + } + }) + + startAnimator(animatorStart, animatorEnd) + } + + /** + * 翻转正面动画 + */ + fun showFrontAnimation() { + if (mFrontView.visibility == View.VISIBLE) return + val animatorStart = ObjectAnimator.ofFloat(this, "rotationY", 0f, 90f) + + val animatorEnd = ObjectAnimator.ofFloat(this, "rotationY", -90f, 0f) + animatorEnd.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator) { + mFrontView.visibility = View.VISIBLE + mBehindView.visibility = View.GONE + } + + override fun onAnimationEnd(animation: Animator) { + } + + override fun onAnimationCancel(animation: Animator) { + } + + override fun onAnimationRepeat(animation: Animator) { + } + }) + + startAnimator(animatorStart, animatorEnd) + } + + private fun startAnimator(animatorStart: Animator, animatorEnd: Animator) { + val animatorSet = AnimatorSet() + animatorSet.play(animatorStart).before(animatorEnd) + animatorSet.duration = mDuration + animatorSet.interpolator = AccelerateDecelerateInterpolator() + animatorSet.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/CustomViewInterfaces.kt b/app/src/main/java/com/abbidot/tracker/widget/CustomViewInterfaces.kt new file mode 100644 index 0000000..aebfdc8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/CustomViewInterfaces.kt @@ -0,0 +1,25 @@ +package com.abbidot.tracker.widget + +/** + *Created by .yzq on 2024/1/25/025. + * @link + * @description: + */ +interface OnCircleViewScaleClickListener { + fun circleScaleClick() + fun circleStartDragClick() + fun circleEndDragClick() +} + +interface OnRectViewRotateScaleClickListener { + fun rectRotateClick() + fun rectScaleClick() + fun rectStartDragClick() + fun rectEndDragClick() +} + +interface OnPolygonViewPointPullClickListener { + fun polygonPointPullClick() + fun polygonStartDragClick() + fun polygonEndDragClick() +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/FencesCircleView.kt b/app/src/main/java/com/abbidot/tracker/widget/FencesCircleView.kt new file mode 100644 index 0000000..4cf918f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/FencesCircleView.kt @@ -0,0 +1,415 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.DashPathEffect +import android.graphics.Paint +import android.graphics.Point +import android.graphics.RectF +import android.text.TextUtils +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ImageUtil +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2022/1/22/022. + * @link + * @description:添加围栏圆形,只针对围栏 + */ +class FencesCircleView : View { + + private var mWidth = 0 + private var mHeight = 0 + + private lateinit var mPaint: Paint + private lateinit var mRectF: RectF + + private var mDashedColor = 0 + private var mFillBgColor = 0 + + //最外面的圆半径 + private var mMaxCircleRadius = 0f + + //中心坐标点 + private var mCentreX = 0f + private var mCentreY = 0f + + //内圆半径 + private lateinit var mInsideCirclePaint: Paint + + //定位图标 + private var mLocationBitmap: Bitmap? = null + private var mZoomBitmap: Bitmap? = null + + //线末尾坐标点 + private var mLineEndX = 0f + private var mLineEndY = 0f + + //是否显示距离文字 + private var isShowDistance = true + + //距离显示 + private var mDistanceText = "" + private lateinit var mTextPaint: Paint + private var isScale = false + private var isDrag = false + private var mOldX = 0f + private var mOldY = 0f + + //是否需要测量,防止百度地图使用会执行onMeasure/地图页面上下滑动重新布局执行onMeasure + private var isNeedMeasure = true + + private var mScaleClickListener: OnCircleViewScaleClickListener? = null + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + //圆的默认大小 +// mMaxCircleRadius = QMUIDisplayHelper.dpToPx(68).toFloat() + mPaint = Paint() + LogUtil.e("${javaClass.canonicalName}------------------->>>init") + mPaint.style = Paint.Style.STROKE + mPaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + //虚线长度 + val dashPathWidth = QMUIDisplayHelper.dpToPx(6).toFloat() + mPaint.pathEffect = DashPathEffect(floatArrayOf(dashPathWidth, dashPathWidth), 0f) + mPaint.color = mDashedColor + + mTextPaint = Paint() + val tf = + ViewUtil.instance.setTypeface(context, context.getString(R.string.number_din_cond_font)) + mTextPaint.typeface = tf + mTextPaint.textSize = QMUIDisplayHelper.sp2px(context, 14).toFloat() + mTextPaint.color = ContextCompat.getColor(context, R.color.black) + + mInsideCirclePaint = Paint() + mInsideCirclePaint.color = mFillBgColor + + mRectF = RectF() + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + mDashedColor = ContextCompat.getColor(context, R.color.tab_select) + mFillBgColor = ContextCompat.getColor(context, R.color.black20) + mLocationBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fences_zone_gps_svg) + mZoomBitmap = ImageUtil.getBitmapFromDrawableAndSvg( + context, R.drawable.icon_fence_circle_scale_svg + ) + return + } + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FencesCircleView) + mDashedColor = typedArray.getColor( + R.styleable.FencesCircleView_fences_circle_dashed_color, + ContextCompat.getColor(context, R.color.tab_select) + ) + mFillBgColor = typedArray.getColor( + R.styleable.FencesCircleView_fences_circle_fill_bg_color, + ContextCompat.getColor(context, R.color.black20) + ) + mLocationBitmap = ImageUtil.getBitmapFromDrawableAndSvg( + context, typedArray.getResourceId( + R.styleable.FencesCircleView_fences_circle_location_drawable, + R.drawable.icon_fences_zone_gps_svg + ) + ) + mZoomBitmap = ImageUtil.getBitmapFromDrawableAndSvg( + context, typedArray.getResourceId( + R.styleable.FencesCircleView_fences_circle_zoom_drawable, + R.drawable.icon_fence_circle_scale_svg + ) + ) + typedArray.recycle() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + LogUtil.e("${javaClass.canonicalName}:onMeasure---mWidth=$mWidth,mHeight=$mHeight") + if (isNeedMeasure) { + mCentreX = mWidth / 2f + mCentreY = mHeight / 2f + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + LogUtil.e("onAttachedToWindow,${javaClass.canonicalName}视图显示") + } + + override fun onDraw(canvas: Canvas) { + LogUtil.d("${javaClass.canonicalName}------>onDraw") + super.onDraw(canvas) + drawCircle(canvas) + } + + private fun drawCircle(canvas: Canvas) { + mLineEndX = mCentreX + mMaxCircleRadius + mLineEndY = mCentreY + canvas.drawCircle(mCentreX, mCentreY, mMaxCircleRadius, mPaint) + canvas.drawCircle( + mCentreX, mCentreY, mMaxCircleRadius - mPaint.strokeWidth / 2f, mInsideCirclePaint + ) + mRectF.set( + mCentreX - mMaxCircleRadius, + mCentreY - mMaxCircleRadius, + mLineEndX, + mCentreY + mMaxCircleRadius + ) + + val imageHalfWidth = mLocationBitmap!!.width / 2f + val imageHalfHeight = mLocationBitmap!!.height / 2f + val left = mCentreX - imageHalfWidth + val top = mCentreY - imageHalfHeight + canvas.drawBitmap(mLocationBitmap!!, left, top, null) + + if (isShowDistance) { + if (!TextUtils.isEmpty(mDistanceText)) { + +// canvas.drawLine(mCentreX + imageHalfWidth, mCentreY, mLineEndX, mLineEndY, mPaint) + + //文字宽度 + val textWidth = mTextPaint.measureText(mDistanceText) + val fontMetrics = mTextPaint.fontMetrics + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top +// val x = if (mMaxCircleRadius > textWidth * 5) { +// mLineEndX - mMaxCircleRadius / 2 - textWidth / 2 +// } else { +// mCentreX + textWidth / 3f +// } +// canvas.drawText(mDistanceText, x, mCentreY - textHeight * 2, mTextPaint) + + val x = if (mCentreX - mMaxCircleRadius > textWidth + textHeight / 2) { + mCentreX - mMaxCircleRadius - textWidth - textHeight / 2 + } else { + mCentreX - mMaxCircleRadius + textHeight / 2 + } + canvas.drawText(mDistanceText, x, mCentreY + textHeight / 4, mTextPaint) + } + + mZoomBitmap?.apply { + canvas.drawBitmap(this, mLineEndX - width / 2, mLineEndY - height / 2, null) + } + } + } + + fun setOnCircleViewScaleClickListener(onCircleViewScaleClickListener: OnCircleViewScaleClickListener) { + mScaleClickListener = onCircleViewScaleClickListener + } + + /** + * 设置圆的半径 单位dp + */ + private fun setCircleRadius(dp: Int) { + mMaxCircleRadius = QMUIDisplayHelper.dpToPx(dp).toFloat() + invalidate() + } + + /** + * 设置圆的半径 单位px + */ + fun setCircleRadiusPx(px: Float) { + mMaxCircleRadius = px + invalidate() + } + + /** + * 设置默认的半径 + */ + fun initDefaultRadius(dp: Int = 68) { + if (dp <= 0) return + setCircleRadius(dp) + } + + fun setCentreXY(point: Point) { + point.apply { + if (mCentreX != x.toFloat() || mCentreY != y.toFloat()) { + mCentreX = x.toFloat() + mCentreY = y.toFloat() + isNeedMeasure = false + invalidate() + } + } + } + +// private fun getCentreX() = mCentreX +// private fun getCentreY() = mCentreY +// +// private fun getLineEndX() = mLineEndX +// private fun getLineEndY() = mLineEndY + + /** + * 获取圆心点 + */ + fun getCircleCentrePoint() = Point( + mCentreX.toInt(), mCentreY.toInt() + ) + + /** + * 获取圆形直虚线的最后一个点 + */ + fun getCircleLineEndPoint() = Point( + mLineEndX.toInt(), mLineEndY.toInt() + ) + + /** + * 获取圆形直虚线的左边最开始的一个点 + */ + fun getCircleLineStartPoint() = Point( + (mLineEndX - mMaxCircleRadius * 2).toInt(), mLineEndY.toInt() + ) + + /** + * 设置显示的距离 + */ + fun setShowDisDistance(distance: String) { + mDistanceText = distance + invalidate() + } + + /** + * 设置隐藏显示距离文字 + */ + fun setShowHideDisDistance(show: Boolean) { + if (isShowDistance != show) { + isShowDistance = show + invalidate() + } + } + + /** + * 设置边框颜色和图片 + */ + fun setColorAndImage( + context: Context, + dashedColorRes: Int = 0, + fillBgColorRes: Int = 0, + locationDrawableRes: Int = 0, + zoomDrawableRes: Int = 0 + ) { + var isRefresh = false + if (dashedColorRes > 0) { + mDashedColor = ContextCompat.getColor(context, dashedColorRes) + mPaint.color = mDashedColor + isRefresh = true + } + if (fillBgColorRes > 0) { + mFillBgColor = ContextCompat.getColor(context, fillBgColorRes) + mInsideCirclePaint.color = mFillBgColor + isRefresh = true + } + if (locationDrawableRes > 0) { + mLocationBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, locationDrawableRes) + isRefresh = true + } + if (zoomDrawableRes > 0) { + mZoomBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, zoomDrawableRes) + isRefresh = true + } + if (isRefresh) invalidate() + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + when (event?.action) { + MotionEvent.ACTION_DOWN -> { + val x = event.x + val y = event.y + mZoomBitmap?.apply { + if (x in mLineEndX - width / 2..mLineEndX + width / 2 && y in mLineEndY - height / 2..mLineEndY + height / 2) { + isScale = true + mOldX = x + return true + } else if (mRectF.contains(x, y)) { + isDrag = true + mOldX = x + mOldY = y + mPaint.strokeWidth = QMUIDisplayHelper.dpToPx(3).toFloat() + mScaleClickListener?.circleStartDragClick() + invalidate() + return true + } + } + } + + MotionEvent.ACTION_MOVE -> { + val x = event.x + val y = event.y + if (isScale) { + val transformX = x - mOldX + //限制缩放不能太小 + if (mMaxCircleRadius + transformX < mLocationBitmap!!.height / 2f) { + return true + } + mMaxCircleRadius += transformX + mOldX = x + invalidate() + return true + } else if (isDrag) { + val transformX = x - mOldX + val transformY = y - mOldY + mCentreX += transformX + mCentreY += transformY + mOldX = x + mOldY = y + invalidate() + return true + } + } + + MotionEvent.ACTION_UP -> { + if (isScale) { + mScaleClickListener?.circleScaleClick() + isScale = false + } else if (isDrag) { + mScaleClickListener?.circleEndDragClick() + isDrag = false + mPaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + invalidate() + } + } + } + return super.onTouchEvent(event) + } + + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + LogUtil.e("${javaClass.canonicalName}视图销毁") + if (null != mLocationBitmap) { + mLocationBitmap?.recycle() + mLocationBitmap = null + } + if (null != mZoomBitmap) { + mZoomBitmap?.recycle() + mZoomBitmap = null + } + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt b/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt new file mode 100644 index 0000000..366aa4e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/FencesPolygonView.kt @@ -0,0 +1,856 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.DashPathEffect +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Point +import android.graphics.RectF +import android.graphics.Region +import android.text.TextUtils +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import androidx.core.content.ContextCompat +import androidx.core.graphics.toRect +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.bean.PointBean +import com.abbidot.tracker.util.ImageUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2022/1/22/022. + * @link 按以下点组成的多边形 + * A + * F B + * E C + * D + * @description:添加围栏多边形,只针对围栏 + */ +class FencesPolygonView : View { + + companion object { + const val POINT_A = 1 + const val POINT_B = 2 + const val POINT_C = 3 + const val POINT_D = 4 + const val POINT_E = 5 + const val POINT_F = 6 + } + + private var mWidth = 0 + private var mHeight = 0 + + //中心坐标点 + private var mCentreX = 0f + private var mCentreY = 0f + private var mAPoint = PointBean() + private var mFPoint = PointBean() + private var mEPoint = PointBean() + private var mDPoint = PointBean() + private var mCPoint = PointBean() + private var mBPoint = PointBean() + private var mTempPoint = PointBean() + + //最外层组成的隐藏的矩形 + private var mOutHideRectWidth = 0f + private var mOutHideRectHeight = 0f + + //定位图标 + private var mLocationBitmap: Bitmap? = null + private var mDotBitmap: Bitmap? = null + + private lateinit var mDashedPaint: Paint + private lateinit var mFillPaint: Paint + + private lateinit var mPath: Path + + private var mDashedColor = 0 + private var mFillBgColor = 0 + + private var mTouchPointTag = "" + private val mTageA = "A" + private val mTageB = "B" + private val mTageC = "C" + private val mTageD = "D" + private val mTageE = "E" + private val mTageF = "F" + + private var isDrag = false + private var mOldX = 0f + private var mOldY = 0f + + //创建矩形的区域, https://www.jianshu.com/p/6a5d8f0f68d4 + private lateinit var mRectRegion: Region + + //创建路径Path区域,最后获得和矩形的交集 + private lateinit var mPathRegion: Region + private lateinit var mPathRectF: RectF + + //是否需要测量,防止百度地图使用会执行onMeasure/地图页面上下滑动重新布局执行onMeasure + private var isNeedMeasure = true + + private var mPointPullClickListener: OnPolygonViewPointPullClickListener? = null + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mPath = Path() + + mRectRegion = Region() + mPathRegion = Region() + mPathRectF = RectF() + + mFillPaint = Paint() + mFillPaint.color = mFillBgColor + + mDashedPaint = Paint() + mDashedPaint.style = Paint.Style.STROKE + mDashedPaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + //虚线长度 + val dashPathWidth = QMUIDisplayHelper.dpToPx(6).toFloat() + mDashedPaint.pathEffect = DashPathEffect(floatArrayOf(dashPathWidth, dashPathWidth), 0f) + mDashedPaint.color = mDashedColor + + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { +// if (null == attrs) { +// return +// } + mDashedColor = ContextCompat.getColor(context, R.color.select_color) + mFillBgColor = ContextCompat.getColor(context, R.color.select_color4) + mLocationBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fences_zone_gps_svg) + mDotBitmap = ImageUtil.getBitmapFromDrawableAndSvg( + context, R.drawable.icon_fence_polygon_operate_svg + ) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + LogUtil.e("${javaClass.canonicalName}:onMeasure---mWidth=$mWidth,mHeight=$mHeight") + + if (isNeedMeasure) { + mCentreX = mWidth / 2f + mCentreY = mHeight / 2f + + initPoint() + } + } + + private fun initPoint() { + if (mOutHideRectHeight <= 0 || mOutHideRectWidth <= 0) return + mAPoint.x = mCentreX + mAPoint.y = mCentreY - mOutHideRectHeight / 2 + mFPoint.x = mCentreX - mOutHideRectWidth / 2 + mFPoint.y = mCentreY - mOutHideRectHeight / 4 + mEPoint.x = mFPoint.x + mEPoint.y = mCentreY + mOutHideRectHeight / 4 + mDPoint.x = mAPoint.x + mDPoint.y = mCentreY + mOutHideRectHeight / 2 + mCPoint.x = mCentreX + mOutHideRectWidth / 2 + mCPoint.y = mEPoint.y + mBPoint.x = mCPoint.x + mBPoint.y = mFPoint.y + + getCentreXY() + } + + private fun getCentreXY() { + val minX = minOf(mAPoint.x, mFPoint.x, mEPoint.x, mDPoint.x, mCPoint.x, mBPoint.x) + val maxX = maxOf(mAPoint.x, mFPoint.x, mEPoint.x, mDPoint.x, mCPoint.x, mBPoint.x) + val minY = minOf(mAPoint.y, mFPoint.y, mEPoint.y, mDPoint.y, mCPoint.y, mBPoint.y) + val maxY = maxOf(mAPoint.y, mFPoint.y, mEPoint.y, mDPoint.y, mCPoint.y, mBPoint.y) + val rectF = RectF(minX, minY, maxX, maxY) + + //中心点改变时 + mCentreX = rectF.centerX() + mCentreY = rectF.centerY() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + LogUtil.e("onAttachedToWindow,${javaClass.canonicalName}视图显示") + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + getCentreXY() + canvas.let { + mLocationBitmap?.apply { + it.drawBitmap(this, mCentreX - width / 2, mCentreY - height / 2, null) + } + mPath.apply { + moveTo(mAPoint.x, mAPoint.y) + lineTo(mFPoint.x, mFPoint.y) + lineTo(mEPoint.x, mEPoint.y) + lineTo(mDPoint.x, mDPoint.y) + lineTo(mCPoint.x, mCPoint.y) + lineTo(mBPoint.x, mBPoint.y) + close() + + //计算控制点的边界 + mPath.computeBounds(mPathRectF, true) + //设置区域路径 + mRectRegion.set(mPathRectF.toRect()) + //设置剪辑描述的区域 + mPathRegion.setPath(mPath, mRectRegion) + + it.drawPath(mPath, mFillPaint) + it.drawPath(mPath, mDashedPaint) + reset() + } + mDotBitmap?.apply { + it.drawBitmap(this, mAPoint.x - width / 2, mAPoint.y - height / 2, null) + it.drawBitmap(this, mFPoint.x - width / 2, mFPoint.y - height / 2, null) + it.drawBitmap(this, mEPoint.x - width / 2, mEPoint.y - height / 2, null) + it.drawBitmap(this, mDPoint.x - width / 2, mDPoint.y - height / 2, null) + it.drawBitmap(this, mCPoint.x - width / 2, mCPoint.y - height / 2, null) + it.drawBitmap(this, mBPoint.x - width / 2, mBPoint.y - height / 2, null) + } + + } + } + + @SuppressLint("ClickableViewAccessibility") + @Synchronized + override fun onTouchEvent(event: MotionEvent?): Boolean { + when (event?.action) { + MotionEvent.ACTION_DOWN -> { + val x = event.x + val y = event.y + mTouchPointTag = "" + if (isClickWherePoint(x, y, mAPoint)) { + mTouchPointTag = mTageA + } else if (isClickWherePoint(x, y, mFPoint)) { + mTouchPointTag = mTageB + } else if (isClickWherePoint(x, y, mEPoint)) { + mTouchPointTag = mTageC + } else if (isClickWherePoint(x, y, mDPoint)) { + mTouchPointTag = mTageD + } else if (isClickWherePoint(x, y, mCPoint)) { + mTouchPointTag = mTageE + } else if (isClickWherePoint(x, y, mBPoint)) { + mTouchPointTag = mTageF + } + if (!TextUtils.isEmpty(mTouchPointTag)) { + return true + } + if (mPathRegion.contains(x.toInt(), y.toInt())) { + isDrag = true + mOldX = x + mOldY = y + mDashedPaint.strokeWidth = QMUIDisplayHelper.dpToPx(3).toFloat() + mPointPullClickListener?.polygonStartDragClick() + invalidate() + return true + } + } + + MotionEvent.ACTION_MOVE -> { + val x = event.x + val y = event.y + mTempPoint.x = x + mTempPoint.y = y + var isInvalidate = false + when (mTouchPointTag) { + mTageA -> { + val intersectTBEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBEF$intersectTBEF") + if (intersectTBEF) { + return false + } + val intersectTBDE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBDE$intersectTBDE") + if (intersectTBDE) { + return false + } + val intersectTBCD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBCD$intersectTBCD") + if (intersectTBCD) { + return false + } + val intersectTBBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBBC$intersectTBBC") + if (intersectTBBC) { + return false + } + + val intersectTFBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFBC$intersectTFBC") + if (intersectTFBC) { + return false + } + val intersectTFCD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFCD$intersectTFCD") + if (intersectTFCD) { + return false + } + val intersectTFDE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFDE$intersectTFDE") + if (intersectTFDE) { + return false + } + val intersectTFEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFEF$intersectTFEF") + if (intersectTFEF) { + return false + } + + mAPoint.x = x + mAPoint.y = y + isInvalidate = true + } + + mTageB -> { + val intersectTCAF = + isSegmentIntersect(mAPoint, mBPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCAF$intersectTCAF") + if (intersectTCAF) { + return false + } + val intersectTCEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCEF$intersectTCEF") + if (intersectTCEF) { + return false + } + val intersectTCDE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCDE$intersectTCDE") + if (intersectTCDE) { + return false + } + val intersectTCCD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCCD$intersectTCCD") + if (intersectTCCD) { + return false + } + + + val intersectTACD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTACD$intersectTACD") + if (intersectTACD) { + return false + } + val intersectTADE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTADE$intersectTADE") + if (intersectTADE) { + return false + } + val intersectTAEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTAEF$intersectTAEF") + if (intersectTAEF) { + return false + } + val intersectTAFA = + isSegmentIntersect(mBPoint, mAPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTAFA$intersectTAFA") + if (intersectTAFA) { + return false + } + mFPoint.x = x + mFPoint.y = y + isInvalidate = true + } + + mTageC -> { + val intersectTBDE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBDE$intersectTBDE") + if (intersectTBDE) { + return false + } + val intersectTBEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBEF$intersectTBEF") + if (intersectTBEF) { + return false + } + val intersectTBFA = + isSegmentIntersect(mBPoint, mAPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBFA$intersectTBFA") + if (intersectTBFA) { + return false + } + val intersectTBAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mFPoint) + LogUtil.i("交点intersectTBAB$intersectTBAB") + if (intersectTBAB) { + return false + } + + val intersectTDAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDAB$intersectTDAB") + if (intersectTDAB) { + return false + } + val intersectTDAF = + isSegmentIntersect(mAPoint, mBPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDAF$intersectTDAF") + if (intersectTDAF) { + return false + } + val intersectTDEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDEF$intersectTDEF") + if (intersectTDEF) { + return false + } + val intersectTDED = + isSegmentIntersect(mCPoint, mDPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDED$intersectTDED") + if (intersectTDED) { + return false + } + mEPoint.x = x + mEPoint.y = y + isInvalidate = true + } + + mTageD -> { + val intersectTCEF = + isSegmentIntersect(mCPoint, mBPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCEF$intersectTCEF") + if (intersectTCEF) { + return false + } + val intersectTCAF = + isSegmentIntersect(mAPoint, mBPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCAF$intersectTCAF") + if (intersectTCAF) { + return false + } + val intersectTCAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCAB$intersectTCAB") + if (intersectTCAB) { + return false + } + val intersectTCBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mEPoint) + LogUtil.i("交点intersectTCBC$intersectTCBC") + if (intersectTCBC) { + return false + } + + val intersectTEBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEBC$intersectTEBC") + if (intersectTEBC) { + return false + } + val intersectTEAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEAB$intersectTEAB") + if (intersectTEAB) { + return false + } + val intersectTEAF = + isSegmentIntersect(mAPoint, mBPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEAF$intersectTEAF") + if (intersectTEAF) { + return false + } + val intersectTEFE = + isSegmentIntersect(mBPoint, mCPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEFE$intersectTEFE") + if (intersectTEFE) { + return false + } + mDPoint.x = x + mDPoint.y = y + isInvalidate = true + } + + mTageE -> { + val intersectTDAF = + isSegmentIntersect(mAPoint, mBPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDAF$intersectTDAF") + if (intersectTDAF) { + return false + } + val intersectTDAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDAB$intersectTDAB") + if (intersectTDAB) { + return false + } + val intersectTDBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDBC$intersectTDBC") + if (intersectTDBC) { + return false + } + val intersectTDCD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mDPoint) + LogUtil.i("交点intersectTDCD$intersectTDCD") + if (intersectTDCD) { + return false + } + + val intersectTFCD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFCD$intersectTFCD") + if (intersectTFCD) { + return false + } + val intersectTFBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFBC$intersectTFBC") + if (intersectTFBC) { + return false + } + val intersectTFAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFAB$intersectTFAB") + if (intersectTFAB) { + return false + } + val intersectTFAF = + isSegmentIntersect(mAPoint, mBPoint, mTempPoint, mBPoint) + LogUtil.i("交点intersectTFAF$intersectTFAF") + if (intersectTFAF) { + return false + } + mCPoint.x = x + mCPoint.y = y + isInvalidate = true + } + + mTageF -> { + val intersectTEAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEAB$intersectTEAB") + if (intersectTEAB) { + return false + } + val intersectTEBC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEBC$intersectTEBC") + if (intersectTEBC) { + return false + } + val intersectTECD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTECD$intersectTECD") + if (intersectTECD) { + return false + } + val intersectTEDE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mCPoint) + LogUtil.i("交点intersectTEDE$intersectTEDE") + if (intersectTEDE) { + return false + } + + val intersectTADE = + isSegmentIntersect(mDPoint, mCPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTADE$intersectTADE") + if (intersectTADE) { + return false + } + val intersectTACD = + isSegmentIntersect(mEPoint, mDPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTACD$intersectTACD") + if (intersectTACD) { + return false + } + val intersectTABC = + isSegmentIntersect(mFPoint, mEPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTABC$intersectTABC") + if (intersectTABC) { + return false + } + val intersectTAAB = + isSegmentIntersect(mAPoint, mFPoint, mTempPoint, mAPoint) + LogUtil.i("交点intersectTAAB$intersectTAAB") + if (intersectTAAB) { + return false + } + mBPoint.x = x + mBPoint.y = y + isInvalidate = true + } + } + if (isInvalidate) { + invalidate() + return true + } else if (isDrag) { + val transformX = x - mOldX + val transformY = y - mOldY + mCentreX += transformX + mCentreY += transformY + mAPoint.x += transformX + mAPoint.y += transformY + mBPoint.x += transformX + mBPoint.y += transformY + mCPoint.x += transformX + mCPoint.y += transformY + mDPoint.x += transformX + mDPoint.y += transformY + mEPoint.x += transformX + mEPoint.y += transformY + mFPoint.x += transformX + mFPoint.y += transformY + mOldX = x + mOldY = y + invalidate() + return true + } + } + + MotionEvent.ACTION_UP -> { + if (isDrag) { + mPointPullClickListener?.polygonEndDragClick() + isDrag = false + mDashedPaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + invalidate() + } else { + mPointPullClickListener?.polygonPointPullClick() + } + } + } + return super.onTouchEvent(event) + } + + private fun crossProduct(p1: PointBean, p2: PointBean, p3: PointBean): Float { + val x1 = p2.x - p1.x + val y1 = p2.y - p1.y + val x2 = p3.x - p1.x + val y2 = p3.y - p1.y + return x1 * y2 - x2 * y1 + } + + /** + * 判断两条线段是否相交 + * https://zhuanlan.zhihu.com/p/644689588 + */ + private fun isSegmentIntersect( + pointBean1: PointBean, pointBean2: PointBean, pointBean3: PointBean, pointBean4: PointBean + ): Boolean { + val d1 = crossProduct(pointBean1, pointBean2, pointBean3) + val d2 = crossProduct(pointBean1, pointBean2, pointBean4) + val d3 = crossProduct(pointBean3, pointBean4, pointBean1) + val d4 = crossProduct(pointBean3, pointBean4, pointBean2) + + return d1 * d2 < 0 && d3 * d4 < 0 + } + + /** + * 是否点击了某个点 + */ + private fun isClickWherePoint(downX: Float, downY: Float, pointBean: PointBean): Boolean { + mDotBitmap?.apply { + if (downX in pointBean.x - width..pointBean.x + width && downY in pointBean.y - height..pointBean.y + height) { + return true + } + return false + } + return false + } + + fun setOnPolygonViewPointPullClickListener(pointPullClickListener: OnPolygonViewPointPullClickListener) { + mPointPullClickListener = pointPullClickListener + } + + /** + * 设置默认的大小 + */ + fun initDefaultWidthHeightSize(widthDp: Int = 120, heightDp: Int = 120) { + mOutHideRectWidth = QMUIDisplayHelper.dpToPx(widthDp).toFloat() + mOutHideRectHeight = QMUIDisplayHelper.dpToPx(heightDp).toFloat() + initPoint() + invalidate() + } + + /** + * 设置边框颜色和图片 + */ + fun setColorAndImage( + context: Context, + dashedColorRes: Int = 0, + fillBgColorRes: Int = 0, + locationDrawableRes: Int = 0, + dotDrawableRes: Int = 0 + ) { + var isRefresh = false + if (dashedColorRes > 0) { + mDashedColor = ContextCompat.getColor(context, dashedColorRes) + mDashedPaint.color = mDashedColor + isRefresh = true + } + if (fillBgColorRes > 0) { + mFillBgColor = ContextCompat.getColor(context, fillBgColorRes) + mFillPaint.color = mFillBgColor + isRefresh = true + } + if (locationDrawableRes > 0) { + mLocationBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, locationDrawableRes) + isRefresh = true + } + if (dotDrawableRes > 0) { + mDotBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, dotDrawableRes) + isRefresh = true + } + if (isRefresh) invalidate() + } + + /** + * 获取中心点 + */ + fun getPolygonCentrePoint() = Point(mCentreX.toInt(), mCentreY.toInt()) + + /** + * 重新设置中心点 + */ + fun setCentreXY(point: Point) { + point.let { + val diffValueX = it.x - mCentreX + val diffValueY = it.y - mCentreY + + mCentreX = it.x.toFloat() + mCentreY = it.y.toFloat() + + mAPoint.apply { + x += diffValueX + y += diffValueY + } + mBPoint.apply { + x += diffValueX + y += diffValueY + } + mCPoint.apply { + x += diffValueX + y += diffValueY + } + mDPoint.apply { + x += diffValueX + y += diffValueY + } + mEPoint.apply { + x += diffValueX + y += diffValueY + } + mFPoint.apply { + x += diffValueX + y += diffValueY + } + isNeedMeasure = false + invalidate() + } + } + + + fun setPolygonABCDEFPoint( + pointCentre: Point, + pointA: Point, + pointB: Point, + pointC: Point, + pointD: Point, + pointE: Point, + pointF: Point + ) { + mAPoint.apply { + x = pointA.x.toFloat() + y = pointA.y.toFloat() + } + mBPoint.apply { + x = pointB.x.toFloat() + y = pointB.y.toFloat() + } + mCPoint.apply { + x = pointC.x.toFloat() + y = pointC.y.toFloat() + } + mDPoint.apply { + x = pointD.x.toFloat() + y = pointD.y.toFloat() + } + mEPoint.apply { + x = pointE.x.toFloat() + y = pointE.y.toFloat() + } + mFPoint.apply { + x = pointF.x.toFloat() + y = pointF.y.toFloat() + } + mCentreX = pointCentre.x.toFloat() + mCentreY = pointCentre.y.toFloat() + invalidate() + } + + /** + * 获取多边形的某一个点 + */ + fun getPolygonABCDEFPoint(wherePoint: Int): Point { + LogUtil.e("getPolygonABCDEFPoint,$mAPoint,$mCentreX") + var point = PointBean() + when (wherePoint) { + POINT_A -> point = mAPoint + POINT_B -> point = mBPoint + POINT_C -> point = mCPoint + POINT_D -> point = mDPoint + POINT_E -> point = mEPoint + POINT_F -> point = mFPoint + } + return Point(point.x.toInt(), point.y.toInt()) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + LogUtil.e("${javaClass.canonicalName}视图销毁") + if (null != mLocationBitmap) { + mLocationBitmap?.recycle() + mLocationBitmap = null + } + if (null != mDotBitmap) { + mDotBitmap?.recycle() + mDotBitmap = null + } + } + +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/FencesRectView.kt b/app/src/main/java/com/abbidot/tracker/widget/FencesRectView.kt new file mode 100644 index 0000000..88bddfe --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/FencesRectView.kt @@ -0,0 +1,738 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.* +import android.text.TextUtils +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ImageUtil +import com.abbidot.tracker.util.ViewUtil +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_A +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_B +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_C +import com.abbidot.tracker.widget.FencesPolygonView.Companion.POINT_D +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.pow +import kotlin.math.sin +import kotlin.math.sqrt + +/** + *Created by .yzq on 2022/1/22/022. + * @link + * @description:添加围栏矩形,只针对围栏 + * + * 按以下点组成的矩形 + * A B + * D C + */ +class FencesRectView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private lateinit var mStrokePaint: Paint + private lateinit var mFillPaint: Paint + + private lateinit var mRectF: RectF + + //矩形的宽高 + private var mRectWidth = 0f + private var mRectHeight = 0f + private var mRectLeft = 0f + private var mRectRight = 0f + private var mRectTop = 0f + private var mRectBottom = 0f + + //中心坐标点 + private var mCentreX = 0f + private var mCentreY = 0f + + //定位图标 + private var mLocationBitmap: Bitmap? = null + private var mDotBitmap: Bitmap? = null + private var mZoomBitmap: Bitmap? = null + private var mRotateBitmap: Bitmap? = null + + private var mDashedColor = 0 + private var mFillBgColor = 0 + + private var isRotate = false + private var isScale = false + private var isDrag = false + private var mOldX = 0f + private var mOldY = 0f + + //是否显示距离 + private var isShowDistance = true + private var mRectWidthDistanceText = "" + private var mRectHeightDistanceText = "" + private lateinit var mTextPaint: Paint + + //旋转的角度(真实的角度单位) + private var mRotateAngle = 0.0 + + //计算画布移动后,点击旋转图片按钮一开始的坐标角度,经过平移坐标轴算出 + private var mStartRotateAngle = 0.0 + + //是否需要测量,防止百度地图使用会执行onMeasure/地图页面上下滑动重新布局执行onMeasure + private var isNeedMeasure = true + + private var mRotateScaleClickListener: OnRectViewRotateScaleClickListener? = null + + constructor(context: Context) : super(context) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + private fun init(context: Context) { + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context +// mRectWidth = QMUIDisplayHelper.dpToPx(100).toFloat() +// mRectHeight = QMUIDisplayHelper.dpToPx(100).toFloat() + + mStrokePaint = Paint() + LogUtil.e("${javaClass.canonicalName}------------------->>>init") + mStrokePaint.style = Paint.Style.STROKE + mStrokePaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + //虚线长度 + val dashPathWidth = QMUIDisplayHelper.dpToPx(6).toFloat() + mStrokePaint.pathEffect = DashPathEffect(floatArrayOf(dashPathWidth, dashPathWidth), 0f) + mStrokePaint.color = ContextCompat.getColor(mContext, R.color.select_color) + + mFillPaint = Paint() + mFillPaint.style = Paint.Style.FILL + mFillPaint.color = ContextCompat.getColor(mContext, R.color.select_color4) + + mTextPaint = Paint() + val tf = + ViewUtil.instance.setTypeface(context, context.getString(R.string.number_din_cond_font)) + mTextPaint.typeface = tf + mTextPaint.textSize = QMUIDisplayHelper.sp2px(context, 14).toFloat() + mTextPaint.color = ContextCompat.getColor(mContext, R.color.black) + + mDotBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_dot_svg) + mLocationBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fences_zone_gps_svg) + mZoomBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_rect_zoom_svg) + mRotateBitmap = + ImageUtil.getBitmapFromDrawableAndSvg(context, R.drawable.icon_fence_rect_rotate_svg) + + mRectF = RectF() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + LogUtil.e("${javaClass.canonicalName}:onMeasure---mWidth=$mWidth,mHeight=$mHeight") + if (isNeedMeasure) { + mCentreX = mWidth / 2f + mCentreY = mHeight / 2f + } + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + LogUtil.e("onAttachedToWindow,${javaClass.canonicalName}视图显示") + } + + override fun onDraw(canvas: Canvas) { + LogUtil.e("${javaClass.canonicalName}------>onDraw") + super.onDraw(canvas) + + canvas.apply { + save() + //只为旋转才移动坐标轴 + translate(mCentreX, mCentreY) + rotate(mRotateAngle.toFloat()) + startDraw(this) + restore() + // val a = getRectABCDXYPoint(FencesRectView.POINT_A) + // drawCircle(a[0], a[1], 50f, mTextPaint) + // val b = getRectABCDXYPoint(FencesRectView.POINT_B) + // drawCircle(b[0], b[1], 50f, mTextPaint) + // val c = getRectABCDXYPoint(FencesRectView.POINT_C) + // drawCircle(c[0], c[1], 50f, mTextPaint) + // val d = getRectABCDXYPoint(FencesRectView.POINT_D) + // drawCircle(d[0], d[1], 50f, mTextPaint) + } + } + + private fun startDraw(canvas: Canvas) { + LogUtil.e("startDraw:mRotateAngle=$mRotateAngle") + + if (mRectWidth == 0f && mRectHeight == 0f) return + + mRectLeft = -mRectWidth / 2 + mRectTop = -mRectHeight / 2 + mRectRight = mRectLeft + mRectWidth + mRectBottom = mRectTop + mRectHeight + + mLocationBitmap?.apply { + val locationImageHalfWidth = width / 2f + val locationImageHalfHeight = height / 2f + val left = -locationImageHalfWidth + val top = -locationImageHalfHeight + canvas.drawBitmap(this, left, top, null) + } + + mRectF.apply { + left = mRectLeft + top = mRectTop + right = mRectRight + bottom = mRectBottom + } + canvas.drawRect(mRectF, mFillPaint) + canvas.drawRect(mRectF, mStrokePaint) + + mDotBitmap?.apply { + val dotImageHalfWidth = width / 2f + val dotImageHalfHeight = height / 2f + canvas.drawBitmap( + this, mRectLeft - dotImageHalfWidth, mRectTop - dotImageHalfHeight, null + ) + canvas.drawBitmap( + this, mRectRight - dotImageHalfWidth, mRectTop - dotImageHalfHeight, null + ) + canvas.drawBitmap( + this, mRectLeft - dotImageHalfWidth, mRectBottom - dotImageHalfHeight, null + ) + if (!isShowDistance) { + canvas.drawBitmap( + this, mRectRight - dotImageHalfWidth, mRectBottom - dotImageHalfHeight, null + ) + } + } + + + if (isShowDistance) { + + mRotateBitmap?.apply { + val startX = mRectLeft + mRectWidth / 2 + canvas.drawLine( + startX, + mRectTop, + startX, + mRectTop - QMUIDisplayHelper.dpToPx(3).toFloat(), + mStrokePaint + ) + canvas.drawBitmap( + this, + startX - width / 2, + mRectTop - height - QMUIDisplayHelper.dpToPx(2).toFloat(), + null + ) + mStartRotateAngle = atan2(mRectTop - height, startX - width / 2) / Math.PI * 180 + } + mZoomBitmap?.apply { + canvas.drawBitmap( + this, mRectRight - width / 2, mRectBottom - height / 2, null + ) + } + + canvasDistanceText(canvas) + } + } + + private fun canvasDistanceText(canvas: Canvas) { + val fontMetrics = mTextPaint.fontMetrics + if (!TextUtils.isEmpty(mRectWidthDistanceText)) { + //文字宽度 + val textWidth = mTextPaint.measureText(mRectWidthDistanceText) + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top + + val y = if (mHeight / 2 - mRectHeight / 2 > textHeight) { + mRectHeight / 2 + textHeight + } else { + mHeight / 2 - textHeight / 6 + } + canvas.drawText( + mRectWidthDistanceText, -(textWidth / 2), y, mTextPaint + ) + } + + if (!TextUtils.isEmpty(mRectHeightDistanceText)) { + //文字宽度 + val textWidth = mTextPaint.measureText(mRectHeightDistanceText) + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top + + val x = if (mWidth / 2 - mRectWidth / 2 > textWidth) { + mRectWidth / 2 + (textHeight / 6) + } else { + mWidth / 2 - textWidth - textHeight / 6 + } + canvas.drawText( + mRectHeightDistanceText, x, (textHeight / 2), mTextPaint + ) + } + } + + fun setOnRectViewRotateScaleClickListener(onRectViewRotateScaleClickListener: OnRectViewRotateScaleClickListener) { + mRotateScaleClickListener = onRectViewRotateScaleClickListener + } + + /** + * 设置默认长宽大小,初始化使用 + */ + fun initDefaultWidthHeight(width: Int = 100, height: Int = 100) { + if (width <= 0 && height <= 0) return + mRectWidth = QMUIDisplayHelper.dpToPx(width).toFloat() + mRectHeight = QMUIDisplayHelper.dpToPx(height).toFloat() + mRotateAngle = 0.0 + postInvalidate() + } + + /** + * 设置显示的距离 + */ + fun setShowDisDistance(widthDistance: String, heightDistance: String) { + mRectWidthDistanceText = widthDistance + mRectHeightDistanceText = heightDistance + postInvalidate() + } + + /** + * 设置隐藏显示距离文字 + */ + fun setShowHideDisDistance(show: Boolean) { + if (isShowDistance != show) { + isShowDistance = show + postInvalidate() + } + } + + /** + * 设置边框颜色和图片 + */ + fun setColorAndImage( + context: Context, + dashedColorRes: Int = 0, + fillBgColorRes: Int = 0, + locationDrawableRes: Int = 0, + zoomDrawableRes: Int = 0, + dotDrawableRes: Int = 0, + rotateDrawableRes: Int = 0 + ) { + var isRefresh = false + if (dashedColorRes > 0) { + mDashedColor = ContextCompat.getColor(context, dashedColorRes) + mStrokePaint.color = mDashedColor + isRefresh = true + } + if (fillBgColorRes > 0) { + mFillBgColor = ContextCompat.getColor(context, fillBgColorRes) + mFillPaint.color = mFillBgColor + isRefresh = true + } + + if (locationDrawableRes > 0) { + mLocationBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, locationDrawableRes) + isRefresh = true + } + if (zoomDrawableRes > 0) { + mZoomBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, zoomDrawableRes) + isRefresh = true + } + if (rotateDrawableRes > 0) { + mRotateBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, rotateDrawableRes) + isRefresh = true + } + if (dotDrawableRes > 0) { + mDotBitmap = ImageUtil.getBitmapFromDrawableAndSvg(context, dotDrawableRes) + isRefresh = true + } + if (isRefresh) invalidate() + } + + private fun getCentreX() = mCentreX + private fun getCentreY() = mCentreY + + /** + * 获取中心点 + */ + fun getRectCentrePoint() = Point(mCentreX.toInt(), mCentreY.toInt()) + + /** + * 获取矩形4个点的某一个点 + */ + fun getRectABCDPoint(wherePoint: Int): Point { + val points = getRectABCDXYPoint(wherePoint) + return Point(points[0].toInt(), points[1].toInt()) + } + + /** + * 返回矩形第哪个点(A,B,C,D)的坐标 + * A **** B + * D **** C + */ + private fun getRectABCDXYPoint(wherePoint: Int): FloatArray { + when (wherePoint) { + POINT_A -> { + val leftTop = getRotateXY(mRectLeft, mRectTop, mRotateAngle) + return floatArrayOf( + leftTop[0] + mCentreX, leftTop[1] + mCentreY + ) + } + + POINT_B -> { + val rightTop = getRotateXY(mRectRight, mRectTop, mRotateAngle) + return floatArrayOf( + rightTop[0] + mCentreX, rightTop[1] + mCentreY + ) + } + + POINT_D -> { + val leftBottom = getRotateXY(mRectLeft, mRectBottom, mRotateAngle) + return floatArrayOf( + leftBottom[0] + mCentreX, leftBottom[1] + mCentreY + ) + } + + POINT_C -> { + val rightBottom = getRotateXY(mRectRight, mRectBottom, mRotateAngle) + return floatArrayOf( + rightBottom[0] + mCentreX, rightBottom[1] + mCentreY + ) + } + } + return floatArrayOf( + 0f, 0f + ) + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + + when (event?.action) { + MotionEvent.ACTION_DOWN -> { + val x = event.x + val y = event.y + LogUtil.e("action_down,x=$x,y=$y") + mRotateBitmap?.apply { + LogUtil.e("x2=${width / 2f + mCentreX},x1=${-width / 2f + mCentreX}") + LogUtil.e("y2=${-(mRectHeight / 2 + height) + mCentreY},aaa=${-mRectHeight / 2 + mCentreY}") + + if (isClickRotate(x, y, mRotateAngle)) { + LogUtil.e("点击了旋转") + isRotate = true + return true + } + } + //是否按下了放大缩小按钮 + mZoomBitmap?.apply { + if (!isRotate && isClickScale(x, y, mRotateAngle)) { + isScale = true + LogUtil.e("点击了放大缩小") + mOldX = x + mOldY = y + return true + } + } + + //因为有旋转角度,需要减去中心 + if (mRectF.contains(x - mCentreX, y - mCentreY)) { + isDrag = true + mOldX = x + mOldY = y + mStrokePaint.strokeWidth = QMUIDisplayHelper.dpToPx(3).toFloat() + mRotateScaleClickListener?.rectStartDragClick() + invalidate() + return true + } + } + + MotionEvent.ACTION_MOVE -> { + val x = event.x + val y = event.y + LogUtil.e("action_move,x=$x,y=$y,oldX=$mOldX,oldY=$mOldY") + if (isRotate) { + //计算手指按下画布移动后的x,y角度 + val transformAngle = atan2(y - mCentreY, x - mCentreX) / Math.PI * 180 + //计算需要旋转的角度 + mRotateAngle = transformAngle - mStartRotateAngle + LogUtil.e("mRotateAngle=$mRotateAngle") + invalidate() + return true + } else if (isScale) { + LogUtil.e("mRectWidth=$mRectWidth,mRectHeight=$mRectHeight,x - oldX=${x - mOldX},y - oldY=${y - mOldY}") + val transformX = x - mOldX + val transformY = y - mOldY + //限制缩放不能太小 + if (mRectWidth + transformX < mLocationBitmap!!.height || mRectHeight + transformY < mLocationBitmap!!.height) { + return true + } + mRectWidth += transformX + mRectHeight += transformY + mOldX = x + mOldY = y + invalidate() + return true + } else if (isDrag) { + val transformX = x - mOldX + val transformY = y - mOldY + mCentreX += transformX + mCentreY += transformY + mOldX = x + mOldY = y + invalidate() + return true + } + } + + MotionEvent.ACTION_UP -> { + if (isRotate) { + mRotateScaleClickListener?.rectRotateClick() + isRotate = false + } + if (isScale) { + mRotateScaleClickListener?.rectScaleClick() + isScale = false + } + if (isDrag) { + mRotateScaleClickListener?.rectEndDragClick() + isDrag = false + mStrokePaint.strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + invalidate() + } + } + } + return super.onTouchEvent(event) + } + + /** + * @param x 是坐标轴移动以(mCentreX,mCentreY)原点的坐标 + * @param y + * 获取以mCentreX、mCentreY为原点的坐标轴进行旋转后的XY坐标 + */ + private fun getRotateXY(x: Float, y: Float, rotate: Double): FloatArray { + //Math.toRadians(double angdeg) 角度转化为弧度 + //Math.toDegrees(Math.PI/2);弧度转化为角度 (π/2的角度值) + val curAngle = atan2(y, x) / Math.PI * 180 + val sumAngle = curAngle + rotate + //将角度化为弧度 +// val angle = Math.PI / 180 * sumAngle + val angle = Math.toRadians(sumAngle) + //初始坐标与中点形成的直线长度不管怎么旋转都是不会变的,用勾股定理求出然后将其作为斜边 + val c = sqrt(x.pow(2) + y.pow(2)) + //斜边乘sin值等于即可求出y坐标 + val a = sin(angle) * c + //斜边乘cos值等于即可求出x坐标 + val b = cos(angle) * c + //目前的xy坐标是相对于图片中点为原点的坐标轴 + return floatArrayOf((b).toFloat(), (a).toFloat()) + } + + /** + * 是否点击按下了旋转,按照移动了坐标轴原点(mCentreX,mCentreY)计算初始坐标 + */ + private fun isClickRotate(clickX: Float, clickY: Float, rotate: Double): Boolean { + var x1 = -(mRectWidth / 2 - mRotateBitmap!!.width / 2) + var x2 = (mRectWidth / 2 - mRotateBitmap!!.width / 2) + var y1 = -(mRectHeight / 2 + mRotateBitmap!!.height + mRotateBitmap!!.width) + var y2 = -(mRectHeight / 2 - mRotateBitmap!!.height) + val minX = getRotateXY(x1, y1, rotate) + val maxX = getRotateXY(x2, y2, rotate) + x1 = minX[0] + x2 = maxX[0] + if (minX[0] > maxX[0]) { + x1 = maxX[0] + x2 = minX[0] + } + y1 = minX[1] + y2 = maxX[1] + if (minX[1] > maxX[1]) { + y1 = maxX[1] + y2 = minX[1] + } + + //处理旋转到某个角度时,旋转图片按钮会超出坐标范围内,不在可点击坐标范围内,把可点击的坐标范围扩大 + if (abs(x2 - x1) < mRotateBitmap!!.height) { + if (x2 > 0) { + x2 += mRotateBitmap!!.height + } else { + x1 -= mRotateBitmap!!.height + } + } + if (abs(y2 - y1) < mRotateBitmap!!.height) { + if (y2 > 0) { + y2 += mRotateBitmap!!.height + } else { + y1 -= mRotateBitmap!!.height + } + } + + val transformX = clickX - mCentreX + val transformY = clickY - mCentreY + + if (transformX in x1..x2 && transformY in y1..y2) { + return true + } + return false + } + + /** + * 是否点击按下了放大缩小,按照移动了坐标轴原点(mCentreX,mCentreY)计算初始坐标 + */ + private fun isClickScale(clickX: Float, clickY: Float, rotate: Double): Boolean { + var x1 = (mRectWidth / 2 - mZoomBitmap!!.width) + var x2 = (mRectWidth / 2 + mZoomBitmap!!.width) + var y1 = (mRectHeight / 2 - mRotateBitmap!!.height) + var y2 = (mRectHeight / 2 + mRotateBitmap!!.height) + val minX = getRotateXY(x1, y1, rotate) + val maxX = getRotateXY(x2, y2, rotate) + x1 = minX[0] + x2 = maxX[0] + if (minX[0] > maxX[0]) { + x1 = maxX[0] + x2 = minX[0] + } + y1 = minX[1] + y2 = maxX[1] + if (minX[1] > maxX[1]) { + y1 = maxX[1] + y2 = minX[1] + } + + val transformX = clickX - mCentreX + val transformY = clickY - mCentreY + + if (transformX in x1..x2 && transformY in y1..y2) { + return true + } + return false + } + + /** + * 设置显示编辑的矩形(利用假设方式) + * 错误方式 + fun setRectABCDXYPoint(left: Float, top: Float, right: Float, bottom: Float) { + //假设A没有旋转的,原点在矩形中心,正常与x轴的角度135℃ + val noRotateAngle = 135.0 + val rotateAX = left - mCentreX + val rotateAY = top - mCentreY + //计算现在编辑后的矩形A点的角度 + val curAngle = atan2(rotateAY, rotateAX) / Math.PI * 180 + //计算旋转了多少度 + mRotateAngle = noRotateAngle - curAngle + //初始坐标与矩形中点形成的直线长度不管怎么旋转都是不会变的,用勾股定理求出然后将其作为斜边 + val c = sqrt(rotateAX.pow(2) + rotateAY.pow(2)) + //将角度化为弧度,计算旋转后的与x轴的角度,然后算出长和宽 + val angle = Math.toRadians(curAngle) + //斜边乘sin值等于即可求出y坐标 + val a = sin(angle) * c + //斜边乘cos值等于即可求出x坐标 + val b = cos(angle) * c + mRectLeft = (-abs(b)).toFloat() + mRectTop = (-abs(a)).toFloat() + + // mRectWidth = abs(b * 2).toFloat() + mRectWidth = abs(right) - abs(left) + // mRectHeight = abs(a * 2).toFloat() + mRectHeight = abs(bottom) - abs(top) + + mRectRight = mRectWidth / 2 + mRectBottom = mRectHeight / 2 + + isEdit = true + + LogUtil.e("编辑的坐标($mRectLeft,$mRectTop),($mRectRight,$mRectBottom),($mCentreX,$mCentreY)") + postInvalidate() + } */ + + fun setRectABCDXYPoint( + pointCentre: Point, pointA: Point, pointB: Point, pointC: Point, pointD: Point + ) { + mCentreX = pointCentre.x.toFloat() + mCentreY = pointCentre.y.toFloat() + //利用屏幕的坐标系根据勾股定理算出矩形的边长,A点和D点与坐标轴组成一个三角形 + mRectHeight = sqrt( + (abs(pointA.x - pointD.x).toFloat()).pow(2) + (abs(pointD.y - pointA.y).toFloat()).pow(2) + ) + //A点和B点与坐标轴组成一个三角形 + mRectWidth = sqrt( + (abs(pointB.x - pointA.x).toFloat()).pow(2) + (abs(pointB.y - pointA.y).toFloat()).pow(2) + ) + + val rotateAX = pointA.x - mCentreX + val rotateAY = pointA.y - mCentreY + //计算现在编辑后的矩形A点与x轴形成的角度 + val curAngle = atan2(rotateAY, rotateAX) / Math.PI * 180 + + //初始坐标与矩形中点形成的直线长度不管怎么旋转都是不会变的,用勾股定理求出然后将其作为斜边 +// val c = sqrt(rotateAX.pow(2) + rotateAY.pow(2)) + //将角度化为弧度,计算旋转后的与x轴的角度,然后算出长和宽 +// val angle = Math.toRadians(curAngle) + //斜边乘sin值等于即可求出y坐标 +// val a = sin(angle) * c + //斜边乘cos值等于即可求出x坐标 +// val b = cos(angle) * c + + //根据矩形长宽算出没有旋转的矩形坐标 + val rectLeft = -mRectWidth / 2 + val rectTop = -mRectHeight / 2 + val rectRight = rectLeft + mRectWidth + val rectBottom = rectTop + mRectHeight + //计算A没有旋转的,原点在矩形中心,正常与x轴的角度 + val noRotateAngle = atan2(rectTop, rectLeft) / Math.PI * 180 + //计算旋转了多少度 + mRotateAngle = curAngle - noRotateAngle + LogUtil.e("没有旋转时初始角度=$noRotateAngle,当前旋转后的角度curAngle=$curAngle,旋转了多少度mRotateAngle=$mRotateAngle") + LogUtil.e("编辑的坐标($rectLeft,$rectTop),($rectRight,$rectBottom),($mCentreX,$mCentreY)") + postInvalidate() + } + + fun setCentreXY(point: Point) { + point.apply { + if (mCentreX != x.toFloat() || mCentreY != y.toFloat()) { + mCentreX = x.toFloat() + mCentreY = y.toFloat() + isNeedMeasure = false + invalidate() + } + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + LogUtil.e("onDetachedFromWindow,${javaClass.canonicalName}视图销毁") + if (null != mDotBitmap) { + mDotBitmap?.recycle() + mDotBitmap = null + } + if (null != mLocationBitmap) { + mLocationBitmap?.recycle() + mLocationBitmap = null + } + if (null != mRotateBitmap) { + mRotateBitmap?.recycle() + mRotateBitmap = null + } + if (null != mZoomBitmap) { + mZoomBitmap?.recycle() + mZoomBitmap = null + } + } +} + diff --git a/app/src/main/java/com/abbidot/tracker/widget/HorizontalBarChartView.kt b/app/src/main/java/com/abbidot/tracker/widget/HorizontalBarChartView.kt new file mode 100644 index 0000000..c546e4a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/HorizontalBarChartView.kt @@ -0,0 +1,260 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.abs + +/** + *Created by .yzq on 2024/1/3/003. + * @link + * @description: + */ +class HorizontalBarChartView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private val mXAxis = mutableListOf("00 Min", "240", "480", "720", "960", "1200", "1440") + private var mYAxis = mutableListOf("Fr", "Th", "We", "Tu", "Mo", "Su", "Today") + + //每个圆柱的宽度 + private var mBarHeight = 0f + + private var mRadius = 0f + private var mData: MutableList? = null + + //每个数值1所需要、占用的高度 + private var mValueHeight = 0f + private var mMaxYAxisValue = 24 * 60 + private var mMaxValue = 0 + + private lateinit var mPaint: Paint + private lateinit var mTextPaint: Paint + private lateinit var mMaxPaint: Paint + + //配合动画,每次画百分几 + private var mAnimatedPercent = 0f + + //动画时长(毫秒) + private val mAnimatorDuration = 1000L + + private var mAxisTextColor = 0 + private var mBarValueColor = 0 + private var mBarMaxValueColor = 0 + private var mBarMaxValueTextColor = 0 + private var mBarValueTextColor = 0 + private var mBarValueBgColor = 0 + + //转换小时 + private var mConvertHour = false + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context + mBarHeight = QMUIDisplayHelper.dpToPx(18).toFloat() + mRadius = QMUIDisplayHelper.dpToPx(8).toFloat() + + mPaint = Paint().apply { + color = mBarValueBgColor + } + mTextPaint = Paint().apply { + color = mAxisTextColor + textSize = QMUIDisplayHelper.sp2px(mContext, 10).toFloat() + } + mMaxPaint = Paint().apply { + color = mBarMaxValueColor + } + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + mAxisTextColor = ContextCompat.getColor(context, R.color.select_color3) + mBarValueColor = ContextCompat.getColor(context, R.color.green_color1) + mBarMaxValueColor = ContextCompat.getColor(context, R.color.select_color) + mBarMaxValueTextColor = ContextCompat.getColor(context, R.color.select_color2) + mBarValueTextColor = ContextCompat.getColor(context, R.color.block_color2) + mBarValueBgColor = ContextCompat.getColor(context, R.color.dark_color) + return + } + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.HorizontalBarChartView) + mAxisTextColor = typedArray.getColor( + R.styleable.HorizontalBarChartView_bar_axis_text_color, + ContextCompat.getColor(context, R.color.select_color3) + ) + mBarValueColor = typedArray.getColor( + R.styleable.HorizontalBarChartView_bar_value_color, + ContextCompat.getColor(context, R.color.green_color1) + ) + mBarMaxValueColor = typedArray.getColor( + R.styleable.HorizontalBarChartView_bar_max_value_color, + ContextCompat.getColor(context, R.color.select_color) + ) + mBarMaxValueTextColor = typedArray.getColor( + R.styleable.HorizontalBarChartView_bar_max_value_text_color, + ContextCompat.getColor(context, R.color.select_color2) + ) + mBarValueTextColor = typedArray.getColor( + R.styleable.HorizontalBarChartView_bar_value_text_color, + ContextCompat.getColor(context, R.color.block_color2) + ) + mBarValueBgColor = typedArray.getColor( + R.styleable.HorizontalBarChartView_bar_value_bg_color, + ContextCompat.getColor(context, R.color.dark_color) + ) + typedArray.recycle() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + drawBar(this) + } + } + + private fun drawBar(canvas: Canvas) { + val fontMetrics = mTextPaint.fontMetrics // 获取字体指标信息 + val textHeight = abs((fontMetrics.top - fontMetrics.bottom))// 计算文本高度 + + val xSize = mXAxis.size + //取最长的字体宽度 + var xTxtWith = mTextPaint.measureText(mXAxis[xSize - 1]) + for (i in 0 until xSize) { + val cTxtWith = mTextPaint.measureText(mXAxis[i]) + if (xTxtWith < cTxtWith) xTxtWith = cTxtWith + } + val xSpace = (mWidth - textHeight / 2 - xTxtWith * xSize) / (xSize - 1) + mTextPaint.color = mAxisTextColor + for (i in 0 until xSize) { + canvas.drawText( + mXAxis[i], xSpace * i + xTxtWith * i + textHeight / 2, mHeight.toFloat(), mTextPaint + ) + } + + val ySize = mYAxis.size + val ySpace = (mHeight - textHeight * ySize) / (ySize + 1) + for (i in 0 until ySize) { + canvas.drawText( + mYAxis[i], 0f, mHeight - (ySpace * (i + 1)) - textHeight * (i + 1), mTextPaint + ) + } + + mData?.apply { + if (size > 0) { + + val xWith = mWidth - xTxtWith + mValueHeight = xWith / mMaxYAxisValue + + val left = xTxtWith + for (i in 0 until size) { + val value = this[i] + val top = + mHeight - (ySpace * (i + 1)) - mBarHeight / 2 - textHeight * (i + 1) - textHeight / 4 + val right = value * mValueHeight + textHeight / 4 + val bottom = top + mBarHeight + + mPaint.color = mBarValueBgColor + canvas.drawRoundRect( + left, top, mWidth.toFloat(), bottom, mRadius, mRadius, mPaint + ) + + if (value < 0) continue + + //防止动画从x轴原点以下开始出现 + val newRight = with(right * mAnimatedPercent) { + if (this < left) left + value * mValueHeight * mAnimatedPercent + else this + } + + if (value == mMaxValue) { + canvas.drawRoundRect( + left, top, newRight, bottom, mRadius, mRadius, mMaxPaint + ) + } else { + mPaint.color = mBarValueColor + canvas.drawRoundRect( + left, top, newRight, bottom, mRadius, mRadius, mPaint + ) + } + //动画结束显示文字 + if (mAnimatedPercent == 1f) { +// mTextPaint.color = mBarMaxValueTextColor + mTextPaint.color = mBarValueTextColor + val string = if (mConvertHour) { + val h = value / 60 + val m = value % 60 + "${h}hr ${m}min" + } else { + "$value min" + } + val textWith = mTextPaint.measureText(string) + canvas.drawText( + string, + mWidth - textWith - textHeight / 3f, + top + textHeight / 1.1f, + mTextPaint + ) + } + } + } + } + } + + + fun setData( + data: MutableList, yAxis: MutableList? = null, convertHour: Boolean = false + ) { + if (null != yAxis) mYAxis = yAxis + mConvertHour = convertHour + val size = data.size + mMaxValue = 0 + for (i in 0 until size) { + if (data[i] > mMaxValue) { + mMaxValue = data[i] + } + } + //根据y轴最大值决定x轴的值 + mMaxYAxisValue = (mMaxValue / 60 + 1) * 60 + val step = mMaxYAxisValue / 6 + for (i in 1..6) { + mXAxis[i] = (i * step).toString() + } + + mData = data + ViewUtil.instance.setValueAnimator(0f, 100f, mAnimatorDuration) { + mAnimatedPercent = it.animatedValue as Float / 100 + postInvalidate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/InviteFamilyAnimateView.kt b/app/src/main/java/com/abbidot/tracker/widget/InviteFamilyAnimateView.kt new file mode 100644 index 0000000..fd6ca05 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/InviteFamilyAnimateView.kt @@ -0,0 +1,415 @@ +package com.abbidot.tracker.widget + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.core.animation.doOnEnd +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ImageUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.abs + + +/** + *Created by .yzq on 2024/2/7/007. + * @link + * @description: + */ +class InviteFamilyAnimateView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private var mScreenWidth = 0 + + //最大圆的半径 + private var mMaxCircleRadius = 0f + + //邮件矩形长度 + private val mMailRectLength = QMUIDisplayHelper.dpToPx(100) + + //最大图片大长度 + private val mMaxImageLength = QMUIDisplayHelper.dpToPx(50) + + //黄色十字长度 + private val mYellowCrossLength = QMUIDisplayHelper.dpToPx(18) + + //底部矩形露出的长度 + private val mOutPartRectLength = ((mMailRectLength / 2f) / 3f) * 2 + + private lateinit var mCirclePaint: Paint + private lateinit var mMailPaint: Paint + private lateinit var mImagePaint: Paint + + private lateinit var mMailPath: Path + private lateinit var mImagePath: Path + + private lateinit var mFirstRectF: RectF + + // private lateinit var mSecondRectF: RectF + private lateinit var mRectF: RectF + + private var mBitmap1: Bitmap? = null + private var mBitmap2: Bitmap? = null + private var mBitmap3: Bitmap? = null + private var mBitmap4: Bitmap? = null + + private var mBitmap1Angle = -120f + private var mBitmap2Angle = -35f + private var mBitmap3Angle = -75f + private var mBitmap4Angle = -135f + + private var mBitmap1RotationAnimator: ValueAnimator? = null + private var mBitmap2RotationAnimator: ValueAnimator? = null + private var mBitmap3RotationAnimator: ValueAnimator? = null + private var mBitmap4RotationAnimator: ValueAnimator? = null + private var isStartAnim = false + + constructor(context: Context) : super(context) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + private fun init(context: Context) { + mContext = context + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mScreenWidth = QMUIDisplayHelper.getScreenWidth(context) + + mCirclePaint = Paint() + mMailPaint = Paint() + mImagePaint = Paint() + + mMailPath = Path() + mImagePath = Path() + + mFirstRectF = RectF() + mRectF = RectF() + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mBitmap1 = + ImageUtil.getBitmapFromDrawableAndSvg(mContext, R.drawable.invite_family_animate_image1) + mBitmap2 = + ImageUtil.getBitmapFromDrawableAndSvg(mContext, R.drawable.invite_family_animate_image2) + mBitmap3 = + ImageUtil.getBitmapFromDrawableAndSvg(mContext, R.drawable.invite_family_animate_image3) + mBitmap4 = + ImageUtil.getBitmapFromDrawableAndSvg(mContext, R.drawable.invite_family_animate_image4) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = maxOf(measuredWidth, mScreenWidth) + mMaxCircleRadius = mWidth / 2f + //最大半圆半径+露出最大图片/2+底部矩形露出部分长度 + mHeight = (mMaxCircleRadius + mMaxImageLength / 2 + mOutPartRectLength).toInt() + setMeasuredDimension(mWidth, mHeight) + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + LogUtil.e("InviteFamilyAnimateView----------------onSizeChanged") + super.onSizeChanged(w, h, oldw, oldh) + } + + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + LogUtil.e("InviteFamilyAnimateView----------------onLayout") + super.onLayout(changed, left, top, right, bottom) + if (!isStartAnim) planetRotationAnimation() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + drawCircle(this) + drawMail(this) + drawImage(this) + } + } + + private fun drawCircle(canvas: Canvas) { + canvas.apply { + mFirstRectF.apply { + left = 0f + top = mMaxImageLength / 2f + right = mWidth.toFloat() + bottom = mMaxCircleRadius * 2 + mMaxImageLength / 2f + } + mCirclePaint.color = ContextCompat.getColor(mContext, R.color.block_color) + drawArc(mFirstRectF, -180f, 180f, true, mCirclePaint) + + val secondCircleSpace = mMaxCircleRadius / 4 + mRectF.apply { + left = mFirstRectF.left + secondCircleSpace + top = mFirstRectF.top + secondCircleSpace + right = mFirstRectF.right - secondCircleSpace + bottom = mFirstRectF.bottom - secondCircleSpace + } + mCirclePaint.color = ContextCompat.getColor(mContext, R.color.white_gray_color) + drawArc(mRectF, -180f, 180f, true, mCirclePaint) + + val thirdCircleSpace = secondCircleSpace * 2 + mRectF.apply { + left = mFirstRectF.left + thirdCircleSpace + top = mFirstRectF.top + thirdCircleSpace + right = mFirstRectF.right - thirdCircleSpace + bottom = mFirstRectF.bottom - thirdCircleSpace + } + mCirclePaint.color = ContextCompat.getColor(mContext, R.color.block_color3) + drawArc(mRectF, -180f, 180f, true, mCirclePaint) + } + } + + private fun drawMail(canvas: Canvas) { + val halfWith = mWidth / 2f + val halfMailRectLength = mMailRectLength / 2f + mMailPath.apply { + //画下面填充白色矩形,填充合成图形的空白 + mRectF.apply { + left = halfWith - halfMailRectLength + top = mHeight - halfMailRectLength + right = halfWith + halfMailRectLength + bottom = mHeight - mMailRectLength / 3f + } + mMailPaint.strokeWidth = 0f + mMailPaint.color = ContextCompat.getColor(mContext, R.color.white) + canvas.drawRect(mRectF, mMailPaint) + + //画最上面的三角形 + moveTo(halfWith, (mHeight - mMailRectLength).toFloat()) + lineTo(mRectF.left, mRectF.top) + lineTo(mRectF.right, mRectF.top) + close() + canvas.drawPath(mMailPath, mMailPaint) + reset() + + //画矩形 + mRectF.apply { + left = halfWith - (halfMailRectLength / 5f) * 4 + top = + mHeight - halfMailRectLength - (halfMailRectLength - (halfMailRectLength / 2.5f)) + right = halfWith + (halfMailRectLength / 5f) * 4 + bottom = mHeight - (halfMailRectLength / 6f) * 2 + } + mMailPaint.color = ContextCompat.getColor(mContext, R.color.select_color) + canvas.drawRect(mRectF, mMailPaint) + + //画右边的三角形 + moveTo(halfWith + halfMailRectLength, mHeight - halfMailRectLength) + lineTo(halfWith + halfMailRectLength, mHeight.toFloat()) + lineTo(halfWith - halfMailRectLength, mHeight.toFloat()) + close() + mMailPaint.color = ContextCompat.getColor(mContext, R.color.white_color10) + canvas.drawPath(mMailPath, mMailPaint) + reset() + + //画左边的三角形 + moveTo(halfWith - halfMailRectLength, mHeight - halfMailRectLength) + lineTo(halfWith - halfMailRectLength, mHeight.toFloat()) + lineTo(halfWith + halfMailRectLength, mHeight.toFloat()) + close() + mMailPaint.color = ContextCompat.getColor(mContext, R.color.white) + canvas.drawPath(mMailPath, mMailPaint) + reset() + + //画绿色的十字 + mMailPaint.strokeWidth = AppUtils.dpToPx(2.5f) + mMailPaint.color = ContextCompat.getColor(mContext, R.color.select_color2) + val linStartX1 = halfWith - mYellowCrossLength / 2f + val linStartY1 = mHeight - halfMailRectLength - halfMailRectLength / 6f + //画横线 + canvas.drawLine( + linStartX1, linStartY1, linStartX1 + mYellowCrossLength, linStartY1, mMailPaint + ) + val linStartY2 = linStartY1 - mYellowCrossLength / 2 + //画竖线 + canvas.drawLine( + halfWith, linStartY2, halfWith, linStartY2 + mYellowCrossLength, mMailPaint + ) + } + } + + private fun drawImage(canvas: Canvas) { + val halfWith = mWidth / 2f +// mImagePath.apply { +// moveTo(0f, mMaxCircleRadius + mMaxImageLength / 2f) +// quadTo( +// halfWith, +// -mMaxCircleRadius*2, +// mWidth.toFloat(), +// mMaxCircleRadius + mMaxImageLength / 2f +// ) +// canvas.drawPath(this, mMailPaint) +// reset() +// } + + canvas.apply { + val circleY = mMaxCircleRadius + mMaxImageLength / 2f + + mBitmap1?.let { + val point = Utils.getPointOnCircleArc( + halfWith, circleY, mMaxCircleRadius, mBitmap1Angle.toDouble() + ) + //矫正x轴 + val checkX = abs(1f / 180f * mBitmap1Angle) + 1 + drawBitmap(it, point[0] - it.width * checkX, point[1] - it.height, null) + } + mBitmap2?.let { + val point = Utils.getPointOnCircleArc( + halfWith, circleY, mMaxCircleRadius, mBitmap2Angle.toDouble() + ) + //矫正x轴 + val checkX = abs(1f / 180f * mBitmap2Angle) + 1 + drawBitmap(it, point[0] - it.width * checkX, point[1] - it.height, null) + } + + val secondCircleSpace = mMaxCircleRadius / 4 + mBitmap3?.let { + val point = Utils.getPointOnCircleArc( + halfWith, + circleY, + mMaxCircleRadius - secondCircleSpace, + mBitmap3Angle.toDouble() + ) + //矫正x轴 + val checkX = abs(1f / 180f * mBitmap3Angle) + 1 + drawBitmap(it, point[0] - it.width * checkX, point[1] - it.height, null) + } + + mBitmap4?.let { + val point = Utils.getPointOnCircleArc( + halfWith, + circleY, + mMaxCircleRadius - secondCircleSpace * 2, + mBitmap4Angle.toDouble() + ) + //矫正x轴 + val checkX = abs(1f / 180f * mBitmap4Angle) + 1 + drawBitmap(it, point[0] - it.width * checkX, point[1] - it.height, null) + } + } + } + + /** + * 星球旋转动画 + */ + private fun planetRotationAnimation() { + isStartAnim = true + mBitmap1RotationAnimator = setRotationAnimator(mBitmap1Angle, 0f).apply { + duration = 10 * 1000 + addUpdateListener { + mBitmap1Angle = it.animatedValue as Float + postInvalidate() + } + } + mBitmap1RotationAnimator?.apply { + if (!isRunning) { + LogUtil.e("木有开始") + start() + } + } + + mBitmap2RotationAnimator = setRotationAnimator(mBitmap2Angle, -180f).apply { + duration = 25 * 1000 + addUpdateListener { + mBitmap2Angle = it.animatedValue as Float +// postInvalidate() + } + } + mBitmap2RotationAnimator?.apply { + if (!isRunning) { + start() + } + } + + mBitmap3RotationAnimator = setRotationAnimator(mBitmap3Angle, -180f).apply { + duration = 20 * 1000 + addUpdateListener { + mBitmap3Angle = it.animatedValue as Float +// postInvalidate() + } + } + mBitmap3RotationAnimator?.apply { + if (!isRunning) { + start() + } + } + + mBitmap4RotationAnimator = setRotationAnimator(mBitmap4Angle, 0f).apply { + duration = 15 * 1000 + addUpdateListener { + mBitmap4Angle = it.animatedValue as Float +// postInvalidate() + } + } + mBitmap4RotationAnimator?.apply { + if (!isRunning) { + start() + } + } + } + + private fun setRotationAnimator(startValue: Float, endValue: Float): ValueAnimator { + return ValueAnimator.ofFloat(startValue, endValue).apply { + repeatMode = ValueAnimator.REVERSE + //匀速变化 + interpolator = LinearInterpolator() + doOnEnd { + repeatCount = ValueAnimator.INFINITE + if (startValue == 0f || endValue == -180f) { + setFloatValues(-180f, 0f) + } else { + setFloatValues(0f, -180f) + } + start() + } + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + mBitmap1RotationAnimator?.cancel() + mBitmap2RotationAnimator?.cancel() + mBitmap3RotationAnimator?.cancel() + mBitmap4RotationAnimator?.cancel() + if (null != mBitmap1) { +// mBitmap1?.recycle() + mBitmap1 = null + } + if (null != mBitmap2) { +// mBitmap2?.recycle() + mBitmap2 = null + } + if (null != mBitmap3) { +// mBitmap3?.recycle() + mBitmap3 = null + } + if (null != mBitmap4) { +// mBitmap4?.recycle() + mBitmap4 = null + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/LineChartView.kt b/app/src/main/java/com/abbidot/tracker/widget/LineChartView.kt new file mode 100644 index 0000000..853be47 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/LineChartView.kt @@ -0,0 +1,270 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.DashPathEffect +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Shader +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.abs + +/** + *Created by .yzq on 2024/1/4/004. + * @link + * @description: + */ +class LineChartView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private lateinit var mAxisPaint: Paint + private lateinit var mDashedPaint: Paint + private lateinit var mChartPaint: Paint + private lateinit var mGradientPaint: Paint + private lateinit var mPath: Path + private lateinit var mGradientPath: Path + + private var mXAxis = mutableListOf("Today", "30/11", "24/11", "18/11", "12/11", "6/11") + private val mYAxis = mutableListOf(240, 480, 720, 960, 1200, 1440) + private var mMaxYAxisValue = 60 * 24 + + private var mData: MutableList? = null + private var mMaxValue = 0 + + //配合动画,每次画百分几 + private var mAnimatedPercent = 0f + + //动画时长(毫秒) + private val mAnimatorDuration = 2500L + + private var mAxisTextColor = 0 + private var mAxisDashedColor = 0 + private var mLineChartColor = 0 + private var mLineGradientStartColor = 0 + private var mLineGradientEndColor = 0 + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context + + mAxisPaint = Paint().apply { + textSize = QMUIDisplayHelper.sp2px(mContext, 10).toFloat() + color = mAxisTextColor + } + mChartPaint = Paint().apply { + color = mLineChartColor + strokeWidth = AppUtils.dpToPx(1f) + style = Paint.Style.STROKE + //拐角的形状。有三个值可以选择:MITER 尖角、 BEVEL 平角和 ROUND 圆角 + strokeJoin = Paint.Join.MITER + } + mDashedPaint = Paint().apply { + color = mAxisDashedColor + style = Paint.Style.STROKE + strokeWidth = AppUtils.dpToPx(0.5f) + } + mGradientPaint = Paint().apply { + style = Paint.Style.FILL + } + + mPath = Path() + mGradientPath = Path() + + //要画的实线和间隔的长度 + val dashedWidth = QMUIDisplayHelper.dpToPx(4).toFloat() + val dashPathEffect = DashPathEffect(floatArrayOf(dashedWidth, dashedWidth), 0f) + mDashedPaint.pathEffect = dashPathEffect + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + mAxisTextColor = ContextCompat.getColor(context, R.color.block_color2) + mAxisDashedColor = ContextCompat.getColor(context, R.color.white_gray_color1) + mLineChartColor = ContextCompat.getColor(context, R.color.select_color) + mLineGradientStartColor = ContextCompat.getColor(context, R.color.white) + mLineGradientEndColor = ContextCompat.getColor(context, R.color.green_color2) + return + } + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.LineChartView) + mAxisTextColor = typedArray.getColor( + R.styleable.LineChartView_line_axis_text_color, + ContextCompat.getColor(context, R.color.block_color2) + ) + mAxisDashedColor = typedArray.getColor( + R.styleable.LineChartView_line_axis_dashed_color, + ContextCompat.getColor(context, R.color.white_gray_color1) + ) + mLineChartColor = typedArray.getColor( + R.styleable.LineChartView_line_chart_color, + ContextCompat.getColor(context, R.color.select_color) + ) + mLineGradientStartColor = typedArray.getColor( + R.styleable.LineChartView_line_gradient_start_color, + ContextCompat.getColor(context, R.color.white) + ) + mLineGradientEndColor = typedArray.getColor( + R.styleable.LineChartView_line_gradient_end_color, + ContextCompat.getColor(context, R.color.green_color2) + ) + typedArray.recycle() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + val fontMetrics = mAxisPaint.fontMetrics // 获取字体指标信息 + val textHeight = abs((fontMetrics.top - fontMetrics.bottom))// 计算文本高度 + + val xSize = mXAxis.size + var xTxtMaxWith = 0f + for (i in 0 until xSize) { + val xTxtWith = mAxisPaint.measureText(mXAxis[i]) + if (xTxtMaxWith < xTxtWith) xTxtMaxWith = xTxtWith + } + val xSpace = (mWidth - xTxtMaxWith * xSize) / (xSize - 1) + for (i in 0 until xSize) { + val x = xSpace * i + xTxtMaxWith * i + canvas.drawText( + mXAxis[i], x, mHeight.toFloat() - textHeight / 4, mAxisPaint + ) + } + + val ySize = mYAxis.size + val newYAxisHeight = mHeight - textHeight - textHeight / 3 + val ySpace = (newYAxisHeight - textHeight * ySize) / ySize + for (i in 0 until ySize) { + canvas.drawText( + mYAxis[i].toString(), + 0f, + newYAxisHeight - (ySpace * (i + 1)) - textHeight * i, + mAxisPaint + ) + } + + //虚线个数 + val dashedCount = 16 + val newX = mWidth - xTxtMaxWith + val strokeWidth = mDashedPaint.strokeWidth + val dashedSpace = (newX - strokeWidth * dashedCount) / (dashedCount - 1) + for (i in 0 until dashedCount) { + val x = xTxtMaxWith + i * dashedSpace + strokeWidth * (i + 1) +// canvas.drawLine(x, newY, x, 0f, mDashedPaint) + mPath.moveTo(x, newYAxisHeight) + mPath.lineTo(x, 0f) + canvas.drawPath(mPath, mDashedPaint) + mPath.reset() + } + + mData?.apply { + if (size > 0) { + val dotWidth = mChartPaint.strokeWidth + //value=1所占的像素 + val valueHeight = newYAxisHeight / mMaxYAxisValue + //点之间的间隔 + val dotSpace = (newX - dotWidth * size) / (size - 1) + //从数值坐标today原点开始连线 + mGradientPath.moveTo(xTxtMaxWith, newYAxisHeight) + for (i in 0 until size) { + val value = this[i] + val x = xTxtMaxWith + i * dotSpace + i * dotWidth + //需要与y轴值对齐,要加上文字的高度textHeight / 1.5f + var y = newYAxisHeight - value * valueHeight + y /= mAnimatedPercent + //防止从x轴底部画起 + if (y > newYAxisHeight) y = newYAxisHeight + if (i == 0) mPath.moveTo(x, y) + else mPath.lineTo(x, y) + mGradientPath.lineTo(x, y) + } + //开始坐标与结束坐标是什么方向就以什么方向渐变 + val shader = LinearGradient( + xTxtMaxWith, + //从最高的那个点开始渐变 + newYAxisHeight - mMaxValue * valueHeight + textHeight / 1.5f, + xTxtMaxWith, + newYAxisHeight, + mLineGradientEndColor, + mLineGradientStartColor, + Shader.TileMode.CLAMP + ) + //最后从数值顶点坐标闭合 + mGradientPath.lineTo(mWidth.toFloat(), newYAxisHeight) + //画一个闭合的路径进行渐变 + mGradientPath.close() + mGradientPaint.shader = shader + canvas.drawPath(mGradientPath, mGradientPaint) + mGradientPath.reset() + + canvas.drawPath(mPath, mChartPaint) + mPath.reset() + } + } + } + + fun setData(data: MutableList, xAxis: MutableList? = null) { + if (null != xAxis) mXAxis = xAxis + val size = data.size + mMaxValue = 0 + for (i in 0 until size) { + if (data[i] > mMaxValue) { + mMaxValue = data[i] + } + } + mData = data + if (mMaxValue == 0) { + mAnimatedPercent = 0f + invalidate() + return + } + //根据x轴最大值决定y轴的值 + mMaxYAxisValue = (mMaxValue / 60 + 1) * 60 + val step = mMaxYAxisValue / 6 + for (i in 0..5) { + mYAxis[i] = (i + 1) * step + } + + ViewUtil.instance.setValueAnimator(0f, 100f, mAnimatorDuration) { + mAnimatedPercent = it.animatedValue as Float / 100 + postInvalidate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MapDeviceNetView.kt b/app/src/main/java/com/abbidot/tracker/widget/MapDeviceNetView.kt new file mode 100644 index 0000000..f169f74 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MapDeviceNetView.kt @@ -0,0 +1,135 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.abbidot.tracker.base.BaseView + + +/** + *Created by .yzq on 2024/10/16/016. + * @link + * @description: + */ +class MapDeviceNetView : BaseView { + + /** + * 网络类型,0没有网络 1蜂窝网 2wifi + */ + private var mNetType = NO_NET_TYPE + + //确定圆的左上角的坐标轴原点位置 + private var mOffsetX = 0 + private var mOffsetY = 0 + private var mWhiteCircleRadius = 0f + + companion object { + const val NO_NET_TYPE = 0 + const val DATA_NET_TYPE = 1 + const val WIFI_NET_TYPE = 2 + } + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + + override fun init(context: Context, attrs: AttributeSet?) { + super.init(context, attrs) + mWhiteCircleRadius = AppUtils.dpToPx(8f) + + mPaint.apply { + strokeWidth = mStrokeWidth + strokeCap = Paint.Cap.ROUND + //开启抗锯齿 + flags = Paint.ANTI_ALIAS_FLAG + } + } + + + override fun startDraw(canvas: Canvas) { + return + if (mNetType == NO_NET_TYPE) return + canvas.apply { + mPaint.apply { + color = ContextCompat.getColor(mContext, R.color.white) + style = Paint.Style.FILL + } + val bigCy = mOffsetY.toFloat() + mWhiteCircleRadius + drawCircle( + mOffsetX.toFloat() + mWhiteCircleRadius, bigCy, mWhiteCircleRadius, mPaint + ) + mPaint.apply { + color = ContextCompat.getColor(mContext, R.color.select_color) + style = Paint.Style.FILL + } + if (mNetType == DATA_NET_TYPE) { + val barNUm = 4 + val twoSpace = (mWhiteCircleRadius * 2 - mStrokeWidth * 6) / 2 + val stopY = mOffsetY.toFloat() + mWhiteCircleRadius + AppUtils.dpToPx(3f) + for (i in 0 until barNUm) { + val startX = mOffsetX.toFloat() + twoSpace + mStrokeWidth * i + mStrokeWidth * i + val startY = if (i == 0) mOffsetY.toFloat() + mWhiteCircleRadius + 5 + else mOffsetY.toFloat() + mWhiteCircleRadius - 5 * i + 2 + drawLine(startX, startY, startX, stopY, mPaint) + } + } else if (mNetType == WIFI_NET_TYPE) { + val cx = mOffsetX.toFloat() + mWhiteCircleRadius + val cy = mOffsetY.toFloat() + mWhiteCircleRadius + mWhiteCircleRadius / 2.3f + drawCircle(cx, cy, mStrokeWidth, mPaint) + mPaint.style = Paint.Style.STROKE + val insideRadius = mWhiteCircleRadius / 2 + val startAngle = -130f + val sweepAngle = 80f + drawArc( + cx - insideRadius, + cy - insideRadius, + cx + insideRadius, + cy + insideRadius, + startAngle, + sweepAngle, + false, + mPaint + ) + val outsideRadius = mWhiteCircleRadius - mStrokeWidth + drawArc( + cx - outsideRadius, + cy - outsideRadius, + cx + outsideRadius, + cy + outsideRadius, + startAngle, + sweepAngle, + false, + mPaint + ) + } + } + } + + fun setOffsetXY(x: Int, y: Int) { + mOffsetX = x + mOffsetY = y + invalidate() + } + + fun setShowNetType(type: Int) { + mNetType = if (type <= NO_NET_TYPE) NO_NET_TYPE + else if (type >= WIFI_NET_TYPE) WIFI_NET_TYPE + else type + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MapMarkerInfoView.kt b/app/src/main/java/com/abbidot/tracker/widget/MapMarkerInfoView.kt new file mode 100644 index 0000000..2114676 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MapMarkerInfoView.kt @@ -0,0 +1,133 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.text.TextUtils +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2024/8/26/026. + * @link + * @description: + */ +class MapMarkerInfoView : View { + + private lateinit var mContext: Context + private var mOffsetX = 0 + private var mOffsetY = 0 + + //文字和边界的距离。三角形的直线长 + private var mPadding = 0 + + private lateinit var mTextPaint: Paint + private lateinit var mPaint: Paint + + //小三角形的path + private lateinit var mTrianglePath: Path + + private var mTimeText = "" + private var mAddressText = "" + + constructor(context: Context) : super(context) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + private fun init(context: Context) { + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context + mPaint = Paint().apply { + color = ContextCompat.getColor(mContext, R.color.white) + } + //开启抗锯齿 + mTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + color = ContextCompat.getColor(mContext, R.color.black) + textSize = QMUIDisplayHelper.sp2px(mContext, 12).toFloat() + } + mPadding = AppUtils.dpToPx(6) + + mTrianglePath = Path() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + if (TextUtils.isEmpty(mAddressText) && TextUtils.isEmpty(mTimeText)) return + mTrianglePath.apply { + reset() + moveTo((mOffsetX - mPadding).toFloat(), (mOffsetY - mPadding).toFloat()) + lineTo(mOffsetX.toFloat(), mOffsetY.toFloat()) + lineTo((mOffsetX + mPadding).toFloat(), (mOffsetY - mPadding).toFloat()) + close() + } + canvas.apply { + + drawPath(mTrianglePath, mPaint) + + val fontMetrics = mTextPaint.fontMetrics + //文字高度 + val textHeight = fontMetrics.bottom - fontMetrics.top + //时间文字宽度 + val timeTextWith = mTextPaint.measureText(mTimeText) + //地址文字宽度 + val addressTextWith = mTextPaint.measureText(mAddressText) + val maxTextWith = maxOf(timeTextWith, addressTextWith) + + drawRoundRect( + mOffsetX - maxTextWith / 2 - mPadding, + mOffsetY - textHeight * 2 - mPadding * 2, + mOffsetX + maxTextWith / 2 + mPadding, + mOffsetY.toFloat() - mPadding, + AppUtils.dpToPx(6f), + AppUtils.dpToPx(6f), + mPaint + ) + drawText( + mTimeText, + mOffsetX - timeTextWith / 2, + mOffsetY - textHeight / 2 - mPadding, + mTextPaint + ) + drawText( + mAddressText, + mOffsetX - addressTextWith / 2, + mOffsetY - textHeight / 2 - textHeight - mPadding, + mTextPaint + ) + } + } + + fun setShowText(time: String, address: String) { + mTimeText = time + mAddressText = address + invalidate() + } + + fun setOffsetXY(x: Int, y: Int) { + mOffsetX = x + mOffsetY = y + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MyBluetoothAnimView.kt b/app/src/main/java/com/abbidot/tracker/widget/MyBluetoothAnimView.kt new file mode 100644 index 0000000..1f8ac2d --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MyBluetoothAnimView.kt @@ -0,0 +1,194 @@ +package com.abbidot.tracker.widget + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R + +/** + *Created by .yzq on 2024/6/19/019. + * @link + * @description: + */ +class MyBluetoothAnimView : View { + private lateinit var mContext: Context + private lateinit var mPathPaint: Paint + private lateinit var mArcPaint: Paint + private lateinit var mPath: Path + + private var mWidth = 0 + private var mHeight = 0 + private var mStrokeWidth = 0f + + private lateinit var mLeftSmallRectF: RectF + private lateinit var mLeftBigRectF: RectF + private lateinit var mRightSmallRectF: RectF + private lateinit var mRightBigRectF: RectF + + private lateinit var mValueAnimator: ValueAnimator + + private var mValue = 0 + + //正在动画中 + private var isAnim = false + + //是否需要动画 + private var mNeedAnim = true + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + setMeasuredDimension(mWidth, mHeight) + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mWidth = AppUtils.dpToPx(30) + mHeight = AppUtils.dpToPx(16) + mStrokeWidth = AppUtils.dpToPx(1f) + + mPathPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.data_black_color) + strokeWidth = mStrokeWidth + strokeCap = Paint.Cap.ROUND + strokeJoin = Paint.Join.ROUND + style = Paint.Style.STROKE + } + + mArcPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.block_color2) + strokeWidth = mStrokeWidth + style = Paint.Style.STROKE + } + + initDrawData() + } + + private fun initDrawData() { + val centerX = mWidth / 2 - mPathPaint.strokeWidth / 2 + val centerY = mHeight / 2 - mPathPaint.strokeWidth / 2 + mPath = Path().apply { + moveTo(centerX, 0f) + lineTo(centerX, mHeight.toFloat()) + lineTo(centerX + centerX / 3, centerY + centerY / 2) + lineTo(centerX - centerX / 4, centerY / 2) + + moveTo(centerX, 0f) + lineTo(centerX + centerX / 3, centerY / 2) + lineTo(centerX - centerX / 4, centerY + centerY / 2) + } + + val bigRadius = centerY + val smallRadius = centerY / 2 + val diffRadius = bigRadius - smallRadius + mLeftSmallRectF = RectF( + bigRadius - smallRadius + mPathPaint.strokeWidth / 2, + bigRadius - smallRadius + mPathPaint.strokeWidth / 2, + bigRadius + smallRadius, + bigRadius + smallRadius + ) + mRightSmallRectF = RectF( + mWidth - bigRadius * 2 + diffRadius, + bigRadius - smallRadius + mPathPaint.strokeWidth / 2, + mWidth - diffRadius, + bigRadius + smallRadius + ) + mLeftBigRectF = RectF( + mPathPaint.strokeWidth / 2, mPathPaint.strokeWidth / 2, bigRadius * 2, bigRadius * 2 + ) + mRightBigRectF = RectF( + mWidth - bigRadius * 2 + mPathPaint.strokeWidth / 2, + mPathPaint.strokeWidth / 2, + mWidth.toFloat() - mPathPaint.strokeWidth / 2, + bigRadius * 2 + ) + + //动画 + mValueAnimator = ValueAnimator.ofInt(0, 3).apply { + duration = 1800 + repeatMode = ValueAnimator.RESTART + repeatCount = ValueAnimator.INFINITE + interpolator = LinearInterpolator() + addUpdateListener { + mValue = it.animatedValue as Int + postInvalidate() + if (!isAnim && mValue == 2) { + cancel() + } + } + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + canvas.apply { + drawPath(mPath, mPathPaint) + if (mNeedAnim) { + if (!isAnim || mValue >= 1) { + drawArc(mLeftSmallRectF, 120f, 120f, false, mArcPaint) + drawArc(mRightSmallRectF, -60f, 120f, false, mArcPaint) + } + if (!isAnim || mValue == 2) { + drawArc(mLeftBigRectF, 130f, 100f, false, mArcPaint) + drawArc(mRightBigRectF, -50f, 100f, false, mArcPaint) + } + } else { + if (mValue > 0) { + drawArc(mLeftSmallRectF, 120f, 120f, false, mArcPaint) + drawArc(mRightSmallRectF, -60f, 120f, false, mArcPaint) + drawArc(mLeftBigRectF, 130f, 100f, false, mArcPaint) + drawArc(mRightBigRectF, -50f, 100f, false, mArcPaint) + } + } + } + } + + /** + * 设置动画开始和停止 + */ + fun setBluetoothAnimStartAndStop(show: Boolean) { + isAnim = show + if (show) { + mValueAnimator.start() + } + } + + fun setValue(value: Int) { + if (value >= 0) { + mNeedAnim = false + mValue = value + invalidate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MyChartView.kt b/app/src/main/java/com/abbidot/tracker/widget/MyChartView.kt new file mode 100644 index 0000000..99b2a6c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MyChartView.kt @@ -0,0 +1,134 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2022/1/7/007. + * @link + * @description: 自定义柱状图 + */ +class MyChartView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + //每个圆柱的宽度 + private var mBarWidth = 0f + + //圆柱之间的间距 + private var mBarSpace = 0f + private var mRadius = 0f + private var mData: MutableList? = null + private lateinit var mPaint: Paint + + //是否显示Y轴分钟 + private var isShowMinY = false + + //配合动画,每次画百分几 + private var mAnimatedPercent = 0f + + //每个数值1所需要、占用的高度 + private var mValueHeight = 0f + + //动画时长(毫秒) + private val mAnimatorDuration = 1000L + + constructor(context: Context) : super(context) { + init(context) + } + + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + private fun init(context: Context) { + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context + mData = ArrayList() + mBarWidth = QMUIDisplayHelper.dpToPx(8).toFloat() + mRadius = QMUIDisplayHelper.dpToPx(4).toFloat() + mPaint = Paint() + mPaint.color = ContextCompat.getColor(mContext, R.color.btn_yellow_color) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + LogUtil.e("onMeasure") + mWidth = measuredWidth + mHeight = measuredHeight + } + + override fun onDraw(canvas: Canvas) { + LogUtil.d("MyChartView------>onDraw") + super.onDraw(canvas) + startDraw(canvas) + } + + private fun startDraw(canvas: Canvas) { + canvas.drawColor(ContextCompat.getColor(mContext, R.color.home_data_black_color1)) +// if (isShowMinY) { +// mPaint!!.color = ContextCompat.getColor(mContext!!, R.color.white) +// mPaint!!.textSize = QMUIDisplayHelper.sp2px(mContext, 11).toFloat() +// val str = "60 min" +// val fontMetrics = mPaint!!.fontMetrics +// //获取文字的高度 +// val height = fontMetrics.bottom - fontMetrics.top +// canvas.drawText(str, height / 4, height, mPaint!!) +// +// mPaint!!.color = ContextCompat.getColor(mContext!!, R.color.btn_yellow_color) +// } + val size = mData!!.size + if (size > 0) { + //执行一次 + if (mValueHeight == 0f) { + //从所有数据中找出最大值 +// var max = Collections.max(mData) +// if (max == 0) max = 1 + //直接用60分钟作为最大值 + val max = 60 + //每个数值1所需要、占用的高度 + mValueHeight = (mHeight / max).toFloat() + mBarSpace = ((mWidth - mBarWidth * size) / size) + } + for (i in 0 until size) { + val value = mData!![i] + if (value <= 0) continue + val left: Float = (mBarSpace * i + i * mBarWidth) + val top: Float = (mHeight - value * mValueHeight) + val right: Float = (mBarSpace * i + mBarWidth * (i + 1)) + val bottom = mWidth.toFloat() + canvas.drawRoundRect( + left, top / mAnimatedPercent, right, bottom, mRadius, mRadius, mPaint + ) + } + } + } + + + fun setData(data: MutableList, showMinY: Boolean = false) { + mData = data + isShowMinY = showMinY + ViewUtil.instance.setValueAnimator(0f, 100f, mAnimatorDuration) { + mAnimatedPercent = it.animatedValue as Float / 100 + postInvalidate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MyMonthView.kt b/app/src/main/java/com/abbidot/tracker/widget/MyMonthView.kt new file mode 100644 index 0000000..36f8cc7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MyMonthView.kt @@ -0,0 +1,105 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import com.haibin.calendarview.Calendar +import com.haibin.calendarview.MonthView +import java.lang.String +import kotlin.Boolean +import kotlin.Int + +/** + *Created by .yzq on 2024/1/9/009. + * @link + * @description: + */ +class MyMonthView(context: Context) : MonthView(context) { + + /** + * 绘制选中的日子 + * + * @param canvas canvas + * @param calendar 日历日历calendar + * @param x 日历Card x起点坐标 + * @param y 日历Card y起点坐标 + * @param hasScheme hasScheme 非标记的日期 + * @return 返回true 则绘制onDrawScheme,因为这里背景色不是是互斥的,所以返回true + */ + override fun onDrawSelected( + canvas: Canvas, calendar: Calendar, x: Int, y: Int, hasScheme: Boolean + ): Boolean { + mSelectedPaint.style = Paint.Style.FILL + canvas.drawCircle( + x + mItemWidth / 2f, y + mItemHeight / 2f, mItemWidth / 2.5f, mSelectedPaint + ) + return false + } + + /** + * 绘制标记的事件日子 + * + * @param canvas canvas + * @param calendar 日历calendar + * @param x 日历Card x起点坐标 + * @param y 日历Card y起点坐标 + */ + override fun onDrawScheme(canvas: Canvas, calendar: Calendar, x: Int, y: Int) { + } + + /** + * 绘制文本 + * + * @param canvas canvas + * @param calendar 日历calendar + * @param x 日历Card x起点坐标 + * @param y 日历Card y起点坐标 + * @param hasScheme 是否是标记的日期 + * @param isSelected 是否选中 + */ + override fun onDrawText( + canvas: Canvas, calendar: Calendar, x: Int, y: Int, hasScheme: Boolean, isSelected: Boolean + ) { + val cx = x + mItemWidth / 2 + + val isInRange = isInRange(calendar) + + if (isSelected) { + canvas.drawText( + String.valueOf(calendar.day), cx.toFloat(), mTextBaseLine + y, mSelectTextPaint + ) +// canvas.drawText( +// calendar.lunar, +// cx.toFloat(), +// mTextBaseLine + y + mItemHeight / 10, +// mSelectedLunarTextPaint +// ) + } else if (hasScheme) { + canvas.drawText( + String.valueOf(calendar.day), + cx.toFloat(), + mTextBaseLine + y, + if (calendar.isCurrentMonth && isInRange) mSchemeTextPaint else mOtherMonthTextPaint + ) +// canvas.drawText( +// calendar.lunar, +// cx.toFloat(), +// mTextBaseLine + y + mItemHeight / 10, +// mCurMonthLunarTextPaint +// ) + } else { + canvas.drawText( + String.valueOf(calendar.day), + cx.toFloat(), + mTextBaseLine + y, + if (calendar.isCurrentDay) mCurDayTextPaint else if (calendar.isCurrentMonth && isInRange) mCurMonthTextPaint else mOtherMonthTextPaint + ) +// canvas.drawText( +// calendar.lunar, +// cx.toFloat(), +// mTextBaseLine + y + mItemHeight / 10, +// if (calendar.isCurrentDay && isInRange) mCurDayLunarTextPaint else if (calendar.isCurrentMonth) mCurMonthLunarTextPaint else mOtherMonthLunarTextPaint +// ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MyRoomView.kt b/app/src/main/java/com/abbidot/tracker/widget/MyRoomView.kt new file mode 100644 index 0000000..a9bde51 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MyRoomView.kt @@ -0,0 +1,167 @@ +package com.abbidot.tracker.widget + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PathMeasure +import android.util.AttributeSet +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R + + +/** + *Created by .yzq on 2024/3/28/028. + * @link + * @description: + */ +class MyRoomView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private lateinit var mPaint: Paint + private lateinit var mGrayPaint: Paint + + private lateinit var mLeftHalfPath: Path + private lateinit var mRightHalfPath: Path + private lateinit var mLeftHalfPathMeasure: PathMeasure + private lateinit var mRightHalfPathMeasure: PathMeasure + + private lateinit var mCloseAnimator: ValueAnimator + private lateinit var mOpenAnimator: ValueAnimator + private val mAnimatorDuration = 2500L + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mWidth = AppUtils.dpToPx(150) + mHeight = mWidth + + mPaint = Paint().apply { + strokeWidth = AppUtils.dpToPx(8f) + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + strokeJoin = Paint.Join.ROUND + color = ContextCompat.getColor(mContext, R.color.black) + } + mGrayPaint = Paint().apply { + strokeWidth = AppUtils.dpToPx(8f) + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + strokeJoin = Paint.Join.ROUND + color = ContextCompat.getColor(mContext, R.color.white_gray_color) + } + + mLeftHalfPathMeasure = PathMeasure() + mLeftHalfPath = Path().apply { + moveTo(mWidth / 2f, 0f + mPaint.strokeWidth / 2) + lineTo(0f + mPaint.strokeWidth / 2, mHeight / 2f) + mLeftHalfPathMeasure.setPath(this, false) + val node = FloatArray(2) + val tempPathLength = mLeftHalfPathMeasure.length + //获取path指定长度某个的点 + mLeftHalfPathMeasure.getPosTan(tempPathLength - tempPathLength / 6, node, null) + lineTo(node[0], node[1]) + lineTo(node[0], mHeight.toFloat() - mPaint.strokeWidth / 2) + lineTo(mWidth / 2f, mHeight.toFloat() - mPaint.strokeWidth / 2) + } + mLeftHalfPathMeasure.setPath(mLeftHalfPath, false) + + mRightHalfPathMeasure = PathMeasure() + mRightHalfPath = Path().apply { + moveTo(mWidth / 2f, 0f + mPaint.strokeWidth / 2) + lineTo(mWidth - mPaint.strokeWidth / 2, mHeight / 2f) + mRightHalfPathMeasure.setPath(this, false) + val node = FloatArray(2) + val tempPathLength = mRightHalfPathMeasure.length + //获取path指定长度某个的点 + mRightHalfPathMeasure.getPosTan(tempPathLength - tempPathLength / 6, node, null) + lineTo(node[0], node[1]) + lineTo(node[0], mHeight.toFloat() - mPaint.strokeWidth / 2) + lineTo(mWidth / 2f, mHeight.toFloat() - mPaint.strokeWidth / 2) + } + mRightHalfPathMeasure.setPath(mRightHalfPath, false) + + val pathLength = mLeftHalfPathMeasure.length + // 创建一个值动画器来更新animatedValue + mCloseAnimator = ValueAnimator.ofFloat(0f, pathLength).apply { + duration = mAnimatorDuration + interpolator = LinearInterpolator() + addUpdateListener { + mAnimatedValue = it.animatedValue as Float + invalidate() + } + } + mOpenAnimator = ValueAnimator.ofFloat(pathLength, 0f).apply { + duration = mAnimatorDuration + interpolator = LinearInterpolator() + addUpdateListener { + mAnimatedValue = it.animatedValue as Float + invalidate() + } + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + setMeasuredDimension(mWidth, mHeight) + } + + private var mAnimatedValue = 0f + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + canvas.drawPath(mLeftHalfPath, mGrayPaint) + canvas.drawPath(mRightHalfPath, mGrayPaint) + +// val dst = FloatArray(2) + // 使用animatedValue来计算路径上的点,获取path指定长度一个的点 +// mLeftHalfPathMeasure.getPosTan(mAnimatedValue, dst, null) +// canvas.drawCircle(dst[0], dst[1], 10f, mPaint) + + val path = Path() + //获取path指定长度某段 + mLeftHalfPathMeasure.getSegment(0f, mAnimatedValue, path, true) + canvas.drawPath(path, mPaint) + + val path1 = Path() + mRightHalfPathMeasure.getSegment(0f, mAnimatedValue, path1, true) + canvas.drawPath(path1, mPaint) + } + + fun playOpenAnimator() { + mOpenAnimator.start() + } + + fun playCloseAnimator() { + mCloseAnimator.start() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MySignalView.kt b/app/src/main/java/com/abbidot/tracker/widget/MySignalView.kt new file mode 100644 index 0000000..7d889e0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MySignalView.kt @@ -0,0 +1,99 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R + +/** + *Created by .yzq on 2024/3/11/011. + * @link + * @description:直播使用的蓝牙信号 + */ +class MySignalView : View { + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + private var mStrokeWidth = 0f + private var mLineSpace = 0f + + private lateinit var mPaint: Paint + private val mMaxValue = 4 + private var mValue = 0 + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + setMeasuredDimension(mWidth, mHeight) + LogUtil.e("onMeasure $widthMeasureSpec,$heightMeasureSpec") + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context +// initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mWidth = AppUtils.dpToPx(24) + mHeight = mWidth + mStrokeWidth = AppUtils.dpToPx(2f) + mLineSpace = (mWidth - mStrokeWidth * mMaxValue) / (mMaxValue - 1) + + mPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.black) + strokeWidth = mStrokeWidth + strokeCap = Paint.Cap.ROUND + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + for (i in 1..mMaxValue) { + mPaint.color = if (i > mValue) { + ContextCompat.getColor(mContext, R.color.black15) + } else { + ContextCompat.getColor(mContext, R.color.black) + } + + //需要减去1个像素 + val startX = (mStrokeWidth * i) + (mLineSpace * (i - 1)) - i + val startY = mHeight - (mHeight * (i / 4f)) + canvas.drawLine(startX, startY, startX, mHeight.toFloat(), mPaint) + } + } + + fun setValue(value: Int) { + mValue = if (value < 0) { + 0 + } else if (value > mMaxValue) { + mMaxValue + } else value + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MyView.kt b/app/src/main/java/com/abbidot/tracker/widget/MyView.kt new file mode 100644 index 0000000..46207e4 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MyView.kt @@ -0,0 +1,67 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Camera +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.tracker.R + +/** + *Created by .yzq on 2022/4/25/025. + * @link + * @description: + */ +class MyView : View { + + private lateinit var mPaint: Paint + private lateinit var mCamera: Camera + + constructor(context: Context) : super(context) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, + defStyleAttr) { + init(context) + } + + private fun init(context: Context) { + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + //开启抗锯齿 + mPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mPaint.style = Paint.Style.STROKE + mPaint.strokeWidth = 2f + mPaint.color = ContextCompat.getColor(context, R.color.gray_color3) + + mCamera= Camera() + } + + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + +// mCamera.save() // 保存 Camera 的状态 + mCamera.rotateX(60f) // 旋转 Camera 的三维空间 + canvas.translate(width/2f, height/2f) // 旋转之后把投影移动回来 + mCamera.applyToCanvas(canvas) // 把旋转投影到 Canvas + canvas.translate(-width/2f, -height/2f) // 旋转之前把绘制内容移动到轴心(原点) +// mCamera.restore() // 恢复 Camera 的状态 + + + canvas.drawCircle(width/2f, height/2f, 50f, mPaint) + canvas.drawCircle(width/2f, height/2f, 150f, mPaint) + mPaint.strokeWidth = 2f + canvas.drawCircle(width/2f, height/2f, 250f, mPaint) + mPaint.strokeWidth =2f + canvas.drawCircle(width/2f, height/2f, 350f, mPaint) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/MyWiFiSignalView.kt b/app/src/main/java/com/abbidot/tracker/widget/MyWiFiSignalView.kt new file mode 100644 index 0000000..748416a --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/MyWiFiSignalView.kt @@ -0,0 +1,198 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R + +/** + *Created by .yzq on 2024/3/27/027. + * @link + * @description:wifi 信号view + */ +class MyWiFiSignalView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + private var mMinCircleRadius = 0f + private var mCircleStrokeWidth = 0f + private var mValueColor = 0 + + private lateinit var mPaint: Paint + private lateinit var mRectF: RectF + + private val mMaxValue = 2 + private var mValue = 0 + + //是否显示锁 + private var isShowLock = false + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mWidth = AppUtils.dpToPx(25) + mHeight = mWidth + + //开启抗锯齿 + mPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + strokeCap = Paint.Cap.ROUND + } + mRectF = RectF() + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + mMinCircleRadius = AppUtils.dpToPx(3f) + mCircleStrokeWidth = AppUtils.dpToPx(2f) + mValueColor = ContextCompat.getColor(context, R.color.green_color100) + return + } + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.MyWiFiSignalView) + mMinCircleRadius = typedArray.getDimension( + R.styleable.MyWiFiSignalView_min_circle_radius, AppUtils.dpToPx(3f) + ) + mCircleStrokeWidth = typedArray.getDimension( + R.styleable.MyWiFiSignalView_circle_stroke_width, AppUtils.dpToPx(2f) + ) + mValueColor = typedArray.getColor( + R.styleable.MyWiFiSignalView_value_color, + ContextCompat.getColor(context, R.color.green_color100) + ) + typedArray.recycle() + } + + /** + * 测量模式有三种:EXACTLY(精确模式)、AT_MOST(最大模式)和 UNSPECIFIED(未指定模式)。 + * 当 View 的宽度和高度都指定为具体数值时,测量模式为 EXACTLY;当 View 的宽度和高度都为 wrap_content 时, + * 测量模式为 AT_MOST;当 View 的宽度或高度未指定时,测量模式为 UNSPECIFIED + */ + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + val widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec) + val heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec) + if (widthMeasureMode == MeasureSpec.AT_MOST || heightMeasureMode == MeasureSpec.AT_MOST || widthMeasureMode == MeasureSpec.UNSPECIFIED || heightMeasureMode == MeasureSpec.UNSPECIFIED) { + setMeasuredDimension(mWidth, mHeight) + } else { + val max = maxOf(measuredWidth, measuredHeight) + setMeasuredDimension(max, max) + mHeight = max + mWidth = max + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + /** + * 不同半径的同心圆 + */ + private fun startDraw(canvas: Canvas) { + val circleX = mWidth / 2f + val circleY = mHeight - mMinCircleRadius + mPaint.apply { + style = Paint.Style.FILL + strokeWidth = 1f + color = mValueColor + } + canvas.drawCircle(circleX, circleY, mMinCircleRadius, mPaint) + + val r2 = mHeight / 2f - mMinCircleRadius + mPaint.apply { + style = Paint.Style.STROKE + strokeWidth = mCircleStrokeWidth + color = if (mValue < mMaxValue - 1) { + ContextCompat.getColor(mContext, R.color.white_gray_color) + } else { + mValueColor + } + } + mRectF.apply { + left = circleX - r2 + top = mHeight / 2f - mPaint.strokeWidth + right = circleX + r2 + bottom = circleY + r2 - mPaint.strokeWidth + } + canvas.drawArc(mRectF, -135f, 90f, false, mPaint) + + val r3 = mHeight - mMinCircleRadius + mPaint.color = if (mValue < mMaxValue) { + ContextCompat.getColor(mContext, R.color.white_gray_color) + } else { + mValueColor + } + mRectF.apply { + left = circleX - r3 + top = 0f + mPaint.strokeWidth / 2f + right = circleX + r3 + bottom = circleY + r3 + mPaint.strokeWidth / 2f + } + canvas.drawArc(mRectF, -135f, 90f, false, mPaint) + + if (isShowLock) { + mPaint.apply { + style = Paint.Style.FILL + color = mValueColor + } + val r = mMinCircleRadius / 3 + mRectF.apply { + left = mWidth - mMinCircleRadius * 2.2f + top = mHeight - mMinCircleRadius * 2.2f + right = mWidth.toFloat() + bottom = mHeight.toFloat() + } + canvas.drawRoundRect(mRectF, r, r, mPaint) + + val l = (mRectF.right - mRectF.left) / 6f + mRectF.apply { + left = mWidth - (l * 5) + top = mHeight / 2f + mMinCircleRadius / 1.5f + right = mWidth - l + bottom = mHeight - mMinCircleRadius / 1.5f + } + mPaint.apply { + style = Paint.Style.STROKE + strokeWidth = AppUtils.dpToPx(1.5f) + } + canvas.drawOval(mRectF, mPaint) + } + } + + fun setValue(value: Int, showLock: Boolean = false) { + mValue = if (value > mMaxValue) mMaxValue + else if (value < 0) 0 + else value + isShowLock = showLock + invalidate() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/NonSwipeRecyclerView.kt b/app/src/main/java/com/abbidot/tracker/widget/NonSwipeRecyclerView.kt new file mode 100644 index 0000000..81cfa4c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/NonSwipeRecyclerView.kt @@ -0,0 +1,24 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.recyclerview.widget.RecyclerView + +/** + *Created by .yzq on 2024/6/23/023. + * @link + * @description:不拦截滑动事件(即不拦截滑动事件使其父布局或者活动可以滑动) + */ +class NonSwipeRecyclerView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : RecyclerView(context, attrs, defStyleAttr) { + + override fun onInterceptTouchEvent(e: MotionEvent?): Boolean { + return false + } + + override fun onTouchEvent(e: MotionEvent?): Boolean { + return false + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/RingChartView.kt b/app/src/main/java/com/abbidot/tracker/widget/RingChartView.kt new file mode 100644 index 0000000..a07d495 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/RingChartView.kt @@ -0,0 +1,196 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2024/1/2/002. + * @link + * @description:圆形图表 + */ +class RingChartView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + private lateinit var mYellowPaint: Paint + private lateinit var mGrayPaint: Paint + private lateinit var mTextPaint: Paint + + private lateinit var mRectF: RectF + + //圆的半径 + private var mRadius = 0 + + //配合动画,每次画百分几 + private var mAnimatedPercent = 0f + + //动画时长(毫秒) + private val mAnimatorDuration = 1000L + + //绿圆扫过的角度 + private var mSweepAngle = 0f + private var mPercentText = "0" + + private var showPercent = true + private var mRingValueColor1 = 0 + private var mRingValueColor2 = 0 + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + mContext = context + + mYellowPaint = Paint().apply { + color = mRingValueColor1 + //画笔的样式,圆形,进度条适合使用 +// strokeCap = Paint.Cap.ROUND + //画笔的填充样式,描边 + style = Paint.Style.STROKE + } + mGrayPaint = Paint().apply { + color = mRingValueColor2 + style = Paint.Style.STROKE + } + mTextPaint = Paint().apply { + textSize = QMUIDisplayHelper.sp2px(mContext, 10).toFloat() + color = ContextCompat.getColor(mContext, R.color.white) + } + mRectF = RectF() + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + mRingValueColor1 = ContextCompat.getColor(context, R.color.select_color2) + mRingValueColor2 = ContextCompat.getColor(context, R.color.gray_color6) + return + } + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RingChartView) + showPercent = typedArray.getBoolean(R.styleable.RingChartView_show_percent_text, true) + mRingValueColor1 = typedArray.getColor( + R.styleable.RingChartView_ring_value_color1, + ContextCompat.getColor(context, R.color.select_color2) + ) + mRingValueColor2 = typedArray.getColor( + R.styleable.RingChartView_ring_value_color2, + ContextCompat.getColor(context, R.color.gray_color6) + ) + typedArray.recycle() + } + + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + drawRing(this) + } + } + + private fun drawRing(canvas: Canvas) { + if (mRadius == 0) { + mRadius = mHeight / 2 + val strokeWidth = mRadius / 2.5f + //圆弧的宽度,向正常圆内外各伸长一半宽度 + mYellowPaint.strokeWidth = strokeWidth + mGrayPaint.strokeWidth = strokeWidth + } + //计算圆环的一半宽度 + val ringHalfRadius = mRadius / 5f + //算上圆环的宽度一半后的半径 + val r = mRadius - ringHalfRadius + val left = mWidth / 2f - r + val top = mHeight / 2f - r + val right = mWidth / 2f + r + val bottom = mHeight / 2f + r + mRectF.let { + it.left = left + it.top = top + it.right = right + it.bottom = bottom + } +// canvas.drawRect(mRectF, mGrayPaint) + + canvas.drawArc(mRectF, 270f, -mSweepAngle / mAnimatedPercent, false, mYellowPaint) + val graySweepAngle = 360f - mSweepAngle + //默认是灰色的圆环 + canvas.drawArc(mRectF, 270f, graySweepAngle / mAnimatedPercent, false, mGrayPaint) + + //动画结束显示文字 + if (mAnimatedPercent == 1f && showPercent && mPercentText.toFloat() > 0f) { + val fontMetrics = mTextPaint.fontMetrics + //获取文字的高度 + val height = fontMetrics.bottom - fontMetrics.top + //根据圆弧的角度确定文字显示的位置,180°则显示中间位置 + + val yYellowText = angleToY(mSweepAngle, height) + canvas.drawText("$mPercentText%", ringHalfRadius, yYellowText, mTextPaint) + + val yGrayText = angleToY(graySweepAngle, height) + val grayText = Utils.formatDecimal(100 - mPercentText.toFloat(), 2) + canvas.drawText("$grayText%", mWidth - r + ringHalfRadius, yGrayText, mTextPaint) + } + } + + /** + * 根据圆弧的角度确定文字显示的位置,180°则显示中间位置 + */ + private fun angleToY(angle: Float, default: Float): Float { + return if (angle >= 180f) mHeight / 2f + else if (angle >= 60f) (angle / 180f) * (mHeight / 2f) + else default + } + + + fun setData(data1: Float, data2: Float) { + if (data1 == 0f && data2 == 0f) { + val sum = 2 + mPercentText = Utils.formatDecimal((1f / sum) * 100, 2) + mSweepAngle = 360f * (1f / sum) + mAnimatedPercent = 1f + mYellowPaint.color = ContextCompat.getColor(mContext, R.color.gray_color12) + invalidate() + } else { + val sum = data1 + data2 + mPercentText = Utils.formatDecimal((data1 / sum) * 100, 2) + mSweepAngle = 360f * (data1 / sum) + mYellowPaint.color = mRingValueColor1 + ViewUtil.instance.setValueAnimator(0f, 100f, mAnimatorDuration) { + mAnimatedPercent = it.animatedValue as Float / 100 + postInvalidate() + } + } + + mPercentText = mPercentText.replace(",", ".") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/RingScaleView.kt b/app/src/main/java/com/abbidot/tracker/widget/RingScaleView.kt new file mode 100644 index 0000000..75e53f7 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/RingScaleView.kt @@ -0,0 +1,416 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.DashPathEffect +import android.graphics.Paint +import android.graphics.Path +import android.graphics.Rect +import android.graphics.RectF +import android.graphics.Region +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.util.QMUIDisplayHelper +import kotlin.math.min + + +/** + *Created by .yzq on 2024/2/4/004. + * @link + * @description:圆环刻度尺 + */ +class RingScaleView : View { + + private lateinit var mContext: Context + private var mWidth = 0 + private var mHeight = 0 + + private lateinit var mOutCirclePaint: Paint + private lateinit var mScalePaint: Paint + private lateinit var mFillPaint: Paint + private lateinit var mTextPaint: Paint + private lateinit var mDashedPaint: Paint + private lateinit var mDragPaint: Paint + private lateinit var mPathPaint: Paint + + private lateinit var mRectF: RectF + private lateinit var mRect: Rect + + private lateinit var mDragPath: Path + private lateinit var mPathRegion: Region + + //最外面的圆需要刻度的个数 + private val mOutCircleScale = 360 + + //最外面的圆的半径 + private var mOutCircleRadius = 0f + + //内圆的半径 + private var mInsideCircleRadius = 0f + + //显示结果的圆半径 + private var mShowResultCircleRadius = 0f + private var mDragCircleRadius = 0f + + //最外面圆的线的长度 + private val mOutCircleStrokeHeight = QMUIDisplayHelper.dpToPx(18).toFloat() + + //最外面圆的线的宽度 + private var mOutCircleStrokeWidth = 0f + + private lateinit var mMinimumString: String + private lateinit var mRecommencedString: String + + //选中的值 + private var mValue = 30 + + //配合动画,每次画百分几 + private var mAnimatedPercent = 0f + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context + initAttrs(context, attrs) + + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mMinimumString = context.getString(R.string.txt_minimum) + mRecommencedString = context.getString(R.string.txt_recommenced) + + mOutCirclePaint = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.STROKE + //虚线长度 + strokeWidth = mOutCircleStrokeHeight + color = ContextCompat.getColor(context, R.color.line_color1) + } + mScalePaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.data_black_color) + strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + } + mFillPaint = Paint() + mTextPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.block_color2) + textSize = QMUIDisplayHelper.sp2px(context, 8).toFloat() + } + mDashedPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.line_color1) + } + mDragPaint = Paint().apply { + strokeWidth = QMUIDisplayHelper.dpToPx(1).toFloat() + color = ContextCompat.getColor(context, R.color.select_color) + style = Paint.Style.STROKE + } + mPathPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.select_color2) + } + + mDragPath = Path() + mPathRegion = Region() + mRectF = RectF() + mRect = Rect() + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + if (null == attrs) { + + } + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mShowResultCircleRadius = QMUIDisplayHelper.dpToPx(28).toFloat() + mDragCircleRadius = mShowResultCircleRadius / 2f + //外圆的半径,虚线的长度默认圆内外各占一半,圆的外边距 + mOutCircleRadius = + width.coerceAtMost(height) / 2f - mOutCirclePaint.strokeWidth / 2f - QMUIDisplayHelper.dpToPx( + 20 + ) + //计算出周长 + val circumference = 2 * Math.PI * mOutCircleRadius + + //根据虚线+空白间隔=圆的周长(这里默认虚线宽度和空白间隔一样大小) + val dashOutGapSize = circumference / (mOutCircleScale * 2) + mOutCircleStrokeWidth = dashOutGapSize.toFloat() + mOutCirclePaint.pathEffect = DashPathEffect( + floatArrayOf( + mOutCircleStrokeWidth, mOutCircleStrokeWidth + ), 0f + ) + + mInsideCircleRadius = mOutCircleRadius - mOutCirclePaint.strokeWidth / 2f + + mDashedPaint.strokeWidth = mScalePaint.strokeWidth * 2 + val dashSpace = mOutCircleStrokeWidth * 3 + mDashedPaint.pathEffect = DashPathEffect( + floatArrayOf(dashSpace, dashSpace), 0f + ) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mWidth = measuredWidth + mHeight = measuredHeight + } + + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + val halfWith = mWidth / 2f + val halfHeight = mHeight / 2f + canvas.apply { + //画虚线 + val lineStartY = halfHeight - mInsideCircleRadius + drawLine(halfWith, lineStartY, halfWith, halfHeight, mDashedPaint) + + //outScaleRectF + mRectF.apply { + left = halfWith - mOutCircleRadius + top = halfHeight - mOutCircleRadius + right = halfWith + mOutCircleRadius + bottom = halfHeight + mOutCircleRadius + } + mOutCirclePaint.color = ContextCompat.getColor(mContext, R.color.select_color2) + drawArc(mRectF, -90f, mValue.toFloat(), false, mOutCirclePaint) + mOutCirclePaint.color = ContextCompat.getColor(mContext, R.color.black25) + drawArc(mRectF, -90f, -(360f - mValue), false, mOutCirclePaint) + + //填充推荐的颜色区域 + mRectF.apply { + left = halfWith - mInsideCircleRadius + top = lineStartY + right = halfWith + mInsideCircleRadius + bottom = halfHeight + mInsideCircleRadius + } + mFillPaint.color = ContextCompat.getColor(mContext, R.color.yellow_color10) + drawArc(mRectF, -90f, 30f, true, mFillPaint) + mFillPaint.color = ContextCompat.getColor(mContext, R.color.green_color10) + drawArc(mRectF, -60f, 180f, true, mFillPaint) + //写区域文字 + save() + rotate(-75f, halfWith, halfHeight) + mTextPaint.color = ContextCompat.getColor(context, R.color.block_color2) + mTextPaint.textSize = QMUIDisplayHelper.sp2px(context, 8).toFloat() + //文字宽度 + var textWidth = mTextPaint.measureText(mMinimumString) + var fontMetrics = mTextPaint.fontMetrics + //文字高度 + var textHeight = fontMetrics.bottom - fontMetrics.top + drawText( + mMinimumString, + halfWith + textWidth + textHeight / 2, + halfHeight + textHeight / 3, + mTextPaint + ) + restore() + drawText( + mRecommencedString, + halfWith - mShowResultCircleRadius / 3, + halfHeight + mShowResultCircleRadius + textHeight * 2, + mTextPaint + ) + + //画圆里面的大小刻度 + save() + var curRotation = 0 + val scaleTempY = lineStartY + mOutCircleStrokeHeight / 2 + for (i in 0 until 24) { + val y2 = if (curRotation % 90 == 0) { + mScalePaint.color = ContextCompat.getColor(mContext, R.color.data_black_color) + scaleTempY + QMUIDisplayHelper.dpToPx(6) //+240 + } else { + mScalePaint.color = ContextCompat.getColor(mContext, R.color.block_color2) + scaleTempY + } + drawLine(halfWith, lineStartY, halfWith, y2, mScalePaint) + curRotation += 15 + rotate(15f, halfWith, halfHeight) + } + restore() + + //画最外层的刻度 + mTextPaint.textSize = QMUIDisplayHelper.sp2px(mContext, 12).toFloat() + mTextPaint.color = ContextCompat.getColor(mContext, R.color.select_color3) + val cR = (min(mWidth, height) - QMUIDisplayHelper.dpToPx(10)) / 2f + for (i in 0 until 360 step 45) { + textWidth = mTextPaint.measureText("$i") + fontMetrics = mTextPaint.fontMetrics + textHeight = fontMetrics.bottom - fontMetrics.top + val cXy = getPointOnCircle(halfWith, halfHeight, cR, i.toDouble()) + if (i == 0) { + drawText("0", cXy[0] - textWidth / 2f, cXy[1] + textHeight / 2f, mTextPaint) + } else { + drawText("$i", cXy[0] - textWidth / 2f, cXy[1], mTextPaint) + } + } + mTextPaint.textSize = QMUIDisplayHelper.sp2px(mContext, 10).toFloat() + textWidth = mTextPaint.measureText("30") + fontMetrics = mTextPaint.fontMetrics + textHeight = fontMetrics.bottom - fontMetrics.top + val cXy = getPointOnCircle(halfWith, halfHeight, cR, 30.0) + drawText("30", cXy[0] - textWidth / 2f, cXy[1] + textHeight / 5f, mTextPaint) + + //画可拖动的圆 + val circleXY = getPointOnCircle( + halfWith, halfHeight, mOutCircleRadius, (mValue * mAnimatedPercent).toDouble() + ) + drawLine(halfWith, halfHeight, circleXY[0], circleXY[1], mDashedPaint) + //外层圆环形 + drawCircle( + circleXY[0], circleXY[1], mDragCircleRadius, mDragPaint + ) + val dragCircleRadius = mDragCircleRadius - mDragPaint.strokeWidth / 2 + mDragPath.reset() + mDragPath.addCircle( + circleXY[0], circleXY[1], dragCircleRadius, Path.Direction.CW + ) + mRect.apply { + left = 0 + top = 0 + right = mWidth + bottom = mHeight + } + mPathRegion.setEmpty() + //取出rect和mDragPath交集区域 + mPathRegion.setPath(mDragPath, Region(mRect)) + drawPath(mPathRegion.boundaryPath, mPathPaint) + //圆心相同的矩形 +// val rect1 = Rect( +// (halfWith - dragCircleRadius).toInt(), +// (circleY + dragCircleRadius).toInt(), +// (halfWith + dragCircleRadius).toInt(), +// (circleY - dragCircleRadius).toInt() +// ) + + //画显示值的白色圆 + mFillPaint.color = ContextCompat.getColor(mContext, R.color.white) + drawCircle(halfWith, halfHeight, mShowResultCircleRadius, mFillPaint) + + //画出值 + val valueString = "$mValue" + mTextPaint.textSize = QMUIDisplayHelper.sp2px(mContext, 18).toFloat() + mTextPaint.color = ContextCompat.getColor(mContext, R.color.data_black_color) + //文字宽度 + textWidth = mTextPaint.measureText(valueString) + drawText(valueString, halfWith - textWidth / 2, halfHeight, mTextPaint) + val minString = "min" + mTextPaint.textSize = QMUIDisplayHelper.sp2px(mContext, 12).toFloat() + mTextPaint.color = ContextCompat.getColor(mContext, R.color.block_color2) + //文字宽度 + textWidth = mTextPaint.measureText(minString) + fontMetrics = mTextPaint.fontMetrics + //文字高度 + textHeight = fontMetrics.bottom - fontMetrics.top + drawText( + minString, halfWith - textWidth / 2, halfHeight + textHeight / 1.3f, mTextPaint + ) + } + } + + /** + *求圆上某个点的坐标 + * @angle 角度是以圆的右方开始计算角度,顺时针增加 + */ + private fun getPointOnCircle( + centerX: Float, centerY: Float, radius: Float, angle: Double + ): FloatArray { + //转换角度从圆的正上方开始,顺时针增加 + return Utils.getPointOnCircleArc(centerX, centerY, radius, angle - 90) + } + + fun getValue() = mValue + + fun setValue(value: Int) { + mValue = if (value >= ConstantInt.MinSportGoal) value + else ConstantInt.DefaultSportGoal + ViewUtil.instance.setValueAnimator(0f, 100f) { + mAnimatedPercent = it.animatedValue as Float / 100 + postInvalidate() + } + } + + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent?): Boolean { + when (event!!.action) { + MotionEvent.ACTION_DOWN -> { + val x = event.x + val y = event.y + if (mPathRegion.contains(x.toInt(), y.toInt())) { + mDragCircleRadius = mShowResultCircleRadius / 2f + AppUtils.dpToPx(3.9f) + return true + } + return false + } + + MotionEvent.ACTION_MOVE -> { + val x = event.x + val y = event.y + //限制左右滑动超过 + val point = getPointOnCircle(mWidth / 2f, mHeight / 2f, mOutCircleRadius, 30.0) + //限制右半边圆部分 + if (mValue == 30) { + if (x < point[0]) return false + } + //限制左边圆部分 + else if (mValue in 270..360) { + if (x > mWidth / 2f) return false + } + //获取以圆右方为0度的角度 + val slipAngle = Utils.getAngleFromCenter(x, y, mWidth / 2f, mHeight / 2f) + mValue = (slipAngle + 90).toInt() + if (mValue in 1..30) { + mValue = 30 + return false + } else if (mValue < 0) { + mValue += 360 + } else if (mValue == 360 || mValue == 0) { + mValue = 360 + return false + } + invalidate() + } + + MotionEvent.ACTION_UP -> { + mDragCircleRadius = mShowResultCircleRadius / 2f + invalidate() + } + } + return super.onTouchEvent(event) + } + + /** + * 解决滑动冲突 + */ + override fun dispatchTouchEvent(event: MotionEvent?): Boolean { + //禁止父容器拦截子元素需要的事件。 + parent.requestDisallowInterceptTouchEvent(true) + return super.dispatchTouchEvent(event) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/SignalRadarView.kt b/app/src/main/java/com/abbidot/tracker/widget/SignalRadarView.kt new file mode 100644 index 0000000..f1f18f0 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/SignalRadarView.kt @@ -0,0 +1,283 @@ +package com.abbidot.tracker.widget + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.LinearGradient +import android.graphics.Paint +import android.graphics.RectF +import android.graphics.Shader +import android.util.AttributeSet +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.Utils +import com.abbidot.tracker.R + +/** + *Created by .yzq on 2024/3/14/014. + * @link + * @description: + */ +class SignalRadarView : View { + + private lateinit var mContext: Context + + private lateinit var mPaint: Paint + private lateinit var mScanPaint: Paint + + private lateinit var mRectF: RectF + + private var mWidth = 0 + private var mHeight = 0 + + private var mStartScanAngle = 0f + private var mMaxRadius = 0f + private var mMinRadius = 0f + private var mCircleRadius1 = 0f + private var mCircleRadius2 = 0f + private var mCircleRadius3 = 0f + private var mCircleRadius4 = 0f + + private var mCircle1Animator: ValueAnimator? = null + private var mCircle2Animator: ValueAnimator? = null + private var mCircle3Animator: ValueAnimator? = null + private var mCircle4Animator: ValueAnimator? = null + private var mScanAnimator: ValueAnimator? = null + + private lateinit var mSignalStrengthColors: IntArray + private var mValue = 0 + + //增幅 + private val mRate = 40f + + constructor(context: Context) : super(context) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context) + } + + private fun init(context: Context) { + //禁用硬件加速,防止返回到界面多次执行onDraw- + setLayerType(LAYER_TYPE_SOFTWARE, null) + + mContext = context + mPaint = Paint().apply { + strokeWidth = AppUtils.dpToPx(1f) + style = Paint.Style.STROKE + } + mScanPaint = Paint().apply { + color = ContextCompat.getColor(context, R.color.radar_color9) + } + + mSignalStrengthColors = intArrayOf( + R.color.radar_color4, + R.color.radar_color7, + R.color.radar_color5, + R.color.radar_color6, + R.color.radar_color9 + ) + + mRectF = RectF() + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mHeight = AppUtils.dpToPx(160) + mWidth = mHeight + mRectF.apply { + left = 0f + top = 0f + right = mWidth.toFloat() + bottom = mHeight.toFloat() + } + setMeasuredDimension(mWidth, mHeight) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + mMaxRadius = mHeight / 2f - mPaint.strokeWidth + mCircleRadius1 = mMaxRadius - AppUtils.dpToPx(70) + mMinRadius = mCircleRadius1 + mCircleRadius4 = mCircleRadius1 + mCircleRadius3 = mCircleRadius1 + mCircleRadius2 = mCircleRadius1 + startScanAnimator() + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + +// val circleX = mWidth / 2f +// val circleY = mHeight / 2f + + canvas.apply { +// var radius = mMaxRadius +// mPaint.style = Paint.Style.FILL +// onDrawCircle(mContext, canvas, R.color.radar_color4, radius, mPaint) +// mPaint.style = Paint.Style.STROKE +// drawLine(circleX, 0f, circleX, mHeight.toFloat(), mPaint) +// drawLine(0f, circleY, mWidth.toFloat(), circleY, mPaint) +// radius -= mPaint.strokeWidth + onDrawCircle(mContext, canvas, R.color.radar_color9, mCircleRadius4, mPaint) +// radius -= AppUtils.dpToPx(18) + onDrawCircle(mContext, canvas, R.color.radar_color9, mCircleRadius3, mPaint) +// radius -= AppUtils.dpToPx(20) + onDrawCircle(mContext, canvas, R.color.radar_color9, mCircleRadius2, mPaint) +// radius -= AppUtils.dpToPx(22) + onDrawCircle(mContext, canvas, R.color.radar_color9, mCircleRadius1, mPaint) + + val strength = (mValue * 20) + mRate + drawArc(mRectF, mStartScanAngle, strength, true, mScanPaint) + } + } + + private fun onDrawCircle( + context: Context, canvas: Canvas, colorId: Int, radius: Float, paint: Paint + ) { + paint.color = ContextCompat.getColor(context, colorId) + canvas.drawCircle(mWidth / 2f, mHeight / 2f, radius, paint) + } + + /** + * 动画 + */ + private fun startScanAnimator() { + if (null == mScanAnimator) { + mScanAnimator = ValueAnimator.ofFloat(0f, 360f).apply { + duration = 1500 + repeatCount = ValueAnimator.INFINITE + //匀速变化 + interpolator = LinearInterpolator() + addUpdateListener { + mStartScanAngle = it.animatedValue as Float + val circleX = mWidth / 2f + val circleY = mHeight / 2f + val strength = (mValue * 20) + mRate + //开始坐标与结束坐标是什么方向就以什么方向渐变 + val start = Utils.getPointOnCircleArc( + circleX, circleY, circleY, mStartScanAngle.toDouble() + strength + ) + val end = Utils.getPointOnCircleArc( + circleX, circleY, circleY, mStartScanAngle.toDouble() + ) + val shader = LinearGradient( + start[0], + start[1], + end[0], + end[1], + ContextCompat.getColor(mContext, mSignalStrengthColors[mValue]), + ContextCompat.getColor(mContext, R.color.transparent), + Shader.TileMode.CLAMP + ) + mScanPaint.shader = shader +// postInvalidate() + } + } + } + mScanAnimator?.apply { + if (!isRunning) { + LogUtil.e("木有开始") + start() + } + } + + if (null == mCircle1Animator) { + mCircle1Animator = setCircleAnimator().apply { + addUpdateListener { + mCircleRadius1 = it.animatedValue as Float + postInvalidate() + } + } + } + mCircle1Animator?.apply { + if (!isRunning) { + start() + } + } + + if (null == mCircle2Animator) { + mCircle2Animator = setCircleAnimator().apply { + addUpdateListener { + mCircleRadius2 = it.animatedValue as Float +// postInvalidate() + } + startDelay = 400 + } + } + mCircle2Animator?.apply { + if (!isRunning) { + start() + } + } + + if (null == mCircle3Animator) { + mCircle3Animator = setCircleAnimator().apply { + addUpdateListener { + mCircleRadius3 = it.animatedValue as Float +// postInvalidate() + } + startDelay = 700 + } + } + mCircle3Animator?.apply { + if (!isRunning) { + start() + } + } + + if (null == mCircle4Animator) { + mCircle4Animator = setCircleAnimator().apply { + addUpdateListener { + mCircleRadius4 = it.animatedValue as Float +// postInvalidate() + } + startDelay = 1100 + } + } + mCircle4Animator?.apply { + if (!isRunning) { + start() + } + } + } + + private fun setCircleAnimator(): ValueAnimator { + return ValueAnimator.ofFloat(mMaxRadius - AppUtils.dpToPx(70), mMaxRadius).apply { + duration = 1500L + repeatCount = ValueAnimator.INFINITE + //匀速变化 + interpolator = LinearInterpolator() + } + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + mScanAnimator?.cancel() + mCircle1Animator?.cancel() + mCircle2Animator?.cancel() + mCircle3Animator?.cancel() + mCircle4Animator?.cancel() + } + + fun setValue(value: Int) { + if (value < 0) return + mValue = value + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/SmallDotProgressView.kt b/app/src/main/java/com/abbidot/tracker/widget/SmallDotProgressView.kt new file mode 100644 index 0000000..9802af2 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/SmallDotProgressView.kt @@ -0,0 +1,122 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import androidx.core.content.ContextCompat +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.tracker.R +import com.qmuiteam.qmui.util.QMUIDisplayHelper + +/** + *Created by .yzq on 2024/1/22/022. + * @link + * @description:小圆的进度条 + */ +class SmallDotProgressView : View { + + private var mWidth = 0 + private var mHeight = 0 + + private val mRadius = AppUtils.dpToPx(2.2f) + private val mBorderSize = AppUtils.dpToPx(1f) + private val mCircleSpace = QMUIDisplayHelper.dpToPx(6) + private val mMaxProgress = 4 + private var mProgress = 1 + + private var mDefColor = 0 + private var mBorderColor = 0 + private var mProgressColor = 0 + + private lateinit var mDefPaint: Paint + private lateinit var mProgressPaint: Paint + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, attrs, defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + //禁用硬件加速,防止返回到界面多次执行onDraw + setLayerType(LAYER_TYPE_SOFTWARE, null) + + initAttrs(context, attrs) + + mDefPaint = Paint() + mDefPaint.color = mDefColor + + mProgressPaint = Paint() + mProgressPaint.color = mBorderColor + } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + mDefColor = ContextCompat.getColor(context, R.color.line_color1) + mBorderColor = ContextCompat.getColor(context, R.color.select_color) + mProgressColor = ContextCompat.getColor(context, R.color.select_color2) + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + canvas.apply { + startDraw(this) + } + } + + private fun startDraw(canvas: Canvas) { + for (i in 0 until mMaxProgress) { + val cx = mRadius + mRadius * 2 * i + mCircleSpace * i + val cy = mHeight / 2f + if (i == mProgress - 1) { + mDefPaint.color = mProgressColor + canvas.drawCircle(cx, cy, mRadius, mProgressPaint) + canvas.drawCircle(cx, cy, mRadius - mBorderSize, mDefPaint) + } else { + mDefPaint.color = mDefColor + canvas.drawCircle(cx, cy, mRadius, mDefPaint) + } + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + mHeight = (mRadius * 2).toInt() + mWidth = (mRadius * 2 * mMaxProgress + mCircleSpace * (mMaxProgress - 1)).toInt() + setMeasuredDimension(mWidth, mHeight) + +// // 获取- 网规则的模式和大小 +// val widthMode = MeasureSpec.getMode(widthMeasureSpec) +// val widthSize = MeasureSpec.getSize(widthMeasureSpec) +// // 获取高- 测量规则的模式和大小 +// val heightMode = MeasureSpec.getMode(heightMeasureSpec) +// val heightSize = MeasureSpec.getSize(heightMeasureSpec) +// LogUtil.e("$widthMode,$widthSize,,,$heightMode,$heightSize,----${MeasureSpec.AT_MOST},${MeasureSpec.EXACTLY},${MeasureSpec.UNSPECIFIED}") + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + //处理布局高度变大(如RelativeLayout,强制变宽),居中 + val newHeight = bottom - top + if (newHeight > mHeight) { + mHeight = newHeight + invalidate() + } + } + + fun setProgress(progress: Int) { + if (progress != mProgress && progress >= 0) { + mProgress = progress + invalidate() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/TypefaceButton.kt b/app/src/main/java/com/abbidot/tracker/widget/TypefaceButton.kt new file mode 100644 index 0000000..c11651f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/TypefaceButton.kt @@ -0,0 +1,32 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.layout.QMUIButton + +/** + * Created by .yzq on 2021/12/10/010. + * + * @link + * @description: 自定义button 方便设置字体 + */ +class TypefaceButton @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0) : + QMUIButton(context, attrs, defStyleAttr) { + @SuppressLint("CustomViewStyleable") + private fun initTypefaceTextView(context: Context, attrs: AttributeSet?) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefaceTextView) + val type = typedArray.getString(R.styleable.TypefaceTextView_typeface) ?: return + val typeface = ViewUtil.instance.setTypeface(context, type) + setTypeface(typeface) + typedArray.recycle() + } + + init { + initTypefaceTextView(context, attrs) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/TypefaceRoundButton.kt b/app/src/main/java/com/abbidot/tracker/widget/TypefaceRoundButton.kt new file mode 100644 index 0000000..bff29ec --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/TypefaceRoundButton.kt @@ -0,0 +1,32 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.util.AttributeSet +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton + +/** + * Created by .yzq on 2021/12/10/010. + * + * @link + * @description: 自定义椭圆button 方便设置字体 + */ +class TypefaceRoundButton @JvmOverloads constructor(context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0) : + QMUIRoundButton(context, attrs, defStyleAttr) { + @SuppressLint("CustomViewStyleable") + private fun initTypefaceTextView(context: Context, attrs: AttributeSet?) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefaceTextView) + val type = typedArray.getString(R.styleable.TypefaceTextView_typeface) ?: return + val typeface = ViewUtil.instance.setTypeface(context, type) + setTypeface(typeface) + typedArray.recycle() + } + + init { + initTypefaceTextView(context, attrs) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/TypefaceTextView.kt b/app/src/main/java/com/abbidot/tracker/widget/TypefaceTextView.kt new file mode 100644 index 0000000..9d604ad --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/TypefaceTextView.kt @@ -0,0 +1,29 @@ +package com.abbidot.tracker.widget + +import android.content.Context +import android.util.AttributeSet +import androidx.appcompat.widget.AppCompatTextView +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil + +/** + * Created by .yzq on 2021/12/10/010. + * + * @link + * @description: 自定义TextView 方便设置字体 + */ +class TypefaceTextView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : AppCompatTextView(context, attrs, defStyleAttr) { + private fun initTypefaceTextView(context: Context, attrs: AttributeSet?) { + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.TypefaceTextView) + val type = typedArray.getString(R.styleable.TypefaceTextView_typeface) ?: return + val typeface = ViewUtil.instance.setTypeface(context, type) + setTypeface(typeface) + typedArray.recycle() + } + + init { + initTypefaceTextView(context, attrs) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/VerifyEditText.kt b/app/src/main/java/com/abbidot/tracker/widget/VerifyEditText.kt new file mode 100644 index 0000000..b06f6fd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/VerifyEditText.kt @@ -0,0 +1,297 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.drawable.Drawable +import android.text.* +import android.text.InputFilter.LengthFilter +import android.text.method.HideReturnsTransformationMethod +import android.text.method.PasswordTransformationMethod +import android.util.AttributeSet +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import com.abbidot.tracker.R +import com.abbidot.tracker.util.ViewUtil +import java.util.* + +/** + * Created by SongSenior on 2021/4/16 + * 一个 EditText 和 n 个 TextView + */ +class VerifyEditText @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : LinearLayout(context, attrs, defStyleAttr) { + private val mTextViewList: MutableList = ArrayList() + private var mEditText: EditText? = null + private var drawableNormal: Drawable? = null + private var drawableSelected: Drawable? = null + private var mContext: Context? = null + + //默认 item 个数为 4 个 + private val mDefaultItemCount = 4 + + //默认每个 item 的宽度为 100 + private val mDefaultItemWidth = 100 + + //默认每个 item 的间距为 50 + private val mDefaultItemMargin = 50 + + //默认每个 item 的字体大小为 14 + private val mDefaultItemTextSize = 14 + + //默认密码明文显示时间为 200ms,之后密文显示 + private val mDefaultPasswordVisibleTime = 200 + + //是否大小写 + private var mCapitalLetter = false + + //监听编辑输入事件 + private var mOnEditClickListener: OnClickListener? = null + //输入完成监听 + private var mInputCompleteListener: InputCompleteListener? = null + + init { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + mContext = context + orientation = HORIZONTAL + gravity = Gravity.CENTER + @SuppressLint("CustomViewStyleable") val obtainStyledAttributes = + getContext().obtainStyledAttributes(attrs, R.styleable.VerifyEditText) + drawableNormal = + obtainStyledAttributes.getDrawable(R.styleable.VerifyEditText_verify_background_normal) + drawableSelected = obtainStyledAttributes.getDrawable( + R.styleable.VerifyEditText_verify_background_selected + ) + val textColor = obtainStyledAttributes.getColor( + R.styleable.VerifyEditText_verify_textColor, + ContextCompat.getColor(context, android.R.color.black) + ) + val verifyTypeface = + obtainStyledAttributes.getString(R.styleable.VerifyEditText_verify_typeface) + val typeface = ViewUtil.instance.setTypeface(context, verifyTypeface) + + var count = obtainStyledAttributes.getInt( + R.styleable.VerifyEditText_verify_count, mDefaultItemCount + ) + val inputType = obtainStyledAttributes.getInt( + R.styleable.VerifyEditText_verify_inputType, InputType.TYPE_CLASS_NUMBER + ) + val passwordVisibleTime = obtainStyledAttributes.getInt( + R.styleable.VerifyEditText_verify_password_visible_time, mDefaultPasswordVisibleTime + ) + val width = obtainStyledAttributes.getDimension( + R.styleable.VerifyEditText_verify_width, mDefaultItemWidth.toFloat() + ).toInt() + val height = + obtainStyledAttributes.getDimension(R.styleable.VerifyEditText_verify_height, 0f) + .toInt() + val margin = obtainStyledAttributes.getDimension( + R.styleable.VerifyEditText_verify_margin, mDefaultItemMargin.toFloat() + ).toInt() + val textSize = px2sp( + context, obtainStyledAttributes.getDimension( + R.styleable.VerifyEditText_verify_textSize, + sp2px(context, mDefaultItemTextSize.toFloat()).toFloat() + ) + ).toFloat() + val password = + obtainStyledAttributes.getBoolean(R.styleable.VerifyEditText_verify_password, false) + mCapitalLetter = + obtainStyledAttributes.getBoolean(R.styleable.VerifyEditText_capital_letter, false) + obtainStyledAttributes.recycle() + if (count < 2) count = 2 //最少 2 个 item + mEditText = EditText(context) + mEditText!!.inputType = inputType + mEditText!!.layoutParams = LayoutParams(1, 1) + mEditText!!.isCursorVisible = false + mEditText!!.background = null + mEditText!!.filters = arrayOf(LengthFilter(count)) //限制输入长度为 count + mEditText!!.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + val textView = mTextViewList[start] //获取对应的 textview + if (before == 0) { + //输入 + val input = s.subSequence(start, s.length) + //获取新输入的字 + textView.text = if (mCapitalLetter) { + //字母转换为大写 + input.toString().uppercase(Locale.getDefault()) + } else { + input + } + + if (password) { //如果需要密文显示 + textView.transformationMethod = + HideReturnsTransformationMethod.getInstance() + //passwordVisibleTime 毫秒后设置为密文显示 + textView.postDelayed({ + textView.transformationMethod = + PasswordTransformationMethod.getInstance() + }, passwordVisibleTime.toLong()) + } + setTextViewBackground(textView, drawableSelected) + } else { + //删除 + textView.text = "" + setTextViewBackground(textView, drawableNormal) + } + if (mInputCompleteListener != null && s.length == mTextViewList.size) mInputCompleteListener!!.complete( + s.toString() + ) + mOnEditClickListener?.onClick(this@VerifyEditText) + } + + override fun afterTextChanged(s: Editable) {} + }) + addView(mEditText) + //点击弹出软键盘 + setOnClickListener { v: View? -> + mEditText!!.requestFocus() + showSoftKeyBoard() + } + //遍历生成 textview + for (i in 0 until count) { + val textView = TextView(context) + textView.textSize = textSize + textView.filters = arrayOf(LengthFilter(1)) + textView.gravity = Gravity.CENTER + textView.setTextColor(textColor) + textView.typeface = typeface + val layoutParams = LayoutParams( + width, if (height == 0) ViewGroup.LayoutParams.WRAP_CONTENT else height + ) + if (i == 0) layoutParams.leftMargin = -1 else layoutParams.leftMargin = margin + textView.layoutParams = layoutParams + setTextViewBackground(textView, drawableNormal) + addView(textView) + mTextViewList.add(textView) + } + } + + /** + * view 添加到窗口时,延迟 500ms 弹出软键盘 + */ + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mEditText!!.postDelayed({ showSoftKeyBoard() }, 500) + } + + /** + * 设置背景 + * + * @param textView + * @param drawable + */ + private fun setTextViewBackground(textView: TextView, drawable: Drawable?) { + if (drawable != null) textView.background = drawable + } + + /** + * 设置背景颜色 + */ + fun setBoxBackground(drawable: Drawable?) { + for (textView in mTextViewList) { + setTextViewBackground(textView, drawable) + } + } + + fun setEditClickListener(onClickListener: OnClickListener) { + mOnEditClickListener = onClickListener + } + + /** + * 获取当前输入的内容 + * + * @return + */ + val content: String + get() { + val text = mEditText!!.text + return if (TextUtils.isEmpty(text)) "" else { + if (mCapitalLetter) { + //字母转换为大写 + mEditText!!.text.toString().uppercase(Locale.getDefault()) + } else mEditText!!.text.toString() + } + } + + /** + * 清除内容 + */ + fun clearContent() { + mEditText!!.setText("") + for (i in mTextViewList.indices) { + val textView = mTextViewList[i] + textView.text = "" + setTextViewBackground(textView, drawableNormal) + } + } + + /** + * 设置默认的内容 + * + * @param content + */ + fun setDefaultContent(content: String) { + mEditText!!.setText(content) + mEditText!!.requestFocus() + val chars = content.toCharArray() + val min = chars.size.coerceAtMost(mTextViewList.size) + for (i in 0 until min) { + val aChar = chars[i] + val s = aChar.toString() + val textView = mTextViewList[i] + textView.text = s + setTextViewBackground(textView, drawableSelected) + } + if (mInputCompleteListener != null && min == mTextViewList.size) mInputCompleteListener!!.complete( + content.substring(0, min) + ) + } + + /** + * 显示软键盘 + */ + private fun showSoftKeyBoard() { + val imm = mContext!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(mEditText, InputMethodManager.SHOW_FORCED) + } + + /** + * 添加输入完成的监听 + * + * @param inputCompleteListener + */ + fun addInputCompleteListener(inputCompleteListener: InputCompleteListener?) { + mInputCompleteListener = inputCompleteListener + val content = mEditText!!.text + if (!TextUtils.isEmpty(content) && content.toString().length == mTextViewList.size) { + mInputCompleteListener!!.complete(content.toString()) + } + } + + interface InputCompleteListener { + fun complete(content: String?) + } + + private fun px2sp(context: Context, pxValue: Float): Int { + val fontScale = context.resources.displayMetrics.scaledDensity + return (pxValue / fontScale + 0.5f).toInt() + } + + private fun sp2px(context: Context, spValue: Float): Int { + val fontScale = context.resources.displayMetrics.scaledDensity + return (spValue * fontScale + 0.5f).toInt() + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/VerticalBottomToTopSeekBar.kt b/app/src/main/java/com/abbidot/tracker/widget/VerticalBottomToTopSeekBar.kt new file mode 100644 index 0000000..7ecf2d6 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/VerticalBottomToTopSeekBar.kt @@ -0,0 +1,76 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.appcompat.widget.AppCompatSeekBar + +/** + * Created by .yzq on 2022/1/6/006. + * + * @link + * @description: 垂直滑动滚动条,从下往上滑动 + */ +class VerticalBottomToTopSeekBar : AppCompatSeekBar { + constructor(context: Context) : super(context) {} + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, + defStyleAttr) { + } + + /** + *onDraw事件中旋转画布,由水平变垂直 + */ + @Synchronized + override fun onDraw(canvas: Canvas) { + canvas.rotate(-90f) + canvas.translate(-height.toFloat(), 0f) + super.onDraw(canvas) + } + + /** + * measure事件中重新计算控件尺寸,对换宽高值 + */ + @Synchronized + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val width = measuredWidth + val height = measuredHeight + setMeasuredDimension(width, height) + } + + /** + * 切记加上尺寸变更时的事件,变更对换宽高值 + */ + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(h, w, oldh, oldw) + } + + /** + * 最后重载—下滑动事件,重新计算progress值 + */ + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!isEnabled) return false + when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> { + if (max <= 0) return true + //因为我这是向上滑动的,所以最大值乘Y轴的滑动比例得到上面的取值,实际值再用最大值去减即可 + progress = max - (max * event.y / height).toInt() + onSizeChanged(width, height, 0, 0) + } + } + return true + } + + /** + * 调用setProgress(int progress)失效,滚动条没有变化 + */ + @Synchronized + override fun setProgress(progress: Int) { + super.setProgress(progress) + onSizeChanged(width, height, 0, 0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/VerticalTopToBottomSeekBar.kt b/app/src/main/java/com/abbidot/tracker/widget/VerticalTopToBottomSeekBar.kt new file mode 100644 index 0000000..cbc702f --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/VerticalTopToBottomSeekBar.kt @@ -0,0 +1,98 @@ +package com.abbidot.tracker.widget + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.appcompat.widget.AppCompatSeekBar +import com.abbidot.baselibrary.util.LogUtil + +/** + * Created by .yzq on 2022/1/6/006. + * + * @link + * @description: 垂直滑动滚动条,从上往下滑动 + */ +class VerticalTopToBottomSeekBar : AppCompatSeekBar { + + private var mOnSeekBarChangeListener: OnSeekBarChangeListener? = null + + constructor(context: Context) : super(context) {} + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {} + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, + defStyleAttr) { + } + + /** + *onDraw事件中旋转画布,由水平变垂直 + */ + @Synchronized + override fun onDraw(canvas: Canvas) { + canvas.rotate(90f) + canvas.translate(0f, -width.toFloat()) + super.onDraw(canvas) + } + + /** + * measure事件中重新计算控件尺寸,对换宽高值 + */ + @Synchronized + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val width = measuredWidth + val height = measuredHeight + setMeasuredDimension(width, height) + } + + /** + * 切记加上尺寸变更时的事件,变更对换宽高值 + */ + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(h, w, oldh, oldw) + } + + /** + * 最后重载—下滑动事件,重新计算progress值 + */ + @SuppressLint("ClickableViewAccessibility") + override fun onTouchEvent(event: MotionEvent): Boolean { + if (!isEnabled) return false + when (event.action) { + MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> { + if (max <= 0) return true + //因为我这是向下滑动的,所以最大值乘Y轴的滑动比例得到上面值即可 + progress = (max * event.y / height).toInt() + LogUtil.d("$progress") + onSizeChanged(width, height, 0, 0) + if (null != mOnSeekBarChangeListener) { + mOnSeekBarChangeListener!!.onProgressChanged(progress) + //手指停止按屏幕,离开屏幕 + if (event.action == MotionEvent.ACTION_UP) { + mOnSeekBarChangeListener!!.onStopChanged(progress) + } + } + } + } + return true + } + + /** + * 调用setProgress(int progress)失效,滚动条没有变化 + */ + @Synchronized + override fun setProgress(progress: Int) { + super.setProgress(progress) + onSizeChanged(width, height, 0, 0) + } + + fun setOnSeekBarChangeProgressListener(l: OnSeekBarChangeListener) { + mOnSeekBarChangeListener = l + } + + + interface OnSeekBarChangeListener { + fun onProgressChanged(progress: Int) + fun onStopChanged(progress: Int) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/DateItem.java b/app/src/main/java/com/abbidot/tracker/widget/calender/DateItem.java new file mode 100644 index 0000000..d0a6ef8 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/DateItem.java @@ -0,0 +1,103 @@ +package com.abbidot.tracker.widget.calender; + +/** + * Created by ZZ on 2016/3/16. + */ +public class DateItem { + + private int day; + private int week; + private int year; + private int month; + private boolean isLastMonth; + private boolean isNextMonth; + private boolean selected; + private boolean current; + //是否是未到的日期。大于今天 + private boolean isFuture; + + public boolean isCurrent() { + return current; + } + + public boolean isFuture() { + return isFuture; + } + + public void setFuture(boolean future) { + isFuture = future; + } + + public void setCurrent(boolean current) { + this.current = current; + } + + public boolean isSelected() { + return selected; + } + + public void setSelected(boolean selected) { + this.selected = selected; + } + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public int getMonth() { + return month; + } + + public void setMonth(int month) { + this.month = month; + } + + public boolean isLastMonth() { + return isLastMonth; + } + + public void setIsLastMonth(boolean isLastMonth) { + this.isLastMonth = isLastMonth; + } + + public boolean isNextMonth() { + return isNextMonth; + } + + public void setIsNextMonth(boolean isNextMonth) { + this.isNextMonth = isNextMonth; + } + + public int getDay() { + return day; + } + + public void setDay(int day) { + this.day = day; + } + + public int getWeek() { + return week; + } + + public void setWeek(int week) { + this.week = week; + } + + @Override + public String toString() { + return "DateItem{" + + "day=" + day + + ", week=" + week + + ", year=" + year + + ", month=" + month + + ", isLastMonth=" + isLastMonth + + ", isNextMonth=" + isNextMonth + + ", selected=" + selected + + '}' + "\n"; + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/DateUtil.java b/app/src/main/java/com/abbidot/tracker/widget/calender/DateUtil.java new file mode 100644 index 0000000..58caa1e --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/DateUtil.java @@ -0,0 +1,108 @@ +package com.abbidot.tracker.widget.calender; + +import android.util.Log; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +/** + * Created by zz on 2016/1/13. + */ +public class DateUtil { + + public static String getYearMonthDay() { + return new SimpleDateFormat("yyyy/MM/dd", Locale.getDefault()).format(new Date()); + } + + public static String getYearMonthDayHourMinuteSecond() { + return new SimpleDateFormat("yyyy/MM/dd HH:mm:ss", Locale.getDefault()).format(new Date()); + } + + public static String getHourMinuteSecond() { + return new SimpleDateFormat("HH:mm:ss", Locale.getDefault()).format(new Date()); + } + + public static int getCurrentYear() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.YEAR); + } + + public static int getCurrentMonth() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.MONTH)+1; + } + + public static int getCurrentDay() { + Calendar calendar = Calendar.getInstance(); + return calendar.get(Calendar.DAY_OF_MONTH); + } + + public static int getCurrentWeek() { + return calWeek(getCurrentYear(), getCurrentMonth(), getCurrentDay()); + } + + public static int calWeek(int y, int m, int d) { + int day = calPassDay(y, m, d); + Log.i("TAG", y+"-"+m+"-"+d+",passDay="+day); + int s = (y-1)+(y-1)/4-(y-1)/100+(y-1)/400+day; + return s%7; + } + + public static int calPassDay(int y, int m, int d) { + int passDay = 0; + for (int i=0; i<=m-1; i++) { + passDay += calDayOfMonth(y, i); + } + return passDay+d; + } + + public static int calDayOfMonth(int y, int m) { + int day = 0; + switch (m) { + case 12: + day = 31; + break; + case 11: + day = 30; + break; + case 10: + day = 31; + break; + case 9: + day = 30; + break; + case 8: + day = 31; + break; + case 7: + day = 31; + break; + case 6: + day = 30; + break; + case 5: + day = 31; + break; + case 4: + day = 30; + break; + case 3: + day = 31; + break; + case 2: + day = isLeapYear(y)?29:28; + break; + case 1: + day = 31; + break; + } + return day; + } + + public static boolean isLeapYear(int y) { + return y % 4 == 0 && y % 100 != 0 || y % 400 == 0; + } + +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/DayAdapter.java b/app/src/main/java/com/abbidot/tracker/widget/calender/DayAdapter.java new file mode 100644 index 0000000..d9b3b4c --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/DayAdapter.java @@ -0,0 +1,187 @@ +package com.abbidot.tracker.widget.calender; + +import android.content.Context; +import android.graphics.drawable.GradientDrawable; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.abbidot.tracker.R; + +import java.util.List; + + +/** + * Created by user999 on 2016/6/7. + */ +public class DayAdapter extends RecyclerView.Adapter { + + private List datas; + private Context context; + private int dayTextColorSelected; + private int dayTextColorNormal; + private int daySelectionColor; + private int weekTextColor; + private OnItemClick itemClick; + private int pressShapeSelectorId; + private int todayPointColor; + + public DayAdapter(Context context, List datas, int dayTextColorSelected, + int dayTextColorNormal, int daySelectionColor, int weekTextColor, + int pressShapeSelectorId, int todayPointColor) { + this.datas = datas; + this.context = context; + this.dayTextColorSelected = dayTextColorSelected; + this.dayTextColorNormal = dayTextColorNormal; + this.daySelectionColor = daySelectionColor; + this.weekTextColor = weekTextColor; + this.pressShapeSelectorId = pressShapeSelectorId; + this.todayPointColor = todayPointColor; + } + + public void setDatas(List datas) { + this.datas = datas; + } + + public List getData() { + return datas; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = View.inflate(parent.getContext(), R.layout.list_item_day, null); + ViewUtil.scaleContentView(view.findViewById(R.id.list_item_root_layout)); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + DateItem item = datas.get(position); + int week = item.getWeek(); + holder.tvWeek.setTextSize(12); + switch (week) { + case 0: + holder.tvWeek.setText("SUN"); + break; + case 1: + holder.tvWeek.setText("MON"); + break; + case 2: + holder.tvWeek.setText("TUE"); + break; + case 3: + holder.tvWeek.setText("WED"); + break; + case 4: + holder.tvWeek.setText("THU"); + break; + case 5: + holder.tvWeek.setText("FRI"); + break; + case 6: + holder.tvWeek.setText("SAT"); + break; + } + GradientDrawable p = (GradientDrawable) holder.tvDay.getBackground(); + if (p != null) { + if (item.isSelected()) { + p.setColor(daySelectionColor); + } else { + p.setColor(ContextCompat.getColor(context, R.color.sleep_time_gray_color)); + } + } else { + } + GradientDrawable t = (GradientDrawable) holder.tvPoint.getBackground(); + if (t != null) { + if (item.isCurrent()) { + t.setColor(todayPointColor); + } else { + t.setColor(0x00000000); + } + } else { + } + + holder.rootView.setBackgroundResource(pressShapeSelectorId); + holder.tvWeek.setTextColor(weekTextColor); + holder.tvDay.setText(item.getDay() + ""); + + holder.tvPoint.setVisibility(item.isCurrent() ? View.VISIBLE : View.INVISIBLE); + + if (item.isFuture()) { + holder.tvDay.setTextColor(ContextCompat.getColor(context, + R.color.sleep_time_gray_color2)); + holder.rootView.setOnClickListener(null); + } else { + holder.tvDay.setTextColor(item.isSelected() ? dayTextColorSelected : + dayTextColorNormal); + holder.rootView.setOnClickListener(v -> { + if (itemClick != null) { + itemClick.onItemClick(datas.get(position).getYear(), + datas.get(position).getMonth(), datas.get(position).getDay(), + datas.get(position).getWeek()); + } + }); + } + } + + public void setPressShapeSelectorId(int pressShapeSelectorId) { + this.pressShapeSelectorId = pressShapeSelectorId; + notifyDataSetChanged(); + } + + public void setDayTextColorSelected(int dayTextColorSelected) { + this.dayTextColorSelected = dayTextColorSelected; + notifyDataSetChanged(); + } + + public void setDayTextColorNormal(int dayTextColorNormal) { + this.dayTextColorNormal = dayTextColorNormal; + notifyDataSetChanged(); + } + + public void setDaySelectionColor(int daySelectionColor) { + this.daySelectionColor = daySelectionColor; + notifyDataSetChanged(); + } + + public void setWeekTextColor(int weekTextColor) { + this.weekTextColor = weekTextColor; + notifyDataSetChanged(); + } + + public void setTodayPointColor(int todayPointColor) { + this.todayPointColor = todayPointColor; + notifyDataSetChanged(); + } + + public void setItemClick(OnItemClick itemClick) { + this.itemClick = itemClick; + } + + public interface OnItemClick { + void onItemClick(int year, int month, int day, int week); + } + + @Override + public int getItemCount() { + return datas == null ? 0 : datas.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + public View rootView; + public TextView tvWeek; + public TextView tvDay; + public View tvPoint; + + public ViewHolder(View itemView) { + super(itemView); + rootView = itemView.findViewById(R.id.list_item_root_layout); + tvWeek = itemView.findViewById(R.id.item_week); + tvDay = itemView.findViewById(R.id.item_day); + tvPoint = itemView.findViewById(R.id.item_point); + } + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/DayV2Adapter.java b/app/src/main/java/com/abbidot/tracker/widget/calender/DayV2Adapter.java new file mode 100644 index 0000000..94c5f77 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/DayV2Adapter.java @@ -0,0 +1,215 @@ +package com.abbidot.tracker.widget.calender; + +import android.content.Context; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.abbidot.tracker.R; + +import java.util.List; + + +/** + * Created by user999 on 2016/6/7. + */ +public class DayV2Adapter extends RecyclerView.Adapter { + + private List datas; + private Context context; + private int dayTextColorSelected; + private int dayTextColorNormal; + private int daySelectionColor; + private int daySelectionDrawable = 0; + private int weekTextColor; + private OnItemClick itemClick; + private int pressShapeSelectorId; + private int todayPointColor; + private int todayBgDrawable = 0; + private int todayTextColor = 0; + + public DayV2Adapter(Context context, List datas, int dayTextColorSelected, + int dayTextColorNormal, int daySelectionColor, int weekTextColor, + int pressShapeSelectorId, int todayPointColor) { + this.datas = datas; + this.context = context; + this.dayTextColorSelected = dayTextColorSelected; + this.dayTextColorNormal = dayTextColorNormal; + this.daySelectionColor = daySelectionColor; + this.weekTextColor = weekTextColor; + this.pressShapeSelectorId = pressShapeSelectorId; + this.todayPointColor = todayPointColor; + } + + public void setDatas(List datas) { + this.datas = datas; + } + + public List getData() { + return datas; + } + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = View.inflate(parent.getContext(), R.layout.item_horizontal_calender_day, null); + // ViewUtil.scaleContentView(view.findViewById(R.id.list_item_root_layout_v2)); + return new ViewHolder(view); + } + + @Override + public void onBindViewHolder(ViewHolder holder, final int position) { + DateItem item = datas.get(position); + int week = item.getWeek(); + holder.tvWeek.setTextSize(12); + switch (week) { + case 0: + holder.tvWeek.setText("Sun"); + break; + case 1: + holder.tvWeek.setText("Mon"); + break; + case 2: + holder.tvWeek.setText("Tue"); + break; + case 3: + holder.tvWeek.setText("Wed"); + break; + case 4: + holder.tvWeek.setText("Thu"); + break; + case 5: + holder.tvWeek.setText("Fri"); + break; + case 6: + holder.tvWeek.setText("Sat"); + break; + } + // GradientDrawable p = (GradientDrawable) holder.tvDay.getBackground(); + // if (p != null) { + // if (item.isSelected()) { + // p.setColor(daySelectionColor); + // } else { + // //"日"默认的背景颜色 + // p.setColor(ContextCompat.getColor(context, R.color.transparent)); + // } + // } else { + // } + if (item.isSelected()) { + if (item.isCurrent()) { + //不显示"今天" + // holder.tvCurrentDay.setVisibility(View.VISIBLE); + // holder.tvDay.setVisibility(View.INVISIBLE); + // holder.tvWeek.setVisibility(View.INVISIBLE); + holder.tvCurrentDay.setVisibility(View.GONE); + holder.tvDay.setVisibility(View.VISIBLE); + holder.tvWeek.setVisibility(View.VISIBLE); + } else { + holder.tvCurrentDay.setVisibility(View.GONE); + holder.tvDay.setVisibility(View.VISIBLE); + holder.tvWeek.setVisibility(View.VISIBLE); + } + if (daySelectionDrawable == 0) { + holder.tvDay.setBackgroundResource(R.drawable.shape22_green_yellow_bg); + } else + holder.tvDay.setBackgroundResource(daySelectionDrawable); + } else { + //"日"默认的背景颜色 + holder.tvDay.setBackgroundResource(R.color.transparent); + + holder.tvCurrentDay.setVisibility(View.GONE); + holder.tvDay.setVisibility(View.VISIBLE); + holder.tvWeek.setVisibility(View.VISIBLE); + } + + + // GradientDrawable t = (GradientDrawable) holder.tvPoint.getBackground(); + // if (t != null) { + // if (item.isCurrent()) { + // t.setColor(todayPointColor); + // } else { + // t.setColor(0x00000000); + // } + // } else { + // } + + holder.rootView.setBackgroundResource(pressShapeSelectorId); + holder.tvWeek.setTextColor(weekTextColor); + holder.tvDay.setText(item.getDay() + ""); + + // holder.tvPoint.setVisibility(item.isCurrent() ? View.VISIBLE : View.INVISIBLE); + + if (item.isFuture()) { + holder.tvDay.setTextColor(ContextCompat.getColor(context, + R.color.sleep_time_gray_color2)); + holder.rootView.setOnClickListener(null); + } else { + holder.tvDay.setTextColor(item.isSelected() ? dayTextColorSelected : + dayTextColorNormal); + holder.rootView.setOnClickListener(v -> { + if (itemClick != null) { + itemClick.onItemClick(datas.get(position).getYear(), + datas.get(position).getMonth(), datas.get(position).getDay(), + datas.get(position).getWeek()); + } + }); + } + + if (todayBgDrawable > 0) { + holder.tvCurrentDay.setBackgroundResource(todayBgDrawable); + } else { + + } + if (todayTextColor > 0) { + holder.tvCurrentDay.setTextColor(ContextCompat.getColor(context,todayTextColor)); + } else { + + } + } + + public void setItemClick(OnItemClick itemClick) { + this.itemClick = itemClick; + } + + public interface OnItemClick { + void onItemClick(int year, int month, int day, int week); + } + + @Override + public int getItemCount() { + return datas == null ? 0 : datas.size(); + } + + public static class ViewHolder extends RecyclerView.ViewHolder { + public View rootView; + public TextView tvWeek; + public TextView tvDay; + // public View tvPoint; + + public TextView tvCurrentDay; + + public ViewHolder(View itemView) { + super(itemView); + rootView = itemView.findViewById(R.id.list_item_root_layout_v2); + tvWeek = itemView.findViewById(R.id.tv_item_calender_week); + tvDay = itemView.findViewById(R.id.tv_item_calender_day); + tvCurrentDay = itemView.findViewById(R.id.tv_item_calender_today); + // tvPoint = itemView.findViewById(R.id.item_point); + } + } + + public void setDaySelectionDrawable(int selectionDrawable) { + if (selectionDrawable > 0) { + daySelectionDrawable = selectionDrawable; + notifyDataSetChanged(); + } + } + + public void setTodaySelectionBgTextColor(int selectionDrawable, int textColor) { + todayBgDrawable = selectionDrawable; + todayTextColor = textColor; + notifyDataSetChanged(); + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderView.java b/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderView.java new file mode 100644 index 0000000..826fc63 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderView.java @@ -0,0 +1,596 @@ +package com.abbidot.tracker.widget.calender; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.NumberPicker; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.abbidot.tracker.R; +import com.abbidot.tracker.adapter.SpaceItemDecoration; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 水平滑动日历,获取当月日期天数 + * Created by zz on 2016/9/14. + * + */ +public class HorizontalCalenderView extends FrameLayout { + + + private View rootView; + private RecyclerView rv; + private LinearLayout llYear; + private LinearLayout llMonth; + private TextView tvYear; + private TextView tvMonth; + private TextView tvYearUnit; + private TextView tvMonthUnit; + + private DayAdapter adapter; + + private int minYear; + + private int curYear; + + private int curMonth; + + private int curDay; + + private int curWeek; + + private int selectedYear; + + private int selectedMonth; + + private int selectedDay; + + private int selectedWeek; + + private int firstWeek; + + private int curMonthLastDay; + + private OnDaySelectedListener daySelectedListener; + + private OnYearMonthClickListener onYearMonthClickListener; + + private boolean showPickDialog; + private int unitColor; + private int yearTextColor; + private int monthTextColor; + + private int dayTextColorSelected; + private int dayTextColorNormal; + private int daySelectionColor; + private int weekTextColor; + + private int pressShapeSelectorId; + private int todayPointColor; + + public HorizontalCalenderView(Context context) { + super(context); + init(context, null); + } + + public HorizontalCalenderView(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public HorizontalCalenderView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + + initAttrs(context, attrs); + + rootView = LayoutInflater.from(context).inflate(R.layout.horizontal_calender_view, null); + + rv = rootView.findViewById(R.id.rv); + llYear = rootView.findViewById(R.id.ll_year); + llMonth = rootView.findViewById(R.id.ll_month); + tvYear = rootView.findViewById(R.id.tv_year); + tvMonth = rootView.findViewById(R.id.tv_month); + tvYearUnit = rootView.findViewById(R.id.tv_year_unit); + tvMonthUnit = rootView.findViewById(R.id.tv_month_unit); + + initData(context); + + initEvent(); + + addView(rootView); + } + + private void initAttrs(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalCalenderView); + showPickDialog = a.getBoolean(R.styleable.HorizontalCalenderView_zhc_show_pick_dialog, + true); + minYear = a.getInteger(R.styleable.HorizontalCalenderView_zhc_min_year, 2012); + unitColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_unit_color, 0xff3498DB); + yearTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_year_text_color, + 0xff000000); + monthTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_month_text_color, + 0xff000000); + dayTextColorSelected = + a.getColor(R.styleable.HorizontalCalenderView_zhc_day_selected_text_color, + 0xffffffff); + dayTextColorNormal = + a.getColor(R.styleable.HorizontalCalenderView_zhc_day_unselected_text_color, + 0xff000000); + weekTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_week_text_color, + 0xff9e9e9e); + daySelectionColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_selection_color, + 0xff3498DB); + //默认背景颜色 + pressShapeSelectorId = + a.getResourceId(R.styleable.HorizontalCalenderView_zhc_press_shape_selector, + R.color.blue); + todayPointColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_today_point_color, + 0xffed9f24); + a.recycle(); + } + + private void initData(Context context) { + curYear = DateUtil.getCurrentYear(); + curMonth = DateUtil.getCurrentMonth(); + curDay = DateUtil.getCurrentDay(); + curWeek = DateUtil.getCurrentWeek(); + selectedYear = curYear; + selectedMonth = curMonth; + selectedDay = curDay; + selectedWeek = curWeek; + + tvYear.setText(curYear + ""); + tvMonth.setText(curMonth + ""); + + List items = getItems(); + adapter = new DayAdapter(context, items, dayTextColorSelected, dayTextColorNormal, + daySelectionColor, weekTextColor, pressShapeSelectorId, todayPointColor); + adapter.setItemClick(new DayAdapter.OnItemClick() { + @Override + public void onItemClick(int year, int month, int day, int week) { + setSelectedDate(year, month, day, week); + } + }); + LinearLayoutManager manager = new LinearLayoutManager(context); + manager.setOrientation(LinearLayoutManager.HORIZONTAL); + rv.setLayoutManager(manager); + rv.addItemDecoration(new SpaceItemDecoration(20, 20, 0, 0)); + rv.setHasFixedSize(true); + rv.offsetChildrenHorizontal(3); + rv.setAdapter(adapter); + rv.scrollToPosition(curDay - 4); + } + + /** + * 获取日期item + * + * @return + */ + private List getItems() { + List items = new ArrayList<>(); + firstWeek = getFirstDayOfWeek(selectedYear, selectedMonth); + int tempWeek = firstWeek; + + int lastMonthDay = getLastDayOfMonth(selectedYear, selectedMonth) - firstWeek; + curMonthLastDay = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + int nextMonthDay = 1; + + for (int i = 0; i < 42; i++) { + DateItem item = new DateItem(); + if (i < firstWeek) { + lastMonthDay++; + item.setIsLastMonth(true); + item.setDay(lastMonthDay); + } else { + item.setIsLastMonth(false); + int d = i - firstWeek + 1; + if (d <= curMonthLastDay) { + item.setIsNextMonth(false); + if (d == selectedDay) { + item.setSelected(true); + } + item.setDay(i - firstWeek + 1); + item.setWeek(tempWeek % 7); + item.setYear(selectedYear); + item.setMonth(selectedMonth); + if (selectedYear == curYear && selectedMonth == curMonth) { + if (d == curDay) + item.setCurrent(true); + else if (d > curDay) + item.setFuture(true); + } + items.add(item); + tempWeek++; + } else { + item.setIsNextMonth(true); + item.setDay(nextMonthDay); + nextMonthDay++; + } + } + } + return items; + } + + public void setUnitColorResId(int colorResId) { + this.unitColor = getContext().getResources().getColor(colorResId); + setUnitColor(this.unitColor); + } + + public void setUnitColor(int colorValue) { + if (tvYearUnit != null) + tvYearUnit.setTextColor(colorValue); + if (tvMonthUnit != null) + tvMonthUnit.setTextColor(colorValue); + } + + public void setYearTextColorResId(int yearTextColorResId) { + this.yearTextColor = getContext().getResources().getColor(yearTextColorResId); + setYearTextColor(yearTextColor); + } + + public void setTodayPointColorResId(int todayPointColorResId) { + this.todayPointColor = getContext().getResources().getColor(todayPointColorResId); + setTodayPointColor(todayPointColor); + } + + public void setTodayPointColor(int todayPointColor) { + adapter.setTodayPointColor(todayPointColor); + } + + public void setYearTextColor(int yearTextColor) { + if (tvYear != null) + tvYear.setTextColor(yearTextColor); + } + + + public void setMonthTextColorResId(int monthTextColorResId) { + this.monthTextColor = getContext().getResources().getColor(monthTextColorResId); + setMonthTextColor(monthTextColor); + } + + public void setMonthTextColor(int monthTextColor) { + if (tvMonth != null) + tvMonth.setTextColor(monthTextColor); + } + + public void setWeekTextColorResId(int weekTextColorResId) { + this.weekTextColor = getContext().getResources().getColor(weekTextColorResId); + setWeekTextColor(weekTextColor); + } + + public void setWeekTextColor(int weekTextColor) { + adapter.setWeekTextColor(weekTextColor); + } + + public void setDaySelectionColorResId(int daySelectionColorResId) { + this.daySelectionColor = getContext().getResources().getColor(daySelectionColorResId); + setDaySelectionColor(daySelectionColor); + } + + public void setDayTextColorSelectedResId(int dayTextColorSelectedRedId) { + this.dayTextColorSelected = getContext().getResources().getColor(dayTextColorSelectedRedId); + setDayTextColorSelected(dayTextColorSelected); + } + + public void setDayTextColorSelected(int dayTextColorSelected) { + adapter.setDayTextColorSelected(dayTextColorSelected); + } + + public void setDayTextColorNormalResId(int dayTextColorNormalRedId) { + this.dayTextColorNormal = getContext().getResources().getColor(dayTextColorNormalRedId); + setDayTextColorNormal(dayTextColorNormal); + } + + public void setDayTextColorNormal(int dayTextColorNormal) { + adapter.setDayTextColorNormal(dayTextColorNormal); + } + + public void setDaySelectionColor(int daySelectionColor) { + adapter.setDaySelectionColor(daySelectionColor); + } + + private void initEvent() { + llYear.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (showPickDialog) { + LinearLayout ll = new LinearLayout(getContext()); + + final NumberPicker yearPicker = new NumberPicker(getContext()); + yearPicker.setMinValue(minYear); + yearPicker.setMaxValue(DateUtil.getCurrentYear()); + yearPicker.setValue(selectedYear); + + final NumberPicker monthPicker = new NumberPicker(getContext()); + monthPicker.setMinValue(1); + monthPicker.setMaxValue(12); + monthPicker.setValue(selectedMonth); + + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(Gravity.CENTER); + ll.addView(yearPicker); + ll.addView(monthPicker); + + new AlertDialog.Builder(getContext()).setView(ll) + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + int year = yearPicker.getValue(); + int month = monthPicker.getValue(); + setSelectedYearAndMonth(year, month); + } + }).setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + } + }).show(); + } + if (onYearMonthClickListener != null) { + onYearMonthClickListener.onYearClick(selectedYear, selectedMonth); + } + } + }); + + llMonth.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (showPickDialog) { + LinearLayout ll = new LinearLayout(getContext()); + + final NumberPicker yearPicker = new NumberPicker(getContext()); + yearPicker.setMinValue(minYear); + yearPicker.setMaxValue(DateUtil.getCurrentYear()); + yearPicker.setValue(selectedYear); + final NumberPicker monthPicker = new NumberPicker(getContext()); + monthPicker.setMinValue(1); + monthPicker.setMaxValue(12); + monthPicker.setValue(selectedMonth); + + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(Gravity.CENTER); + ll.addView(yearPicker); + ll.addView(monthPicker); + + monthPicker.requestFocus(); + + new AlertDialog.Builder(getContext()).setView(ll) + .setPositiveButton("确定", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + int year = yearPicker.getValue(); + int month = monthPicker.getValue(); + setSelectedYearAndMonth(year, month); + } + }).setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + } + }).show(); + } + if (onYearMonthClickListener != null) { + onYearMonthClickListener.onMonthClick(selectedYear, selectedMonth); + } + } + }); + } + + public boolean isShowPickDialog() { + return showPickDialog; + } + + public void setShowPickDialog(boolean showPickDialog) { + this.showPickDialog = showPickDialog; + } + + public int getFirstDayOfWeek(int y, int m) { + return DateUtil.calWeek(y, m, 1); + } + + public int getLastDayOfMonth(int y, int m) { + if (m == 1) { + return DateUtil.calDayOfMonth(y - 1, 12); + } else { + return DateUtil.calDayOfMonth(y, m - 1); + } + } + + public interface OnYearMonthClickListener { + void onYearClick(int selectedYear, int selectedMonth); + + void onMonthClick(int selectedYear, int selectedMonth); + } + + public interface OnDaySelectedListener { + void onSelected(boolean hasChanged, int year, int month, int day, int week, DateItem items); + } + + public String setSelectedDate() { + return String.format(selectedYear + "-" + selectedMonth + "-" + selectedDay); + } + + public int getSelectedYear() { + return selectedYear; + } + + public int getSelectedMonth() { + return selectedMonth; + } + + public int getSelectedDay() { + return selectedDay; + } + + public int getSelectedWeek() { + return selectedWeek; + } + + public void setSelectedDate(int year, int month, int day) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + int lastDay = selectedDay; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedDay = day; + this.selectedWeek = DateUtil.calWeek(selectedYear, selectedMonth, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), day); + daySelectedListener.onSelected(lastYear != year || lastMonth != month || lastDay != day, year, month, day, selectedWeek, dateItem); + } + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + /** + * 补齐0的日期 + * + * @param separated 日期分隔符 + * @param year + * @param month + * @param day + * @return + */ + public String fillingDate(String separated, int year, int month, int day) { + return year + separated + (month < 10 ? "0" + month : month + "") + separated + (day < 10 ? "0" + day : day + ""); + } + + /** + * 根据日期找到该item下所有熟悉变量 + * + * @param items + * @param day + * @return + */ + private DateItem getDayFindItem(List items, int day) { + DateItem dateItem = null; + for (int i = 0; i < items.size(); i++) { + if (items.get(i).getDay() == day) { + dateItem = items.get(i); + break; + } + } + return dateItem; + } + + private void setSelectedDate(int year, int month, int day, int week) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + int lastDay = selectedDay; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedDay = day; + this.selectedWeek = week; + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + if (daySelectedListener != null) { + DateItem dateItem = getDayFindItem(adapter.getData(), day); + daySelectedListener.onSelected(lastYear != year || lastMonth != month || lastDay != day, year, month, day, selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setPressShapeSelectorId(int pressShapeSelectorId) { + this.pressShapeSelectorId = pressShapeSelectorId; + if (llYear != null) + llYear.setBackgroundResource(pressShapeSelectorId); + if (llMonth != null) { + llMonth.setBackgroundResource(pressShapeSelectorId); + } + adapter.setPressShapeSelectorId(pressShapeSelectorId); + } + + public void setSelectedYear(int year) { + int lastYear = selectedYear; + this.selectedYear = year; + this.selectedWeek = DateUtil.calWeek(year, selectedMonth, selectedDay); + tvYear.setText(year + ""); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null) { + DateItem dateItem = getDayFindItem(adapter.getData(), selectedDay); + daySelectedListener.onSelected(lastYear != year, year, selectedMonth, selectedDay, + selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setSelectedYearAndMonth(int year, int month) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedWeek = DateUtil.calWeek(selectedYear, selectedMonth, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), selectedDay); + daySelectedListener.onSelected(lastYear != year || lastMonth != month, year, month, + selectedDay, selectedWeek, dateItem); + } + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setSelectedMonth(int month) { + int lastMonth = this.selectedMonth; + this.selectedMonth = month; + tvMonth.setText(month + ""); + this.selectedWeek = DateUtil.calWeek(selectedYear, month, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), selectedDay); + daySelectedListener.onSelected(lastMonth != month, selectedYear, month, selectedDay, + selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public String getSelectedDate() { + return selectedYear + "-" + selectedMonth + "-" + selectedDay; + } + + public String getSelectedDate(String gapStr) { + return selectedYear + gapStr + selectedMonth + gapStr + selectedDay; + } + + public void setOnDaySelectedListener(OnDaySelectedListener daySelectedListener) { + this.daySelectedListener = daySelectedListener; + } + + public void setOnYearMonthClickListener(OnYearMonthClickListener onYearMonthClickListener) { + this.onYearMonthClickListener = onYearMonthClickListener; + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderViewV1.java b/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderViewV1.java new file mode 100644 index 0000000..1713c0b --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderViewV1.java @@ -0,0 +1,644 @@ +package com.abbidot.tracker.widget.calender; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.NumberPicker; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.abbidot.tracker.R; +import com.abbidot.tracker.adapter.SpaceItemDecoration; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 水平滑动日历,获取最近30天日期天数 + */ +public class HorizontalCalenderViewV1 extends FrameLayout { + + private View rootView; + private RecyclerView rv; + private LinearLayout llYear; + private LinearLayout llMonth; + private TextView tvYear; + private TextView tvMonth; + private TextView tvYearUnit; + private TextView tvMonthUnit; + + private DayAdapter adapter; + + private int minYear; + + private int curYear; + + private int curMonth; + + private int curDay; + + private int curWeek; + + private int selectedYear; + + private int selectedMonth; + + private int selectedDay; + + private int selectedWeek; + + private int firstWeek; + + private int curMonthLastDay; + + private OnDaySelectedListener daySelectedListener; + + private OnYearMonthClickListener onYearMonthClickListener; + + private boolean showPickDialog; + private int unitColor; + private int yearTextColor; + private int monthTextColor; + + private int dayTextColorSelected; + private int dayTextColorNormal; + private int daySelectionColor; + private int weekTextColor; + + private int pressShapeSelectorId; + private int todayPointColor; + + public HorizontalCalenderViewV1(Context context) { + super(context); + init(context, null); + } + + public HorizontalCalenderViewV1(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public HorizontalCalenderViewV1(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + + initAttrs(context, attrs); + + rootView = LayoutInflater.from(context).inflate(R.layout.horizontal_calender_view, null); + + rv = rootView.findViewById(R.id.rv); + llYear = rootView.findViewById(R.id.ll_year); + llMonth = rootView.findViewById(R.id.ll_month); + tvYear = rootView.findViewById(R.id.tv_year); + tvMonth = rootView.findViewById(R.id.tv_month); + tvYearUnit = rootView.findViewById(R.id.tv_year_unit); + tvMonthUnit = rootView.findViewById(R.id.tv_month_unit); + + initData(context); + + initEvent(); + + addView(rootView); + } + + private void initAttrs(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalCalenderView); + showPickDialog = a.getBoolean(R.styleable.HorizontalCalenderView_zhc_show_pick_dialog, + true); + minYear = a.getInteger(R.styleable.HorizontalCalenderView_zhc_min_year, 2012); + unitColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_unit_color, 0xff3498DB); + yearTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_year_text_color, + 0xff000000); + monthTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_month_text_color, + 0xff000000); + dayTextColorSelected = + a.getColor(R.styleable.HorizontalCalenderView_zhc_day_selected_text_color, + 0xffffffff); + dayTextColorNormal = + a.getColor(R.styleable.HorizontalCalenderView_zhc_day_unselected_text_color, + 0xff000000); + weekTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_week_text_color, + 0xff9e9e9e); + daySelectionColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_selection_color, + 0xff3498DB); + //默认背景颜色 + pressShapeSelectorId = + a.getResourceId(R.styleable.HorizontalCalenderView_zhc_press_shape_selector, + R.color.blue); + todayPointColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_today_point_color, + 0xffed9f24); + a.recycle(); + } + + private void initData(Context context) { + curYear = DateUtil.getCurrentYear(); + curMonth = DateUtil.getCurrentMonth(); + curDay = DateUtil.getCurrentDay(); + curWeek = DateUtil.getCurrentWeek(); + selectedYear = curYear; + selectedMonth = curMonth; + selectedDay = curDay; + selectedWeek = curWeek; + + tvYear.setText(curYear + ""); + tvMonth.setText(curMonth + ""); + + //获取当月的日期 + // List items = getItems(); + //获取最近30天的日期 + List items = getLast30DaysItems(); + adapter = new DayAdapter(context, items, dayTextColorSelected, dayTextColorNormal, + daySelectionColor, weekTextColor, pressShapeSelectorId, todayPointColor); + adapter.setItemClick(new DayAdapter.OnItemClick() { + @Override + public void onItemClick(int year, int month, int day, int week) { + setSelectedDate(year, month, day, week); + } + }); + LinearLayoutManager manager = new LinearLayoutManager(context); + manager.setOrientation(LinearLayoutManager.HORIZONTAL); + rv.setLayoutManager(manager); + rv.addItemDecoration(new SpaceItemDecoration(20, 20, 0, 0)); + rv.setHasFixedSize(true); + rv.offsetChildrenHorizontal(3); + rv.setAdapter(adapter); + + DateItem dateItem = getDayFindItem(adapter.getData(), curMonth, curDay); + rv.scrollToPosition(items.indexOf(dateItem) - 3); + } + + /** + * 获取最近30天日期item + * + * @return + */ + private List getLast30DaysItems() { + List last30DaysItem = new ArrayList<>(); + List currentMonthItem = getItems(); + int pastDayIndex = 0; + for (int i = 0; i < currentMonthItem.size(); i++) { + if (!currentMonthItem.get(i).isFuture()) { + pastDayIndex++; + } + } + //获取最近多少天的日期 + int lastDays = 30; + if (pastDayIndex < lastDays) { + //处理当前是1月的情况 + int lastMonth = curMonth - 1; + int year = curYear; + if (lastMonth <= 0) { + lastMonth = 12; + year--; + } + List lastMonthItem = getMonthItems(year, lastMonth); + lastMonthItem = lastMonthItem.subList(pastDayIndex, lastMonthItem.size()); + last30DaysItem.addAll(lastMonthItem); + } + last30DaysItem.addAll(currentMonthItem); + return last30DaysItem; + } + + /** + * 获取日期item + * + * @return + */ + private List getMonthItems(int year, int month) { + List items = new ArrayList<>(); + int firstWeek = getFirstDayOfWeek(year, month); + int tempWeek = firstWeek; + + int lastMonthDay = getLastDayOfMonth(year, month) - firstWeek; + int curMonthLastDay = DateUtil.calDayOfMonth(year, month); + int nextMonthDay = 1; + + for (int i = 0; i < 42; i++) { + DateItem item = new DateItem(); + if (i < firstWeek) { + lastMonthDay++; + item.setIsLastMonth(true); + item.setDay(lastMonthDay); + } else { + item.setIsLastMonth(false); + int d = i - firstWeek + 1; + if (d <= curMonthLastDay) { + item.setIsNextMonth(false); + if (month == selectedMonth && d == selectedDay) { + item.setSelected(true); + } + item.setDay(i - firstWeek + 1); + item.setWeek(tempWeek % 7); + item.setYear(year); + item.setMonth(month); + if (year == curYear && month == curMonth) { + if (d == curDay) + item.setCurrent(true); + else if (d > curDay) + item.setFuture(true); + } + items.add(item); + tempWeek++; + } else { + item.setIsNextMonth(true); + item.setDay(nextMonthDay); + nextMonthDay++; + } + } + } + return items; + } + + /** + * 获取日期item + * + * @return + */ + private List getItems() { + firstWeek = getFirstDayOfWeek(selectedYear, selectedMonth); + curMonthLastDay = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + + return getMonthItems(selectedYear, selectedMonth); + } + + public void setUnitColorResId(int colorResId) { + this.unitColor = getContext().getResources().getColor(colorResId); + setUnitColor(this.unitColor); + } + + public void setUnitColor(int colorValue) { + if (tvYearUnit != null) + tvYearUnit.setTextColor(colorValue); + if (tvMonthUnit != null) + tvMonthUnit.setTextColor(colorValue); + } + + public void setYearTextColorResId(int yearTextColorResId) { + this.yearTextColor = getContext().getResources().getColor(yearTextColorResId); + setYearTextColor(yearTextColor); + } + + public void setTodayPointColorResId(int todayPointColorResId) { + this.todayPointColor = getContext().getResources().getColor(todayPointColorResId); + setTodayPointColor(todayPointColor); + } + + public void setTodayPointColor(int todayPointColor) { + adapter.setTodayPointColor(todayPointColor); + } + + public void setYearTextColor(int yearTextColor) { + if (tvYear != null) + tvYear.setTextColor(yearTextColor); + } + + + public void setMonthTextColorResId(int monthTextColorResId) { + this.monthTextColor = getContext().getResources().getColor(monthTextColorResId); + setMonthTextColor(monthTextColor); + } + + public void setMonthTextColor(int monthTextColor) { + if (tvMonth != null) + tvMonth.setTextColor(monthTextColor); + } + + public void setWeekTextColorResId(int weekTextColorResId) { + this.weekTextColor = getContext().getResources().getColor(weekTextColorResId); + setWeekTextColor(weekTextColor); + } + + public void setWeekTextColor(int weekTextColor) { + adapter.setWeekTextColor(weekTextColor); + } + + public void setDaySelectionColorResId(int daySelectionColorResId) { + this.daySelectionColor = getContext().getResources().getColor(daySelectionColorResId); + setDaySelectionColor(daySelectionColor); + } + + public void setDayTextColorSelectedResId(int dayTextColorSelectedRedId) { + this.dayTextColorSelected = getContext().getResources().getColor(dayTextColorSelectedRedId); + setDayTextColorSelected(dayTextColorSelected); + } + + public void setDayTextColorSelected(int dayTextColorSelected) { + adapter.setDayTextColorSelected(dayTextColorSelected); + } + + public void setDayTextColorNormalResId(int dayTextColorNormalRedId) { + this.dayTextColorNormal = getContext().getResources().getColor(dayTextColorNormalRedId); + setDayTextColorNormal(dayTextColorNormal); + } + + public void setDayTextColorNormal(int dayTextColorNormal) { + adapter.setDayTextColorNormal(dayTextColorNormal); + } + + public void setDaySelectionColor(int daySelectionColor) { + adapter.setDaySelectionColor(daySelectionColor); + } + + private void initEvent() { + llYear.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (showPickDialog) { + LinearLayout ll = new LinearLayout(getContext()); + + final NumberPicker yearPicker = new NumberPicker(getContext()); + yearPicker.setMinValue(minYear); + yearPicker.setMaxValue(DateUtil.getCurrentYear()); + yearPicker.setValue(selectedYear); + + final NumberPicker monthPicker = new NumberPicker(getContext()); + monthPicker.setMinValue(1); + monthPicker.setMaxValue(12); + monthPicker.setValue(selectedMonth); + + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(Gravity.CENTER); + ll.addView(yearPicker); + ll.addView(monthPicker); + + new AlertDialog.Builder(getContext()).setView(ll).setPositiveButton("确定", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + int year = yearPicker.getValue(); + int month = monthPicker.getValue(); + setSelectedYearAndMonth(year, month); + } + }).setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + } + }).show(); + } + if (onYearMonthClickListener != null) { + onYearMonthClickListener.onYearClick(selectedYear, selectedMonth); + } + } + }); + + llMonth.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View view) { + if (showPickDialog) { + LinearLayout ll = new LinearLayout(getContext()); + + final NumberPicker yearPicker = new NumberPicker(getContext()); + yearPicker.setMinValue(minYear); + yearPicker.setMaxValue(DateUtil.getCurrentYear()); + yearPicker.setValue(selectedYear); + final NumberPicker monthPicker = new NumberPicker(getContext()); + monthPicker.setMinValue(1); + monthPicker.setMaxValue(12); + monthPicker.setValue(selectedMonth); + + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(Gravity.CENTER); + ll.addView(yearPicker); + ll.addView(monthPicker); + + monthPicker.requestFocus(); + + new AlertDialog.Builder(getContext()).setView(ll).setPositiveButton("确定", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + int year = yearPicker.getValue(); + int month = monthPicker.getValue(); + setSelectedYearAndMonth(year, month); + } + }).setNegativeButton("取消", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + } + }).show(); + } + if (onYearMonthClickListener != null) { + onYearMonthClickListener.onMonthClick(selectedYear, selectedMonth); + } + } + }); + } + + public boolean isShowPickDialog() { + return showPickDialog; + } + + public void setShowPickDialog(boolean showPickDialog) { + this.showPickDialog = showPickDialog; + } + + public int getFirstDayOfWeek(int y, int m) { + return DateUtil.calWeek(y, m, 1); + } + + public int getLastDayOfMonth(int y, int m) { + if (m == 1) { + return DateUtil.calDayOfMonth(y - 1, 12); + } else { + return DateUtil.calDayOfMonth(y, m - 1); + } + } + + public interface OnYearMonthClickListener { + void onYearClick(int selectedYear, int selectedMonth); + + void onMonthClick(int selectedYear, int selectedMonth); + } + + public interface OnDaySelectedListener { + void onSelected(boolean hasChanged, int year, int month, int day, int week, DateItem items); + } + + public String setSelectedDate() { + return String.format(selectedYear + "-" + selectedMonth + "-" + selectedDay); + } + + public int getSelectedYear() { + return selectedYear; + } + + public int getSelectedMonth() { + return selectedMonth; + } + + public int getSelectedDay() { + return selectedDay; + } + + public int getSelectedWeek() { + return selectedWeek; + } + + public void setSelectedDate(int year, int month, int day) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + int lastDay = selectedDay; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedDay = day; + this.selectedWeek = DateUtil.calWeek(selectedYear, selectedMonth, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), month, day); + daySelectedListener.onSelected(lastYear != year || lastMonth != month || lastDay != day, year, month, day, selectedWeek, dateItem); + } + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + /** + * 补齐0的日期 + * + * @param separated 日期分隔符 + * @param year + * @param month + * @param day + * @return + */ + public String fillingDate(String separated, int year, int month, int day) { + return year + separated + (month < 10 ? "0" + month : month + "") + separated + (day < 10 ? "0" + day : day + ""); + } + + /** + * 根据日期找到该item下所有属性变量 + * + * @param items + * @param day + * @return + */ + private DateItem getDayFindItem(List items, int month, int day) { + DateItem dateItem = null; + for (int i = 0; i < items.size(); i++) { + items.get(i).setSelected(false); + if (items.get(i).getDay() == day && items.get(i).getMonth() == month) { + dateItem = items.get(i); + dateItem.setSelected(true); + // break; + } + } + return dateItem; + } + + private void setSelectedDate(int year, int month, int day, int week) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + int lastDay = selectedDay; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedDay = day; + this.selectedWeek = week; + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + DateItem dateItem = getDayFindItem(adapter.getData(), month, day); + if (daySelectedListener != null) { + daySelectedListener.onSelected(lastYear != year || lastMonth != month || lastDay != day, year, month, day, selectedWeek, dateItem); + } + + adapter.notifyDataSetChanged(); + rv.scrollToPosition(adapter.getData().indexOf(dateItem) - 3); + rv.smoothScrollToPosition(adapter.getData().indexOf(dateItem) + 3); + } + + public void setPressShapeSelectorId(int pressShapeSelectorId) { + this.pressShapeSelectorId = pressShapeSelectorId; + if (llYear != null) + llYear.setBackgroundResource(pressShapeSelectorId); + if (llMonth != null) { + llMonth.setBackgroundResource(pressShapeSelectorId); + } + adapter.setPressShapeSelectorId(pressShapeSelectorId); + } + + public void setSelectedYear(int year) { + int lastYear = selectedYear; + this.selectedYear = year; + this.selectedWeek = DateUtil.calWeek(year, selectedMonth, selectedDay); + tvYear.setText(year + ""); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null) { + DateItem dateItem = getDayFindItem(adapter.getData(), selectedMonth, selectedDay); + daySelectedListener.onSelected(lastYear != year, year, selectedMonth, selectedDay, + selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setSelectedYearAndMonth(int year, int month) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedWeek = DateUtil.calWeek(selectedYear, selectedMonth, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), selectedMonth, selectedDay); + daySelectedListener.onSelected(lastYear != year || lastMonth != month, year, month, + selectedDay, selectedWeek, dateItem); + } + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setSelectedMonth(int month) { + int lastMonth = this.selectedMonth; + this.selectedMonth = month; + tvMonth.setText(month + ""); + this.selectedWeek = DateUtil.calWeek(selectedYear, month, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), month, selectedDay); + daySelectedListener.onSelected(lastMonth != month, selectedYear, month, selectedDay, + selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public String getSelectedDate() { + return selectedYear + "-" + selectedMonth + "-" + selectedDay; + } + + public String getSelectedDate(String gapStr) { + return selectedYear + gapStr + selectedMonth + gapStr + selectedDay; + } + + public void setOnDaySelectedListener(OnDaySelectedListener daySelectedListener) { + this.daySelectedListener = daySelectedListener; + } + + public void setOnYearMonthClickListener(OnYearMonthClickListener onYearMonthClickListener) { + this.onYearMonthClickListener = onYearMonthClickListener; + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderViewV2.java b/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderViewV2.java new file mode 100644 index 0000000..7dbf740 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/HorizontalCalenderViewV2.java @@ -0,0 +1,685 @@ +package com.abbidot.tracker.widget.calender; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.NumberPicker; +import android.widget.TextView; + +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.abbidot.tracker.R; +import com.abbidot.tracker.adapter.SpaceItemDecoration; +import com.qmuiteam.qmui.util.QMUIDisplayHelper; + +import java.util.ArrayList; +import java.util.List; + + +/** + * 水平滑动日历,获取最近30天日期天数 + */ +public class HorizontalCalenderViewV2 extends FrameLayout { + + private View rootView; + private RecyclerView rv; + private LinearLayout llYear; + private LinearLayout llMonth; + private TextView tvYear; + private TextView tvMonth; + private TextView tvYearUnit; + private TextView tvMonthUnit; + + private DayV2Adapter adapter; + + private int minYear; + + private int curYear; + + private int curMonth; + + private int curDay; + + private int curWeek; + + private int selectedYear; + + private int selectedMonth; + + private int selectedDay; + + private int selectedWeek; + + private int firstWeek; + + private int curMonthLastDay; + + private OnDaySelectedListener daySelectedListener; + + private OnYearMonthClickListener onYearMonthClickListener; + + private boolean showPickDialog; + private int unitColor; + private int yearTextColor; + private int monthTextColor; + + private int dayTextColorSelected; + private int dayTextColorNormal; + private int daySelectionColor; + private int weekTextColor; + + private int pressShapeSelectorId; + private int todayPointColor; + + public HorizontalCalenderViewV2(Context context) { + super(context); + init(context, null); + } + + public HorizontalCalenderViewV2(Context context, AttributeSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public HorizontalCalenderViewV2(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + + initAttrs(context, attrs); + + rootView = LayoutInflater.from(context).inflate(R.layout.horizontal_calender_view, null); + + rv = rootView.findViewById(R.id.rv); + llYear = rootView.findViewById(R.id.ll_year); + llMonth = rootView.findViewById(R.id.ll_month); + tvYear = rootView.findViewById(R.id.tv_year); + tvMonth = rootView.findViewById(R.id.tv_month); + tvYearUnit = rootView.findViewById(R.id.tv_year_unit); + tvMonthUnit = rootView.findViewById(R.id.tv_month_unit); + + initData(context); + + initEvent(); + + addView(rootView); + } + + private void initAttrs(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.HorizontalCalenderView); + showPickDialog = a.getBoolean(R.styleable.HorizontalCalenderView_zhc_show_pick_dialog, + true); + minYear = a.getInteger(R.styleable.HorizontalCalenderView_zhc_min_year, 2012); + unitColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_unit_color, 0xff3498DB); + yearTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_year_text_color, + 0xff000000); + monthTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_month_text_color, + 0xff000000); + dayTextColorSelected = + a.getColor(R.styleable.HorizontalCalenderView_zhc_day_selected_text_color, + 0xffffffff); + dayTextColorNormal = + a.getColor(R.styleable.HorizontalCalenderView_zhc_day_unselected_text_color, + 0xff000000); + weekTextColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_week_text_color, + 0xff9e9e9e); + daySelectionColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_selection_color, + 0xff3498DB); + //默认背景颜色 + pressShapeSelectorId = + a.getResourceId(R.styleable.HorizontalCalenderView_zhc_press_shape_selector, + R.color.blue); + todayPointColor = a.getColor(R.styleable.HorizontalCalenderView_zhc_today_point_color, + 0xffed9f24); + a.recycle(); + } + + private void initData(Context context) { + curYear = DateUtil.getCurrentYear(); + curMonth = DateUtil.getCurrentMonth(); + curDay = DateUtil.getCurrentDay(); + curWeek = DateUtil.getCurrentWeek(); + selectedYear = curYear; + selectedMonth = curMonth; + selectedDay = curDay; + selectedWeek = curWeek; + + tvYear.setText(curYear + ""); + tvMonth.setText(curMonth + ""); + + //获取当月的日期 + // List items = getItems(); + //获取最近365天的日期 + List items = getLast365DaysItems(); + adapter = new DayV2Adapter(context, items, dayTextColorSelected, dayTextColorNormal, + daySelectionColor, weekTextColor, pressShapeSelectorId, todayPointColor); + adapter.setItemClick((year, month, day, week) -> { + setSelectedDate(year, month, day, week); + }); + LinearLayoutManager manager = new LinearLayoutManager(context); + manager.setOrientation(LinearLayoutManager.HORIZONTAL); + rv.setLayoutManager(manager); + rv.addItemDecoration(new SpaceItemDecoration(QMUIDisplayHelper.dpToPx(5), + QMUIDisplayHelper.dpToPx(5), 0, 0)); + rv.setHasFixedSize(true); + rv.offsetChildrenHorizontal(3); + rv.setAdapter(adapter); + + DateItem dateItem = getDayFindItem(adapter.getData(), selectedYear, curMonth, curDay); + rv.scrollToPosition(items.indexOf(dateItem) - 3); + } + + /** + * 获取最近30天日期item + * + * @return + */ + private List getLast30DaysItems() { + List last30DaysItem = new ArrayList<>(); + List currentMonthItem = getItems(); + int pastDayIndex = 0; + for (int i = 0; i < currentMonthItem.size(); i++) { + if (!currentMonthItem.get(i).isFuture()) { + pastDayIndex++; + } + } + //获取最近多少天的日期 + int lastDays = 30; + if (pastDayIndex < lastDays) { + //处理当前是1月的情况 + int lastMonth = curMonth - 1; + int year = curYear; + if (lastMonth <= 0) { + lastMonth = 12; + year--; + } + List lastMonthItem = getMonthItems(year, lastMonth); + lastMonthItem = lastMonthItem.subList(pastDayIndex, lastMonthItem.size()); + last30DaysItem.addAll(lastMonthItem); + } + last30DaysItem.addAll(currentMonthItem); + return last30DaysItem; + } + + /** + * 获取最近365天日期item + * + * @return + */ + private List getLast365DaysItems() { + List last30DaysItem = new ArrayList<>(); + List currentMonthItem = getItems(); + int pastDayIndex = 0; + for (int i = 0; i < currentMonthItem.size(); i++) { + if (!currentMonthItem.get(i).isFuture()) { + pastDayIndex++; + } + } + int lastMonth = curMonth; + int year = curYear - 1; + while (lastMonth != curMonth || year != curYear) { + List lastMonthItem = getMonthItems(year, lastMonth); + last30DaysItem.addAll(lastMonthItem); + lastMonth++; + if (lastMonth == 13) { + lastMonth = 1; + year++; + } + } + last30DaysItem = last30DaysItem.subList(pastDayIndex, last30DaysItem.size()); + last30DaysItem.addAll(currentMonthItem); + return last30DaysItem; + } + + /** + * 获取日期item + * + * @return + */ + private List getMonthItems(int year, int month) { + List items = new ArrayList<>(); + int firstWeek = getFirstDayOfWeek(year, month); + int tempWeek = firstWeek; + + int lastMonthDay = getLastDayOfMonth(year, month) - firstWeek; + int curMonthLastDay = DateUtil.calDayOfMonth(year, month); + int nextMonthDay = 1; + + for (int i = 0; i < 42; i++) { + DateItem item = new DateItem(); + if (i < firstWeek) { + lastMonthDay++; + item.setIsLastMonth(true); + item.setDay(lastMonthDay); + } else { + item.setIsLastMonth(false); + int d = i - firstWeek + 1; + if (d <= curMonthLastDay) { + item.setIsNextMonth(false); + if (month == selectedMonth && d == selectedDay) { + item.setSelected(true); + } + item.setDay(i - firstWeek + 1); + item.setWeek(tempWeek % 7); + item.setYear(year); + item.setMonth(month); + if (year == curYear && month == curMonth) { + if (d == curDay) + item.setCurrent(true); + else if (d > curDay) + item.setFuture(true); + } + items.add(item); + tempWeek++; + } else { + item.setIsNextMonth(true); + item.setDay(nextMonthDay); + nextMonthDay++; + } + } + } + return items; + } + + /** + * 获取日期item + * + * @return + */ + private List getItems() { + firstWeek = getFirstDayOfWeek(selectedYear, selectedMonth); + curMonthLastDay = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + + return getMonthItems(selectedYear, selectedMonth); + } + + public void setUnitColorResId(int colorResId) { + this.unitColor = getContext().getResources().getColor(colorResId); + setUnitColor(this.unitColor); + } + + public void setUnitColor(int colorValue) { + if (tvYearUnit != null) + tvYearUnit.setTextColor(colorValue); + if (tvMonthUnit != null) + tvMonthUnit.setTextColor(colorValue); + } + + public void setYearTextColorResId(int yearTextColorResId) { + this.yearTextColor = getContext().getResources().getColor(yearTextColorResId); + setYearTextColor(yearTextColor); + } + + // public void setTodayPointColorResId(int todayPointColorResId) { + // this.todayPointColor = getContext().getResources().getColor(todayPointColorResId); + // setTodayPointColor(todayPointColor); + // } + + // public void setTodayPointColor(int todayPointColor) { + // adapter.setTodayPointColor(todayPointColor); + // } + + public void setYearTextColor(int yearTextColor) { + if (tvYear != null) + tvYear.setTextColor(yearTextColor); + } + + + public void setMonthTextColorResId(int monthTextColorResId) { + this.monthTextColor = getContext().getResources().getColor(monthTextColorResId); + setMonthTextColor(monthTextColor); + } + + public void setMonthTextColor(int monthTextColor) { + if (tvMonth != null) + tvMonth.setTextColor(monthTextColor); + } + + // public void setWeekTextColorResId(int weekTextColorResId) { + // this.weekTextColor = getContext().getResources().getColor(weekTextColorResId); + // setWeekTextColor(weekTextColor); + // } + + // public void setWeekTextColor(int weekTextColor) { + // adapter.setWeekTextColor(weekTextColor); + // } + + // public void setDaySelectionColorResId(int daySelectionColorResId) { + // this.daySelectionColor = getContext().getResources().getColor(daySelectionColorResId); + // setDaySelectionColor(daySelectionColor); + // } + + // public void setDayTextColorSelectedResId(int dayTextColorSelectedRedId) { + // this.dayTextColorSelected = getContext().getResources().getColor + // (dayTextColorSelectedRedId); + // setDayTextColorSelected(dayTextColorSelected); + // } + + // public void setDayTextColorSelected(int dayTextColorSelected) { + // adapter.setDayTextColorSelected(dayTextColorSelected); + // } + + // public void setDayTextColorNormalResId(int dayTextColorNormalRedId) { + // this.dayTextColorNormal = getContext().getResources().getColor(dayTextColorNormalRedId); + // setDayTextColorNormal(dayTextColorNormal); + // } + + // public void setDayTextColorNormal(int dayTextColorNormal) { + // adapter.setDayTextColorNormal(dayTextColorNormal); + // } + // + // public void setDaySelectionColor(int daySelectionColor) { + // adapter.setDaySelectionColor(daySelectionColor); + // } + + private void initEvent() { + llYear.setOnClickListener(view -> { + if (showPickDialog) { + LinearLayout ll = new LinearLayout(getContext()); + + final NumberPicker yearPicker = new NumberPicker(getContext()); + yearPicker.setMinValue(minYear); + yearPicker.setMaxValue(DateUtil.getCurrentYear()); + yearPicker.setValue(selectedYear); + + final NumberPicker monthPicker = new NumberPicker(getContext()); + monthPicker.setMinValue(1); + monthPicker.setMaxValue(12); + monthPicker.setValue(selectedMonth); + + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(Gravity.CENTER); + ll.addView(yearPicker); + ll.addView(monthPicker); + + new AlertDialog.Builder(getContext()).setView(ll) + .setPositiveButton("确定", (dialogInterface, i) -> { + int year = yearPicker.getValue(); + int month = monthPicker.getValue(); + setSelectedYearAndMonth(year, month); + }).setNegativeButton("取消", (dialogInterface, i) -> { + }).show(); + } + if (onYearMonthClickListener != null) { + onYearMonthClickListener.onYearClick(selectedYear, selectedMonth); + } + }); + + llMonth.setOnClickListener(view -> { + if (showPickDialog) { + LinearLayout ll = new LinearLayout(getContext()); + + final NumberPicker yearPicker = new NumberPicker(getContext()); + yearPicker.setMinValue(minYear); + yearPicker.setMaxValue(DateUtil.getCurrentYear()); + yearPicker.setValue(selectedYear); + final NumberPicker monthPicker = new NumberPicker(getContext()); + monthPicker.setMinValue(1); + monthPicker.setMaxValue(12); + monthPicker.setValue(selectedMonth); + + ll.setOrientation(LinearLayout.HORIZONTAL); + ll.setGravity(Gravity.CENTER); + ll.addView(yearPicker); + ll.addView(monthPicker); + + monthPicker.requestFocus(); + + new AlertDialog.Builder(getContext()).setView(ll) + .setPositiveButton("确定", (dialogInterface, i) -> { + int year = yearPicker.getValue(); + int month = monthPicker.getValue(); + setSelectedYearAndMonth(year, month); + }).setNegativeButton("取消", (dialogInterface, i) -> { + }).show(); + } + if (onYearMonthClickListener != null) { + onYearMonthClickListener.onMonthClick(selectedYear, selectedMonth); + } + }); + } + + public boolean isShowPickDialog() { + return showPickDialog; + } + + public void setShowPickDialog(boolean showPickDialog) { + this.showPickDialog = showPickDialog; + } + + public int getFirstDayOfWeek(int y, int m) { + return DateUtil.calWeek(y, m, 1); + } + + public int getLastDayOfMonth(int y, int m) { + if (m == 1) { + return DateUtil.calDayOfMonth(y - 1, 12); + } else { + return DateUtil.calDayOfMonth(y, m - 1); + } + } + + public interface OnYearMonthClickListener { + void onYearClick(int selectedYear, int selectedMonth); + + void onMonthClick(int selectedYear, int selectedMonth); + } + + public interface OnDaySelectedListener { + void onSelected(boolean hasChanged, int year, int month, int day, int week, DateItem items); + } + + public String setSelectedDate() { + return String.format(selectedYear + "-" + selectedMonth + "-" + selectedDay); + } + + public int getSelectedYear() { + return selectedYear; + } + + public int getSelectedMonth() { + return selectedMonth; + } + + public int getSelectedDay() { + return selectedDay; + } + + public int getSelectedWeek() { + return selectedWeek; + } + + public void setSelectedDate(int year, int month, int day) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + int lastDay = selectedDay; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedDay = day; + this.selectedWeek = DateUtil.calWeek(selectedYear, selectedMonth, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + + setSelectedDate(year, month, day, selectedWeek); + + // if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + // DateItem dateItem = getDayFindItem(adapter.getData(), month, day); + // daySelectedListener.onSelected(lastYear != year || lastMonth != month || lastDay + // != day, year, month, day, selectedWeek, dateItem); + // } + // tvYear.setText(year + ""); + // tvMonth.setText(month + ""); + // // adapter.setDatas(getItems()); + // adapter.notifyDataSetChanged(); + // //自动滑动到显示的日期 + // int position=0; + // List dateItemList=adapter.getData(); + // for (int i=0;i items, int year, int month, int day) { + DateItem dateItem = null; + for (int i = 0; i < items.size(); i++) { + items.get(i).setSelected(false); + if (items.get(i).getYear() == year && items.get(i).getDay() == day && items.get(i) + .getMonth() == month) { + dateItem = items.get(i); + dateItem.setSelected(true); + // break; + } + } + return dateItem; + } + + private void setSelectedDate(int year, int month, int day, int week) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + int lastDay = selectedDay; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedDay = day; + this.selectedWeek = week; + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + DateItem dateItem = getDayFindItem(adapter.getData(), year, month, day); + if (daySelectedListener != null) { + daySelectedListener.onSelected(lastYear != year || lastMonth != month || lastDay != day, year, month, day, selectedWeek, dateItem); + } + + adapter.notifyDataSetChanged(); + int index = adapter.getData().indexOf(dateItem); + int position = index - 3; + if (position < 0) + position = 0; + rv.scrollToPosition(position); + rv.smoothScrollToPosition(index + 3); + } + + // public void setPressShapeSelectorId(int pressShapeSelectorId) { + // this.pressShapeSelectorId = pressShapeSelectorId; + // if (llYear != null) + // llYear.setBackgroundResource(pressShapeSelectorId); + // if (llMonth != null) { + // llMonth.setBackgroundResource(pressShapeSelectorId); + // } + // adapter.setPressShapeSelectorId(pressShapeSelectorId); + // } + + public void setSelectedYear(int year) { + int lastYear = selectedYear; + this.selectedYear = year; + this.selectedWeek = DateUtil.calWeek(year, selectedMonth, selectedDay); + tvYear.setText(year + ""); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null) { + DateItem dateItem = getDayFindItem(adapter.getData(), year, selectedMonth, selectedDay); + daySelectedListener.onSelected(lastYear != year, year, selectedMonth, selectedDay, + selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setSelectedYearAndMonth(int year, int month) { + int lastYear = selectedYear; + int lastMonth = selectedMonth; + this.selectedYear = year; + this.selectedMonth = month; + this.selectedWeek = DateUtil.calWeek(selectedYear, selectedMonth, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), year, selectedMonth, selectedDay); + daySelectedListener.onSelected(lastYear != year || lastMonth != month, year, month, + selectedDay, selectedWeek, dateItem); + } + tvYear.setText(year + ""); + tvMonth.setText(month + ""); + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public void setSelectedMonth(int month) { + int lastMonth = this.selectedMonth; + this.selectedMonth = month; + tvMonth.setText(month + ""); + this.selectedWeek = DateUtil.calWeek(selectedYear, month, selectedDay); + int totalDayOfMonth = DateUtil.calDayOfMonth(selectedYear, selectedMonth); + + if (daySelectedListener != null && totalDayOfMonth >= selectedDay) { + DateItem dateItem = getDayFindItem(adapter.getData(), selectedYear, month, selectedDay); + daySelectedListener.onSelected(lastMonth != month, selectedYear, month, selectedDay, + selectedWeek, dateItem); + } + adapter.setDatas(getItems()); + adapter.notifyDataSetChanged(); + rv.scrollToPosition(selectedDay - 4); + rv.smoothScrollToPosition(selectedDay + 2); + } + + public String getSelectedDate() { + return selectedYear + "-" + selectedMonth + "-" + selectedDay; + } + + public String getSelectedDate(String gapStr) { + return selectedYear + gapStr + selectedMonth + gapStr + selectedDay; + } + + public void setOnDaySelectedListener(OnDaySelectedListener daySelectedListener) { + this.daySelectedListener = daySelectedListener; + } + + public void setOnYearMonthClickListener(OnYearMonthClickListener onYearMonthClickListener) { + this.onYearMonthClickListener = onYearMonthClickListener; + } + + public void setDaySelectionDrawable(int selectionDrawable) { + adapter.setDaySelectionDrawable(selectionDrawable); + } + + public void setTodaySelectionBgTextColor(int selectionDrawable, int textColor) { + adapter.setTodaySelectionBgTextColor(selectionDrawable, textColor); + } +} diff --git a/app/src/main/java/com/abbidot/tracker/widget/calender/ViewUtil.java b/app/src/main/java/com/abbidot/tracker/widget/calender/ViewUtil.java new file mode 100644 index 0000000..ac47b44 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/widget/calender/ViewUtil.java @@ -0,0 +1,272 @@ +package com.abbidot.tracker.widget.calender; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Resources; +import android.os.Build.VERSION; +import android.util.DisplayMetrics; +import android.util.TypedValue; +import android.view.View; +import android.view.View.MeasureSpec; +import android.view.ViewGroup; +import android.view.ViewGroup.MarginLayoutParams; +import android.widget.AbsListView; +import android.widget.GridView; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.TextView; + +public class ViewUtil { + public static final int INVALID = Integer.MIN_VALUE; + + public static int UI_WIDTH = 720; + public static int UI_HEIGHT = 1280; + public static int UI_DENSITY = 4; + + public static void setAbsListViewHeight(AbsListView absListView, int lineNumber, + int verticalSpace) { + int totalHeight = getAbsListViewHeight(absListView, lineNumber, verticalSpace); + ViewGroup.LayoutParams params = absListView.getLayoutParams(); + params.height = totalHeight; + ((MarginLayoutParams) params).setMargins(0, 0, 0, 0); + absListView.setLayoutParams(params); + } + + public static int getAbsListViewHeight(AbsListView absListView, int lineNumber, + int verticalSpace) { + int totalHeight = 0; + int w = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + int h = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + absListView.measure(w, h); + ListAdapter mListAdapter = absListView.getAdapter(); + if (mListAdapter == null) { + return totalHeight; + } + int count = mListAdapter.getCount(); + if (absListView instanceof ListView) { + for (int i = 0; i < count; i++) { + View listItem = mListAdapter.getView(i, null, absListView); + listItem.measure(w, h); + totalHeight += listItem.getMeasuredHeight(); + } + if (count == 0) { + totalHeight = verticalSpace; + } else { + totalHeight = + totalHeight + (((ListView) absListView).getDividerHeight() * (count - 1)); + } + } else if (absListView instanceof GridView) { + int remain = count % lineNumber; + if (remain > 0) { + remain = 1; + } + if (mListAdapter.getCount() == 0) { + totalHeight = verticalSpace; + } else { + View listItem = mListAdapter.getView(0, null, absListView); + listItem.measure(w, h); + int line = count / lineNumber + remain; + totalHeight = line * listItem.getMeasuredHeight() + (line - 1) * verticalSpace; + } + } + return totalHeight; + } + + public static void measureView(View view) { + ViewGroup.LayoutParams p = view.getLayoutParams(); + if (p == null) { + p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + } + int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width); + int lpHeight = p.height; + int childHeightSpec; + if (lpHeight > 0) { + childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); + } else { + childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); + } + view.measure(childWidthSpec, childHeightSpec); + } + + public static int scaleValue(Context context, float value) { + DisplayMetrics mDisplayMetrics = getDisplayMetrics(context); + int width = mDisplayMetrics.widthPixels; + int height = mDisplayMetrics.heightPixels; + if (width > height) { + width = mDisplayMetrics.heightPixels; + height = mDisplayMetrics.widthPixels; + } + if (mDisplayMetrics.scaledDensity >= UI_DENSITY) { + if (width > UI_WIDTH) { + value = value * (1.3f - 1.0f / mDisplayMetrics.scaledDensity); + } else if (width < UI_WIDTH) { + value = value * (1.0f - 1.0f / mDisplayMetrics.scaledDensity); + } + } else { + float offset = UI_DENSITY - mDisplayMetrics.scaledDensity; + if (offset > 0.5f) { + value = value * 0.9f; + } else { + value = value * 0.95f; + } + } + return scale(mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels, value); + } + + public static DisplayMetrics getDisplayMetrics(Context context) { + Resources mResources; + if (context == null) { + mResources = Resources.getSystem(); + } else { + mResources = context.getResources(); + } + DisplayMetrics mDisplayMetrics = mResources.getDisplayMetrics(); + return mDisplayMetrics; + } + + public static int scaleTextValue(Context context, float value) { + return scaleValue(context, value); + } + + public static int scale(int displayWidth, int displayHeight, float pxValue) { + if (pxValue == 0) { + return 0; + } + float scale = 1; + try { + int width = displayWidth; + int height = displayHeight; + if (width > height) { + width = displayHeight; + height = displayWidth; + } + float scaleWidth = (float) width / UI_WIDTH; + float scaleHeight = (float) height / UI_HEIGHT; + scale = Math.min(scaleWidth, scaleHeight); + } catch (Exception e) { + } + return Math.round(pxValue * scale + 0.5f); + } + + public static float applyDimension(int unit, float value, DisplayMetrics metrics) { + switch (unit) { + case TypedValue.COMPLEX_UNIT_PX: + return value; + case TypedValue.COMPLEX_UNIT_DIP: + return value * metrics.density; + case TypedValue.COMPLEX_UNIT_SP: + return value * metrics.scaledDensity; + case TypedValue.COMPLEX_UNIT_PT: + return value * metrics.xdpi * (1.0f / 72); + case TypedValue.COMPLEX_UNIT_IN: + return value * metrics.xdpi; + case TypedValue.COMPLEX_UNIT_MM: + return value * metrics.xdpi * (1.0f / 25.4f); + } + return 0; + } + + public static void scaleContentView(ViewGroup contentView) { + ViewUtil.scaleView(contentView); + if (contentView.getChildCount() > 0) { + for (int i = 0; i < contentView.getChildCount(); i++) { + View view = contentView.getChildAt(i); + if (view instanceof ViewGroup) { + scaleContentView((ViewGroup) (view)); + } else { + scaleView(contentView.getChildAt(i)); + } + } + } + } + + @SuppressLint("NewApi") + public static void scaleView(View view) { + if (view instanceof TextView) { + TextView textView = (TextView) view; + setTextSize(textView, textView.getTextSize()); + } + ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) view.getLayoutParams(); + if (null != params) { + int width = INVALID; + int height = INVALID; + if (params.width != ViewGroup.LayoutParams.WRAP_CONTENT && params.width != ViewGroup.LayoutParams.MATCH_PARENT) { + width = params.width; + } + if (params.height != ViewGroup.LayoutParams.WRAP_CONTENT && params.height != ViewGroup.LayoutParams.MATCH_PARENT) { + height = params.height; + } + setViewSize(view, width, height); + setPadding(view, view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), + view.getPaddingBottom()); + } + if (view.getLayoutParams() instanceof MarginLayoutParams) { + MarginLayoutParams mMarginLayoutParams = (MarginLayoutParams) view.getLayoutParams(); + if (mMarginLayoutParams != null) { + setMargin(view, mMarginLayoutParams.leftMargin, mMarginLayoutParams.topMargin, + mMarginLayoutParams.rightMargin, mMarginLayoutParams.bottomMargin); + } + } + if (VERSION.SDK_INT >= 16) { + int minWidth = scaleValue(view.getContext(), view.getMinimumWidth()); + int minHeight = scaleValue(view.getContext(), view.getMinimumHeight()); + view.setMinimumWidth(minWidth); + view.setMinimumHeight(minHeight); + } + } + + public static void setTextSize(TextView textView, float sizePixels) { + float scaledSize = scaleTextValue(textView.getContext(), sizePixels); + textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, scaledSize); + } + + public static void setViewSize(View view, int widthPixels, int heightPixels) { + int scaledWidth = scaleValue(view.getContext(), widthPixels); + int scaledHeight = scaleValue(view.getContext(), heightPixels); + ViewGroup.LayoutParams params = view.getLayoutParams(); + if (params == null) { + return; + } + if (widthPixels != INVALID) { + params.width = scaledWidth; + } + if (heightPixels != INVALID && heightPixels != 1) { + params.height = scaledHeight; + } + view.setLayoutParams(params); + } + + public static void setPadding(View view, int left, int top, int right, int bottom) { + int scaledLeft = scaleValue(view.getContext(), left); + int scaledTop = scaleValue(view.getContext(), top); + int scaledRight = scaleValue(view.getContext(), right); + int scaledBottom = scaleValue(view.getContext(), bottom); + view.setPadding(scaledLeft, scaledTop, scaledRight, scaledBottom); + } + + public static void setMargin(View view, int left, int top, int right, int bottom) { + int scaledLeft = scaleValue(view.getContext(), left); + int scaledTop = scaleValue(view.getContext(), top); + int scaledRight = scaleValue(view.getContext(), right); + int scaledBottom = scaleValue(view.getContext(), bottom); + if (view.getLayoutParams() instanceof MarginLayoutParams) { + MarginLayoutParams mMarginLayoutParams = (MarginLayoutParams) view.getLayoutParams(); + if (mMarginLayoutParams != null) { + if (left != INVALID) { + mMarginLayoutParams.leftMargin = scaledLeft; + } + if (right != INVALID) { + mMarginLayoutParams.rightMargin = scaledRight; + } + if (top != INVALID) { + mMarginLayoutParams.topMargin = scaledTop; + } + if (bottom != INVALID) { + mMarginLayoutParams.bottomMargin = scaledBottom; + } + view.setLayoutParams(mMarginLayoutParams); + } + } + } +} diff --git a/app/src/main/java/com/abbidot/tracker/wxapi/WXBaseActivity.kt b/app/src/main/java/com/abbidot/tracker/wxapi/WXBaseActivity.kt new file mode 100644 index 0000000..d96ab43 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/wxapi/WXBaseActivity.kt @@ -0,0 +1,52 @@ +package com.abbidot.tracker.wxapi + +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.FragmentActivity +import com.abbidot.tracker.util.ThirdPartyUtil +import com.tencent.mm.opensdk.modelbase.BaseReq +import com.tencent.mm.opensdk.modelbase.BaseResp +import com.tencent.mm.opensdk.openapi.IWXAPIEventHandler + +/** + *Created by .yzq on 2022/7/11/011. + * @link + * @description:封装微信Activity类 + */ +open class WXBaseActivity : FragmentActivity(), IWXAPIEventHandler { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + ThirdPartyUtil.mApi.handleIntent(intent, this) + initData() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + ThirdPartyUtil.mApi.handleIntent(intent, this) + } + + override fun onReq(baseReq: BaseReq?) { + baseReq?.let { + onWXReq(it) + } + } + + override fun onResp(baseResp: BaseResp?) { + baseResp?.let { + onWXResp(it) + } + } + + open fun initData() {} + + /** + * 处理微信请求回调 + */ + open fun onWXReq(baseReq: BaseReq) {} + + /** + * 处理微信请求后返回回调 + */ + open fun onWXResp(baseResp: BaseResp) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/wxapi/WXEntryActivity.kt b/app/src/main/java/com/abbidot/tracker/wxapi/WXEntryActivity.kt new file mode 100644 index 0000000..13656a3 --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/wxapi/WXEntryActivity.kt @@ -0,0 +1,73 @@ +package com.abbidot.tracker.wxapi + +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.XEventBus +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.deprecated.ui.fragment.vm.LoginCNViewModel +import com.hjq.toast.Toaster +import com.tencent.mm.opensdk.constants.ConstantsAPI +import com.tencent.mm.opensdk.modelbase.BaseResp +import com.tencent.mm.opensdk.modelmsg.SendAuth +import dagger.hilt.android.AndroidEntryPoint +import javax.inject.Inject + +/** + *Created by .yzq on 2022/7/8/008. + * @link + * @description:微信登录回调Activity + */ +@AndroidEntryPoint +class WXEntryActivity : WXBaseActivity() { + @Inject + lateinit var mLoginCNViewModel: LoginCNViewModel + + override fun initData() { + mLoginCNViewModel.mWXLoginResultLiveData.observe(this) { + LogUtil.e(it.toString()) + XEventBus.post(EventName.WXLoginResult, it) + finish() + } + } + + override fun onWXResp(baseResp: BaseResp) { + if (baseResp.type == ConstantsAPI.COMMAND_SENDAUTH) { + val result: String + val r = baseResp as SendAuth.Resp + when (baseResp.errCode) { + BaseResp.ErrCode.ERR_OK -> { + result = "登录成功" + val code = r.code + LogUtil.e("${result}TAG_WECHAT_CODE", code) + mLoginCNViewModel.wxLogin(code) + } + BaseResp.ErrCode.ERR_AUTH_DENIED -> { + result = "用户拒绝微信授权登录" + LogUtil.e("ERROR", result) + Toaster.show(result) + finish() + } + BaseResp.ErrCode.ERR_USER_CANCEL -> { + result = "用户取消微信授权登录" + LogUtil.e("ERROR", result) + Toaster.show(result) + finish() + } + else -> { + result = "微信授权登录失败,${baseResp.errStr},code=${r.code}" + LogUtil.e("ERROR", result) + Toaster.show(result) + finish() + } + } + } + //分享回调 + else if (baseResp.type == ConstantsAPI.COMMAND_SENDMESSAGE_TO_WX) { + when (baseResp.errCode) { + BaseResp.ErrCode.ERR_OK -> Toaster.show("分享成功") + BaseResp.ErrCode.ERR_AUTH_DENIED -> Toaster.show("分享出错") + BaseResp.ErrCode.ERR_USER_CANCEL -> Toaster.show("取消分享") + } + finish() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/abbidot/tracker/wxapi/WXPayEntryActivity.kt b/app/src/main/java/com/abbidot/tracker/wxapi/WXPayEntryActivity.kt new file mode 100644 index 0000000..b23ddcd --- /dev/null +++ b/app/src/main/java/com/abbidot/tracker/wxapi/WXPayEntryActivity.kt @@ -0,0 +1,44 @@ +package com.abbidot.tracker.wxapi + +import android.content.Intent +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.tracker.R +import com.abbidot.tracker.constant.ConstantInt +import com.abbidot.tracker.constant.ConstantString +import com.abbidot.tracker.deprecated.ui.activity.profile.recharge.PaymentResultActivity +import com.hjq.toast.Toaster +import com.tencent.mm.opensdk.modelbase.BaseResp + +/** + *Created by .yzq on 2022/7/11/011. + * @link + * @description:微信支付回调Activity + */ +class WXPayEntryActivity : WXBaseActivity() { + + override fun onWXResp(baseResp: BaseResp) { + LogUtil.e("WXPayEntryActivity", "code=" + baseResp.errCode) + when (baseResp.errCode) { + BaseResp.ErrCode.ERR_OK -> { + Toaster.show(R.string.txt_pay_success) + //支付成功 + LogUtil.e("WXPayEntryActivity", "支付成功") + val intent = Intent(this, PaymentResultActivity::class.java) + intent.putExtra(ConstantString.Type, ConstantInt.Type1) + startActivity(intent) + } + BaseResp.ErrCode.ERR_USER_CANCEL -> { + Toaster.show(R.string.txt_cancel_pay) + //取消支付 + LogUtil.e("WXPayEntryActivity", "取消支付") + } + else -> { + Toaster.show(R.string.txt_pay_fail) + //支付失败 + LogUtil.e("WXPayEntryActivity", "支付失败${baseResp.errStr}") + } + } + finish() + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v23/shape_transparent_black_bg.xml b/app/src/main/res/drawable-v23/shape_transparent_black_bg.xml new file mode 100644 index 0000000..a99cfd6 --- /dev/null +++ b/app/src/main/res/drawable-v23/shape_transparent_black_bg.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/bg_color.png b/app/src/main/res/drawable-xhdpi/bg_color.png new file mode 100644 index 0000000..72a289a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_color.png differ diff --git a/app/src/main/res/drawable-xhdpi/bg_dialog.png b/app/src/main/res/drawable-xhdpi/bg_dialog.png new file mode 100644 index 0000000..337d3e6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_dialog.png differ diff --git a/app/src/main/res/drawable-xhdpi/bg_list_my_pets.png b/app/src/main/res/drawable-xhdpi/bg_list_my_pets.png new file mode 100644 index 0000000..75b435c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_list_my_pets.png differ diff --git a/app/src/main/res/drawable-xhdpi/bg_pay_amount.png b/app/src/main/res/drawable-xhdpi/bg_pay_amount.png new file mode 100644 index 0000000..28785da Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/bg_pay_amount.png differ diff --git a/app/src/main/res/drawable-xhdpi/card_pay_manager.png b/app/src/main/res/drawable-xhdpi/card_pay_manager.png new file mode 100644 index 0000000..927febb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/card_pay_manager.png differ diff --git a/app/src/main/res/drawable-xhdpi/gray_ico_switch_on.png b/app/src/main/res/drawable-xhdpi/gray_ico_switch_on.png new file mode 100644 index 0000000..1225270 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/gray_ico_switch_on.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_back.png b/app/src/main/res/drawable-xhdpi/ico_back.png new file mode 100644 index 0000000..efdd50e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_back.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_bluetooth.png b/app/src/main/res/drawable-xhdpi/ico_bluetooth.png new file mode 100644 index 0000000..e701052 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_bluetooth.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_cancel.png b/app/src/main/res/drawable-xhdpi/ico_cancel.png new file mode 100644 index 0000000..fcf9fa8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_cancel.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_cancel_detail.png b/app/src/main/res/drawable-xhdpi/ico_cancel_detail.png new file mode 100644 index 0000000..29b141b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_cancel_detail.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_center.png b/app/src/main/res/drawable-xhdpi/ico_center.png new file mode 100644 index 0000000..490ce33 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_center.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_color_selector.png b/app/src/main/res/drawable-xhdpi/ico_color_selector.png new file mode 100644 index 0000000..f7cc2ed Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_color_selector.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_data_behavior.png b/app/src/main/res/drawable-xhdpi/ico_data_behavior.png new file mode 100644 index 0000000..783ec36 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_data_behavior.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_date.png b/app/src/main/res/drawable-xhdpi/ico_date.png new file mode 100644 index 0000000..f7ee593 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_date.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_delete.png b/app/src/main/res/drawable-xhdpi/ico_delete.png new file mode 100644 index 0000000..f4bbb68 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_delete.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_done.png b/app/src/main/res/drawable-xhdpi/ico_done.png new file mode 100644 index 0000000..5646c5f Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_done.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fance_rotate.png b/app/src/main/res/drawable-xhdpi/ico_fance_rotate.png new file mode 100644 index 0000000..8290480 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fance_rotate.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fance_scale1.png b/app/src/main/res/drawable-xhdpi/ico_fance_scale1.png new file mode 100644 index 0000000..64bf3a4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fance_scale1.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fance_scale2.png b/app/src/main/res/drawable-xhdpi/ico_fance_scale2.png new file mode 100644 index 0000000..16c8870 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fance_scale2.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_feelback.png b/app/src/main/res/drawable-xhdpi/ico_feelback.png new file mode 100644 index 0000000..4f52ecf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_feelback.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fence_address.png b/app/src/main/res/drawable-xhdpi/ico_fence_address.png new file mode 100644 index 0000000..7fde57b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fence_address.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fence_rectangle.png b/app/src/main/res/drawable-xhdpi/ico_fence_rectangle.png new file mode 100644 index 0000000..eafe550 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fence_rectangle.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fence_rectangle_active.png b/app/src/main/res/drawable-xhdpi/ico_fence_rectangle_active.png new file mode 100644 index 0000000..b5bcdbc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fence_rectangle_active.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fence_round.png b/app/src/main/res/drawable-xhdpi/ico_fence_round.png new file mode 100644 index 0000000..847ee57 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fence_round.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fence_round_active.png b/app/src/main/res/drawable-xhdpi/ico_fence_round_active.png new file mode 100644 index 0000000..d5f5878 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fence_round_active.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_fence_time.png b/app/src/main/res/drawable-xhdpi/ico_fence_time.png new file mode 100644 index 0000000..86fcbbb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_fence_time.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_gou_off.png b/app/src/main/res/drawable-xhdpi/ico_gou_off.png new file mode 100644 index 0000000..5d9eb8a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_gou_off.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_gou_on.png b/app/src/main/res/drawable-xhdpi/ico_gou_on.png new file mode 100644 index 0000000..9e50821 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_gou_on.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_gps_no.png b/app/src/main/res/drawable-xhdpi/ico_gps_no.png new file mode 100644 index 0000000..f189670 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_gps_no.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_help.png b/app/src/main/res/drawable-xhdpi/ico_help.png new file mode 100644 index 0000000..cb85893 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_help.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_history_point.png b/app/src/main/res/drawable-xhdpi/ico_history_point.png new file mode 100644 index 0000000..0db34f4 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_history_point.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_history_point_one.png b/app/src/main/res/drawable-xhdpi/ico_history_point_one.png new file mode 100644 index 0000000..b0fe63a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_history_point_one.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_history_track_active.png b/app/src/main/res/drawable-xhdpi/ico_history_track_active.png new file mode 100644 index 0000000..6aaae0a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_history_track_active.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_history_track_active_one.png b/app/src/main/res/drawable-xhdpi/ico_history_track_active_one.png new file mode 100644 index 0000000..73d9e18 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_history_track_active_one.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_amexcard.png b/app/src/main/res/drawable-xhdpi/ico_lay_amexcard.png new file mode 100644 index 0000000..fe2db54 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_amexcard.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_discovercard.png b/app/src/main/res/drawable-xhdpi/ico_lay_discovercard.png new file mode 100644 index 0000000..b420743 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_discovercard.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_maestrocard.png b/app/src/main/res/drawable-xhdpi/ico_lay_maestrocard.png new file mode 100644 index 0000000..08c43f5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_maestrocard.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_mastercard.png b/app/src/main/res/drawable-xhdpi/ico_lay_mastercard.png new file mode 100644 index 0000000..2749952 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_mastercard.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_newcard.png b/app/src/main/res/drawable-xhdpi/ico_lay_newcard.png new file mode 100644 index 0000000..9579afe Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_newcard.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_paypal.png b/app/src/main/res/drawable-xhdpi/ico_lay_paypal.png new file mode 100644 index 0000000..793199c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_paypal.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_lay_visa_card.png b/app/src/main/res/drawable-xhdpi/ico_lay_visa_card.png new file mode 100644 index 0000000..e73f173 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_lay_visa_card.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_link_pet.png b/app/src/main/res/drawable-xhdpi/ico_link_pet.png new file mode 100644 index 0000000..a0a8f33 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_link_pet.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_list_next.png b/app/src/main/res/drawable-xhdpi/ico_list_next.png new file mode 100644 index 0000000..92b54cf Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_list_next.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_loading_code.png b/app/src/main/res/drawable-xhdpi/ico_loading_code.png new file mode 100644 index 0000000..c6e03b9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_loading_code.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_loading_user.png b/app/src/main/res/drawable-xhdpi/ico_loading_user.png new file mode 100644 index 0000000..40a7011 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_loading_user.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_loading_wechat.png b/app/src/main/res/drawable-xhdpi/ico_loading_wechat.png new file mode 100644 index 0000000..33f55c6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_loading_wechat.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_loading_wechat2.png b/app/src/main/res/drawable-xhdpi/ico_loading_wechat2.png new file mode 100644 index 0000000..6f79662 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_loading_wechat2.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_man.png b/app/src/main/res/drawable-xhdpi/ico_man.png new file mode 100644 index 0000000..f7ab57e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_man.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_man_active.png b/app/src/main/res/drawable-xhdpi/ico_man_active.png new file mode 100644 index 0000000..c9d04c7 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_man_active.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_battery.png b/app/src/main/res/drawable-xhdpi/ico_map_battery.png new file mode 100644 index 0000000..d7acb10 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_battery.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_battery_low.png b/app/src/main/res/drawable-xhdpi/ico_map_battery_low.png new file mode 100644 index 0000000..887ba09 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_battery_low.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_gaode.png b/app/src/main/res/drawable-xhdpi/ico_map_gaode.png new file mode 100644 index 0000000..9ee47fb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_gaode.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_google.png b/app/src/main/res/drawable-xhdpi/ico_map_google.png new file mode 100644 index 0000000..8c569f5 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_google.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_gps.png b/app/src/main/res/drawable-xhdpi/ico_map_gps.png new file mode 100644 index 0000000..ee20e7b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_gps.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_light_off.png b/app/src/main/res/drawable-xhdpi/ico_map_light_off.png new file mode 100644 index 0000000..1bf3ad2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_light_off.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_light_on.png b/app/src/main/res/drawable-xhdpi/ico_map_light_on.png new file mode 100644 index 0000000..b59c182 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_light_on.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_navigation_go.png b/app/src/main/res/drawable-xhdpi/ico_map_navigation_go.png new file mode 100644 index 0000000..56c756e Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_navigation_go.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_map_refresh.png b/app/src/main/res/drawable-xhdpi/ico_map_refresh.png new file mode 100644 index 0000000..156843d Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_map_refresh.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_off.png b/app/src/main/res/drawable-xhdpi/ico_off.png new file mode 100644 index 0000000..5257c3a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_off.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_on.png b/app/src/main/res/drawable-xhdpi/ico_on.png new file mode 100644 index 0000000..53c3d92 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_on.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_pay_hot.png b/app/src/main/res/drawable-xhdpi/ico_pay_hot.png new file mode 100644 index 0000000..67bd0a0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_pay_hot.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_pet_link.png b/app/src/main/res/drawable-xhdpi/ico_pet_link.png new file mode 100644 index 0000000..0cae317 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_pet_link.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_photo.png b/app/src/main/res/drawable-xhdpi/ico_photo.png new file mode 100644 index 0000000..1849843 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_photo.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_recharge.png b/app/src/main/res/drawable-xhdpi/ico_recharge.png new file mode 100644 index 0000000..f20ae0c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_recharge.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_search.png b/app/src/main/res/drawable-xhdpi/ico_search.png new file mode 100644 index 0000000..bb69da6 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_search.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_setting.png b/app/src/main/res/drawable-xhdpi/ico_setting.png new file mode 100644 index 0000000..5a6c53b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_setting.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_sw_dowm.png b/app/src/main/res/drawable-xhdpi/ico_sw_dowm.png new file mode 100644 index 0000000..ec144cc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_sw_dowm.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_sw_dowm_br.png b/app/src/main/res/drawable-xhdpi/ico_sw_dowm_br.png new file mode 100644 index 0000000..3db8982 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_sw_dowm_br.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_switch_off.png b/app/src/main/res/drawable-xhdpi/ico_switch_off.png new file mode 100644 index 0000000..42271a9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_switch_off.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_switch_on.png b/app/src/main/res/drawable-xhdpi/ico_switch_on.png new file mode 100644 index 0000000..20bbca0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_switch_on.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_unlinked.png b/app/src/main/res/drawable-xhdpi/ico_unlinked.png new file mode 100644 index 0000000..3699acc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_unlinked.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_wman.png b/app/src/main/res/drawable-xhdpi/ico_wman.png new file mode 100644 index 0000000..4df8a49 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_wman.png differ diff --git a/app/src/main/res/drawable-xhdpi/ico_wman_active.png b/app/src/main/res/drawable-xhdpi/ico_wman_active.png new file mode 100644 index 0000000..43d1ef8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ico_wman_active.png differ diff --git a/app/src/main/res/drawable-xhdpi/icon_tracker_device_small.png b/app/src/main/res/drawable-xhdpi/icon_tracker_device_small.png new file mode 100644 index 0000000..3d9c5b8 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/icon_tracker_device_small.png differ diff --git a/app/src/main/res/drawable-xhdpi/map_live_start.png b/app/src/main/res/drawable-xhdpi/map_live_start.png new file mode 100644 index 0000000..fbca316 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/map_live_start.png differ diff --git a/app/src/main/res/drawable-xhdpi/pic_alipay.png b/app/src/main/res/drawable-xhdpi/pic_alipay.png new file mode 100644 index 0000000..d81cdb2 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/pic_alipay.png differ diff --git a/app/src/main/res/drawable-xhdpi/pic_avatar_df.png b/app/src/main/res/drawable-xhdpi/pic_avatar_df.png new file mode 100644 index 0000000..8b0e912 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/pic_avatar_df.png differ diff --git a/app/src/main/res/drawable-xhdpi/pic_history_location.png b/app/src/main/res/drawable-xhdpi/pic_history_location.png new file mode 100644 index 0000000..9f26e92 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/pic_history_location.png differ diff --git a/app/src/main/res/drawable-xhdpi/pic_map_gps_avatar.png b/app/src/main/res/drawable-xhdpi/pic_map_gps_avatar.png new file mode 100644 index 0000000..5dd3129 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/pic_map_gps_avatar.png differ diff --git a/app/src/main/res/drawable-xhdpi/pic_map_location.png b/app/src/main/res/drawable-xhdpi/pic_map_location.png new file mode 100644 index 0000000..f9986a0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/pic_map_location.png differ diff --git a/app/src/main/res/drawable-xhdpi/pic_wechatpay.png b/app/src/main/res/drawable-xhdpi/pic_wechatpay.png new file mode 100644 index 0000000..7102291 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/pic_wechatpay.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bg_color.png b/app/src/main/res/drawable-xxhdpi/bg_color.png new file mode 100644 index 0000000..9f4f27f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_color.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bg_dialog.png b/app/src/main/res/drawable-xxhdpi/bg_dialog.png new file mode 100644 index 0000000..f763856 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_dialog.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bg_list_my_pets.png b/app/src/main/res/drawable-xxhdpi/bg_list_my_pets.png new file mode 100644 index 0000000..2eed575 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_list_my_pets.png differ diff --git a/app/src/main/res/drawable-xxhdpi/bg_pay_amount.png b/app/src/main/res/drawable-xxhdpi/bg_pay_amount.png new file mode 100644 index 0000000..6129750 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/bg_pay_amount.png differ diff --git a/app/src/main/res/drawable-xxhdpi/card_pay_manager.png b/app/src/main/res/drawable-xxhdpi/card_pay_manager.png new file mode 100644 index 0000000..bf6c667 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/card_pay_manager.png differ diff --git a/app/src/main/res/drawable-xxhdpi/gray_ico_switch_on.png b/app/src/main/res/drawable-xxhdpi/gray_ico_switch_on.png new file mode 100644 index 0000000..a6b53a1 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/gray_ico_switch_on.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_back.png b/app/src/main/res/drawable-xxhdpi/ico_back.png new file mode 100644 index 0000000..9daaa0d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_back.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_bluetooth.png b/app/src/main/res/drawable-xxhdpi/ico_bluetooth.png new file mode 100644 index 0000000..a1a5df2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_bluetooth.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_cancel.png b/app/src/main/res/drawable-xxhdpi/ico_cancel.png new file mode 100644 index 0000000..73357e3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_cancel.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_cancel_detail.png b/app/src/main/res/drawable-xxhdpi/ico_cancel_detail.png new file mode 100644 index 0000000..9ad3fc2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_cancel_detail.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_center.png b/app/src/main/res/drawable-xxhdpi/ico_center.png new file mode 100644 index 0000000..a7bdcb8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_center.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_color_selector.png b/app/src/main/res/drawable-xxhdpi/ico_color_selector.png new file mode 100644 index 0000000..945d89f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_color_selector.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_data_behavior.png b/app/src/main/res/drawable-xxhdpi/ico_data_behavior.png new file mode 100644 index 0000000..32c3a16 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_data_behavior.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_date.png b/app/src/main/res/drawable-xxhdpi/ico_date.png new file mode 100644 index 0000000..142245a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_date.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_delete.png b/app/src/main/res/drawable-xxhdpi/ico_delete.png new file mode 100644 index 0000000..2c1b893 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_delete.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_done.png b/app/src/main/res/drawable-xxhdpi/ico_done.png new file mode 100644 index 0000000..46aaded Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_done.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fance_rotate.png b/app/src/main/res/drawable-xxhdpi/ico_fance_rotate.png new file mode 100644 index 0000000..db9698d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fance_rotate.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fance_scale1.png b/app/src/main/res/drawable-xxhdpi/ico_fance_scale1.png new file mode 100644 index 0000000..68e6ec0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fance_scale1.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fance_scale2.png b/app/src/main/res/drawable-xxhdpi/ico_fance_scale2.png new file mode 100644 index 0000000..e0dc130 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fance_scale2.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_feelback.png b/app/src/main/res/drawable-xxhdpi/ico_feelback.png new file mode 100644 index 0000000..7b4f5aa Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_feelback.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fence_address.png b/app/src/main/res/drawable-xxhdpi/ico_fence_address.png new file mode 100644 index 0000000..e5828ea Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fence_address.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fence_rectangle.png b/app/src/main/res/drawable-xxhdpi/ico_fence_rectangle.png new file mode 100644 index 0000000..b369d46 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fence_rectangle.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fence_rectangle_active.png b/app/src/main/res/drawable-xxhdpi/ico_fence_rectangle_active.png new file mode 100644 index 0000000..80dbe58 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fence_rectangle_active.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fence_round.png b/app/src/main/res/drawable-xxhdpi/ico_fence_round.png new file mode 100644 index 0000000..ac3c0f8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fence_round.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fence_round_active.png b/app/src/main/res/drawable-xxhdpi/ico_fence_round_active.png new file mode 100644 index 0000000..0a05ebc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fence_round_active.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_fence_time.png b/app/src/main/res/drawable-xxhdpi/ico_fence_time.png new file mode 100644 index 0000000..d50ffbd Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_fence_time.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_gou_off.png b/app/src/main/res/drawable-xxhdpi/ico_gou_off.png new file mode 100644 index 0000000..89a172a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_gou_off.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_gou_on.png b/app/src/main/res/drawable-xxhdpi/ico_gou_on.png new file mode 100644 index 0000000..992b0e7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_gou_on.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_gps_no.png b/app/src/main/res/drawable-xxhdpi/ico_gps_no.png new file mode 100644 index 0000000..8e9dd60 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_gps_no.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_help.png b/app/src/main/res/drawable-xxhdpi/ico_help.png new file mode 100644 index 0000000..09c896b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_help.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_history_point.png b/app/src/main/res/drawable-xxhdpi/ico_history_point.png new file mode 100644 index 0000000..a34b289 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_history_point.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_history_point_one.png b/app/src/main/res/drawable-xxhdpi/ico_history_point_one.png new file mode 100644 index 0000000..afe0870 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_history_point_one.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_history_track_active.png b/app/src/main/res/drawable-xxhdpi/ico_history_track_active.png new file mode 100644 index 0000000..b3594d9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_history_track_active.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_history_track_active_one.png b/app/src/main/res/drawable-xxhdpi/ico_history_track_active_one.png new file mode 100644 index 0000000..c97bbf4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_history_track_active_one.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_amexcard.png b/app/src/main/res/drawable-xxhdpi/ico_lay_amexcard.png new file mode 100644 index 0000000..5cdd6dc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_amexcard.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_discovercard.png b/app/src/main/res/drawable-xxhdpi/ico_lay_discovercard.png new file mode 100644 index 0000000..2c515f6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_discovercard.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_maestrocard.png b/app/src/main/res/drawable-xxhdpi/ico_lay_maestrocard.png new file mode 100644 index 0000000..e2464d3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_maestrocard.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_mastercard.png b/app/src/main/res/drawable-xxhdpi/ico_lay_mastercard.png new file mode 100644 index 0000000..5faad77 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_mastercard.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_newcard.png b/app/src/main/res/drawable-xxhdpi/ico_lay_newcard.png new file mode 100644 index 0000000..e6221e3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_newcard.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_paypal.png b/app/src/main/res/drawable-xxhdpi/ico_lay_paypal.png new file mode 100644 index 0000000..59bacd2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_paypal.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_lay_visa_card.png b/app/src/main/res/drawable-xxhdpi/ico_lay_visa_card.png new file mode 100644 index 0000000..50483d3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_lay_visa_card.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_link_pet.png b/app/src/main/res/drawable-xxhdpi/ico_link_pet.png new file mode 100644 index 0000000..56569fa Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_link_pet.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_list_next.png b/app/src/main/res/drawable-xxhdpi/ico_list_next.png new file mode 100644 index 0000000..300debb Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_list_next.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_loading_code.png b/app/src/main/res/drawable-xxhdpi/ico_loading_code.png new file mode 100644 index 0000000..145cdb3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_loading_code.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_loading_user.png b/app/src/main/res/drawable-xxhdpi/ico_loading_user.png new file mode 100644 index 0000000..ffbaf59 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_loading_user.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_loading_wechat.png b/app/src/main/res/drawable-xxhdpi/ico_loading_wechat.png new file mode 100644 index 0000000..a933812 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_loading_wechat.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_loading_wechat2.png b/app/src/main/res/drawable-xxhdpi/ico_loading_wechat2.png new file mode 100644 index 0000000..3d0453b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_loading_wechat2.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_man.png b/app/src/main/res/drawable-xxhdpi/ico_man.png new file mode 100644 index 0000000..63b9d52 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_man.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_man_active.png b/app/src/main/res/drawable-xxhdpi/ico_man_active.png new file mode 100644 index 0000000..846f697 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_man_active.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_battery.png b/app/src/main/res/drawable-xxhdpi/ico_map_battery.png new file mode 100644 index 0000000..d42861e Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_battery.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_battery_low.png b/app/src/main/res/drawable-xxhdpi/ico_map_battery_low.png new file mode 100644 index 0000000..fe46c48 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_battery_low.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_gaode.png b/app/src/main/res/drawable-xxhdpi/ico_map_gaode.png new file mode 100644 index 0000000..5244c26 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_gaode.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_google.png b/app/src/main/res/drawable-xxhdpi/ico_map_google.png new file mode 100644 index 0000000..f6f3a1d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_google.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_gps.png b/app/src/main/res/drawable-xxhdpi/ico_map_gps.png new file mode 100644 index 0000000..7d220d3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_gps.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_light_off.png b/app/src/main/res/drawable-xxhdpi/ico_map_light_off.png new file mode 100644 index 0000000..f07807b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_light_off.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_light_on.png b/app/src/main/res/drawable-xxhdpi/ico_map_light_on.png new file mode 100644 index 0000000..361198c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_light_on.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_navigation_go.png b/app/src/main/res/drawable-xxhdpi/ico_map_navigation_go.png new file mode 100644 index 0000000..ffabba4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_navigation_go.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_map_refresh.png b/app/src/main/res/drawable-xxhdpi/ico_map_refresh.png new file mode 100644 index 0000000..8e631a6 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_map_refresh.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_off.png b/app/src/main/res/drawable-xxhdpi/ico_off.png new file mode 100644 index 0000000..31eef89 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_off.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_on.png b/app/src/main/res/drawable-xxhdpi/ico_on.png new file mode 100644 index 0000000..305c9df Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_on.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_pay_hot.png b/app/src/main/res/drawable-xxhdpi/ico_pay_hot.png new file mode 100644 index 0000000..4d4088c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_pay_hot.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_pet_link.png b/app/src/main/res/drawable-xxhdpi/ico_pet_link.png new file mode 100644 index 0000000..e0089d7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_pet_link.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_photo.png b/app/src/main/res/drawable-xxhdpi/ico_photo.png new file mode 100644 index 0000000..059a011 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_photo.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_recharge.png b/app/src/main/res/drawable-xxhdpi/ico_recharge.png new file mode 100644 index 0000000..c308535 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_recharge.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_search.png b/app/src/main/res/drawable-xxhdpi/ico_search.png new file mode 100644 index 0000000..5695688 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_setting.png b/app/src/main/res/drawable-xxhdpi/ico_setting.png new file mode 100644 index 0000000..04599b9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_setting.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_sw_dowm.png b/app/src/main/res/drawable-xxhdpi/ico_sw_dowm.png new file mode 100644 index 0000000..19cd085 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_sw_dowm.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_sw_dowm_br.png b/app/src/main/res/drawable-xxhdpi/ico_sw_dowm_br.png new file mode 100644 index 0000000..af0cf3b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_sw_dowm_br.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_switch_off.png b/app/src/main/res/drawable-xxhdpi/ico_switch_off.png new file mode 100644 index 0000000..02d64b9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_switch_off.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_switch_on.png b/app/src/main/res/drawable-xxhdpi/ico_switch_on.png new file mode 100644 index 0000000..1ff3194 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_switch_on.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_unlinked.png b/app/src/main/res/drawable-xxhdpi/ico_unlinked.png new file mode 100644 index 0000000..99a194a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_unlinked.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_wman.png b/app/src/main/res/drawable-xxhdpi/ico_wman.png new file mode 100644 index 0000000..4944f72 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_wman.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ico_wman_active.png b/app/src/main/res/drawable-xxhdpi/ico_wman_active.png new file mode 100644 index 0000000..d1a7883 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ico_wman_active.png differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_tracker_device_small.png b/app/src/main/res/drawable-xxhdpi/icon_tracker_device_small.png new file mode 100644 index 0000000..283db7b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_tracker_device_small.png differ diff --git a/app/src/main/res/drawable-xxhdpi/map_live_start.png b/app/src/main/res/drawable-xxhdpi/map_live_start.png new file mode 100644 index 0000000..890895c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/map_live_start.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pic_alipay.png b/app/src/main/res/drawable-xxhdpi/pic_alipay.png new file mode 100644 index 0000000..57c7241 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pic_alipay.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pic_avatar_df.png b/app/src/main/res/drawable-xxhdpi/pic_avatar_df.png new file mode 100644 index 0000000..e24dcb5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pic_avatar_df.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pic_history_location.png b/app/src/main/res/drawable-xxhdpi/pic_history_location.png new file mode 100644 index 0000000..2481c85 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pic_history_location.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pic_map_gps_avatar.png b/app/src/main/res/drawable-xxhdpi/pic_map_gps_avatar.png new file mode 100644 index 0000000..54bb1ff Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pic_map_gps_avatar.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pic_map_location.png b/app/src/main/res/drawable-xxhdpi/pic_map_location.png new file mode 100644 index 0000000..863cf03 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pic_map_location.png differ diff --git a/app/src/main/res/drawable-xxhdpi/pic_wechatpay.png b/app/src/main/res/drawable-xxhdpi/pic_wechatpay.png new file mode 100644 index 0000000..4ce08b5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/pic_wechatpay.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/bg_color.png b/app/src/main/res/drawable-xxxhdpi/bg_color.png new file mode 100644 index 0000000..c1ad5e8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_color.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/bg_dialog.png b/app/src/main/res/drawable-xxxhdpi/bg_dialog.png new file mode 100644 index 0000000..8f9b7c2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_dialog.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/bg_list_my_pets.png b/app/src/main/res/drawable-xxxhdpi/bg_list_my_pets.png new file mode 100644 index 0000000..f2c98fd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_list_my_pets.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/bg_pay_amount.png b/app/src/main/res/drawable-xxxhdpi/bg_pay_amount.png new file mode 100644 index 0000000..c8b4f9c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/bg_pay_amount.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/card_pay_manager.png b/app/src/main/res/drawable-xxxhdpi/card_pay_manager.png new file mode 100644 index 0000000..8926d79 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/card_pay_manager.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/gray_ico_switch_on.png b/app/src/main/res/drawable-xxxhdpi/gray_ico_switch_on.png new file mode 100644 index 0000000..1bb5e4e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/gray_ico_switch_on.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_back.png b/app/src/main/res/drawable-xxxhdpi/ico_back.png new file mode 100644 index 0000000..71745ce Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_back.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_bluetooth.png b/app/src/main/res/drawable-xxxhdpi/ico_bluetooth.png new file mode 100644 index 0000000..0f193bd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_bluetooth.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_cancel.png b/app/src/main/res/drawable-xxxhdpi/ico_cancel.png new file mode 100644 index 0000000..0205d92 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_cancel.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_cancel_detail.png b/app/src/main/res/drawable-xxxhdpi/ico_cancel_detail.png new file mode 100644 index 0000000..f105ae9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_cancel_detail.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_center.png b/app/src/main/res/drawable-xxxhdpi/ico_center.png new file mode 100644 index 0000000..4cd5cf0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_center.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_color_selector.png b/app/src/main/res/drawable-xxxhdpi/ico_color_selector.png new file mode 100644 index 0000000..490f572 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_color_selector.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_data_behavior.png b/app/src/main/res/drawable-xxxhdpi/ico_data_behavior.png new file mode 100644 index 0000000..57bff98 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_data_behavior.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_date.png b/app/src/main/res/drawable-xxxhdpi/ico_date.png new file mode 100644 index 0000000..da02e59 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_date.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_delete.png b/app/src/main/res/drawable-xxxhdpi/ico_delete.png new file mode 100644 index 0000000..3cd1601 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_delete.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_done.png b/app/src/main/res/drawable-xxxhdpi/ico_done.png new file mode 100644 index 0000000..3731f42 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_done.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fance_rotate.png b/app/src/main/res/drawable-xxxhdpi/ico_fance_rotate.png new file mode 100644 index 0000000..3aff9d4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fance_rotate.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fance_scale1.png b/app/src/main/res/drawable-xxxhdpi/ico_fance_scale1.png new file mode 100644 index 0000000..c97dcea Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fance_scale1.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fance_scale2.png b/app/src/main/res/drawable-xxxhdpi/ico_fance_scale2.png new file mode 100644 index 0000000..982a062 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fance_scale2.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_feelback.png b/app/src/main/res/drawable-xxxhdpi/ico_feelback.png new file mode 100644 index 0000000..7edb345 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_feelback.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fence_address.png b/app/src/main/res/drawable-xxxhdpi/ico_fence_address.png new file mode 100644 index 0000000..4f7d589 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fence_address.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fence_rectangle.png b/app/src/main/res/drawable-xxxhdpi/ico_fence_rectangle.png new file mode 100644 index 0000000..52e6f49 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fence_rectangle.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fence_rectangle_active.png b/app/src/main/res/drawable-xxxhdpi/ico_fence_rectangle_active.png new file mode 100644 index 0000000..5ec2e67 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fence_rectangle_active.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fence_round.png b/app/src/main/res/drawable-xxxhdpi/ico_fence_round.png new file mode 100644 index 0000000..5f0d907 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fence_round.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fence_round_active.png b/app/src/main/res/drawable-xxxhdpi/ico_fence_round_active.png new file mode 100644 index 0000000..28d0aea Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fence_round_active.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_fence_time.png b/app/src/main/res/drawable-xxxhdpi/ico_fence_time.png new file mode 100644 index 0000000..9c59b19 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_fence_time.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_gou_off.png b/app/src/main/res/drawable-xxxhdpi/ico_gou_off.png new file mode 100644 index 0000000..0a9c251 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_gou_off.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_gou_on.png b/app/src/main/res/drawable-xxxhdpi/ico_gou_on.png new file mode 100644 index 0000000..70c05c2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_gou_on.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_gps_no.png b/app/src/main/res/drawable-xxxhdpi/ico_gps_no.png new file mode 100644 index 0000000..0a9b9c5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_gps_no.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_help.png b/app/src/main/res/drawable-xxxhdpi/ico_help.png new file mode 100644 index 0000000..bdffe3d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_help.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_history_point.png b/app/src/main/res/drawable-xxxhdpi/ico_history_point.png new file mode 100644 index 0000000..a251e12 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_history_point.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_history_point_one.png b/app/src/main/res/drawable-xxxhdpi/ico_history_point_one.png new file mode 100644 index 0000000..46bed70 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_history_point_one.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_history_track_active.png b/app/src/main/res/drawable-xxxhdpi/ico_history_track_active.png new file mode 100644 index 0000000..97e202a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_history_track_active.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_history_track_active_one.png b/app/src/main/res/drawable-xxxhdpi/ico_history_track_active_one.png new file mode 100644 index 0000000..8cc103a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_history_track_active_one.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_amexcard.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_amexcard.png new file mode 100644 index 0000000..17d5258 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_amexcard.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_discovercard.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_discovercard.png new file mode 100644 index 0000000..cfae6ae Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_discovercard.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_maestrocard.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_maestrocard.png new file mode 100644 index 0000000..9b4d026 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_maestrocard.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_mastercard.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_mastercard.png new file mode 100644 index 0000000..9eae217 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_mastercard.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_newcard.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_newcard.png new file mode 100644 index 0000000..b5a432c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_newcard.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_paypal.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_paypal.png new file mode 100644 index 0000000..bdef9f5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_paypal.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_lay_visa_card.png b/app/src/main/res/drawable-xxxhdpi/ico_lay_visa_card.png new file mode 100644 index 0000000..887a953 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_lay_visa_card.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_link_pet.png b/app/src/main/res/drawable-xxxhdpi/ico_link_pet.png new file mode 100644 index 0000000..b1a86d5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_link_pet.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_list_next.png b/app/src/main/res/drawable-xxxhdpi/ico_list_next.png new file mode 100644 index 0000000..686ff45 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_list_next.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_loading_code.png b/app/src/main/res/drawable-xxxhdpi/ico_loading_code.png new file mode 100644 index 0000000..72eb38b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_loading_code.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_loading_user.png b/app/src/main/res/drawable-xxxhdpi/ico_loading_user.png new file mode 100644 index 0000000..a0449e1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_loading_user.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_loading_wechat.png b/app/src/main/res/drawable-xxxhdpi/ico_loading_wechat.png new file mode 100644 index 0000000..0981716 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_loading_wechat.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_loading_wechat2.png b/app/src/main/res/drawable-xxxhdpi/ico_loading_wechat2.png new file mode 100644 index 0000000..9055f49 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_loading_wechat2.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_man.png b/app/src/main/res/drawable-xxxhdpi/ico_man.png new file mode 100644 index 0000000..fff88c6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_man.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_man_active.png b/app/src/main/res/drawable-xxxhdpi/ico_man_active.png new file mode 100644 index 0000000..9995d0e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_man_active.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_battery.png b/app/src/main/res/drawable-xxxhdpi/ico_map_battery.png new file mode 100644 index 0000000..0e27abf Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_battery.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_battery_low.png b/app/src/main/res/drawable-xxxhdpi/ico_map_battery_low.png new file mode 100644 index 0000000..f47b7e3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_battery_low.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_gaode.png b/app/src/main/res/drawable-xxxhdpi/ico_map_gaode.png new file mode 100644 index 0000000..afae42a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_gaode.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_google.png b/app/src/main/res/drawable-xxxhdpi/ico_map_google.png new file mode 100644 index 0000000..a7ea9ac Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_google.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_gps.png b/app/src/main/res/drawable-xxxhdpi/ico_map_gps.png new file mode 100644 index 0000000..f47c696 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_gps.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_light_off.png b/app/src/main/res/drawable-xxxhdpi/ico_map_light_off.png new file mode 100644 index 0000000..b68f39f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_light_off.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_light_on.png b/app/src/main/res/drawable-xxxhdpi/ico_map_light_on.png new file mode 100644 index 0000000..5001283 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_light_on.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_navigation_go.png b/app/src/main/res/drawable-xxxhdpi/ico_map_navigation_go.png new file mode 100644 index 0000000..49739e8 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_navigation_go.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_map_refresh.png b/app/src/main/res/drawable-xxxhdpi/ico_map_refresh.png new file mode 100644 index 0000000..8e02e59 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_map_refresh.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_off.png b/app/src/main/res/drawable-xxxhdpi/ico_off.png new file mode 100644 index 0000000..baebc0c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_off.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_on.png b/app/src/main/res/drawable-xxxhdpi/ico_on.png new file mode 100644 index 0000000..cf6c381 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_on.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_pay_hot.png b/app/src/main/res/drawable-xxxhdpi/ico_pay_hot.png new file mode 100644 index 0000000..04cba84 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_pay_hot.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_pet_link.png b/app/src/main/res/drawable-xxxhdpi/ico_pet_link.png new file mode 100644 index 0000000..eb50c16 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_pet_link.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_photo.png b/app/src/main/res/drawable-xxxhdpi/ico_photo.png new file mode 100644 index 0000000..c3b2c01 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_photo.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_recharge.png b/app/src/main/res/drawable-xxxhdpi/ico_recharge.png new file mode 100644 index 0000000..283268b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_recharge.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_search.png b/app/src/main/res/drawable-xxxhdpi/ico_search.png new file mode 100644 index 0000000..01f32b5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_search.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_setting.png b/app/src/main/res/drawable-xxxhdpi/ico_setting.png new file mode 100644 index 0000000..a9970ac Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_setting.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_sw_dowm.png b/app/src/main/res/drawable-xxxhdpi/ico_sw_dowm.png new file mode 100644 index 0000000..0515cea Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_sw_dowm.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_sw_dowm_br.png b/app/src/main/res/drawable-xxxhdpi/ico_sw_dowm_br.png new file mode 100644 index 0000000..8e5b3c4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_sw_dowm_br.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_switch_off.png b/app/src/main/res/drawable-xxxhdpi/ico_switch_off.png new file mode 100644 index 0000000..a7e5166 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_switch_off.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_switch_on.png b/app/src/main/res/drawable-xxxhdpi/ico_switch_on.png new file mode 100644 index 0000000..42d0e0b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_switch_on.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_unlinked.png b/app/src/main/res/drawable-xxxhdpi/ico_unlinked.png new file mode 100644 index 0000000..126c1b9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_unlinked.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_wman.png b/app/src/main/res/drawable-xxxhdpi/ico_wman.png new file mode 100644 index 0000000..c0af1ad Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_wman.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ico_wman_active.png b/app/src/main/res/drawable-xxxhdpi/ico_wman_active.png new file mode 100644 index 0000000..9f9d58b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ico_wman_active.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_tracker_device_small.png b/app/src/main/res/drawable-xxxhdpi/icon_tracker_device_small.png new file mode 100644 index 0000000..b5ef1fc Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/icon_tracker_device_small.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/map_live_start.png b/app/src/main/res/drawable-xxxhdpi/map_live_start.png new file mode 100644 index 0000000..cf3cad2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/map_live_start.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_alipay.png b/app/src/main/res/drawable-xxxhdpi/pic_alipay.png new file mode 100644 index 0000000..35154ed Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_alipay.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_avatar_df.png b/app/src/main/res/drawable-xxxhdpi/pic_avatar_df.png new file mode 100644 index 0000000..481c706 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_avatar_df.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_history_location.png b/app/src/main/res/drawable-xxxhdpi/pic_history_location.png new file mode 100644 index 0000000..991edb1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_history_location.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_map_gps_avatar.png b/app/src/main/res/drawable-xxxhdpi/pic_map_gps_avatar.png new file mode 100644 index 0000000..9e32c45 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_map_gps_avatar.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_map_location.png b/app/src/main/res/drawable-xxxhdpi/pic_map_location.png new file mode 100644 index 0000000..ddb8fa6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_map_location.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_wechatpay.png b/app/src/main/res/drawable-xxxhdpi/pic_wechatpay.png new file mode 100644 index 0000000..336cbf5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_wechatpay.png differ diff --git a/app/src/main/res/drawable/abbidot_ico_1024.png b/app/src/main/res/drawable/abbidot_ico_1024.png new file mode 100644 index 0000000..5db8228 Binary files /dev/null and b/app/src/main/res/drawable/abbidot_ico_1024.png differ diff --git a/app/src/main/res/drawable/abbidot_logo_loading.png b/app/src/main/res/drawable/abbidot_logo_loading.png new file mode 100644 index 0000000..36837ac Binary files /dev/null and b/app/src/main/res/drawable/abbidot_logo_loading.png differ diff --git a/app/src/main/res/drawable/active_tracker_help.png b/app/src/main/res/drawable/active_tracker_help.png new file mode 100644 index 0000000..0f93a29 Binary files /dev/null and b/app/src/main/res/drawable/active_tracker_help.png differ diff --git a/app/src/main/res/drawable/battery_life_help1.png b/app/src/main/res/drawable/battery_life_help1.png new file mode 100644 index 0000000..b45dcf5 Binary files /dev/null and b/app/src/main/res/drawable/battery_life_help1.png differ diff --git a/app/src/main/res/drawable/battery_life_help2.png b/app/src/main/res/drawable/battery_life_help2.png new file mode 100644 index 0000000..644ac39 Binary files /dev/null and b/app/src/main/res/drawable/battery_life_help2.png differ diff --git a/app/src/main/res/drawable/battery_life_help3.png b/app/src/main/res/drawable/battery_life_help3.png new file mode 100644 index 0000000..2df292c Binary files /dev/null and b/app/src/main/res/drawable/battery_life_help3.png differ diff --git a/app/src/main/res/drawable/battery_life_help4.png b/app/src/main/res/drawable/battery_life_help4.png new file mode 100644 index 0000000..1752f05 Binary files /dev/null and b/app/src/main/res/drawable/battery_life_help4.png differ diff --git a/app/src/main/res/drawable/battery_life_help5.png b/app/src/main/res/drawable/battery_life_help5.png new file mode 100644 index 0000000..639f238 Binary files /dev/null and b/app/src/main/res/drawable/battery_life_help5.png differ diff --git a/app/src/main/res/drawable/bg_feedback_shape.xml b/app/src/main/res/drawable/bg_feedback_shape.xml new file mode 100644 index 0000000..18a638c --- /dev/null +++ b/app/src/main/res/drawable/bg_feedback_shape.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_enabled_text_color.xml b/app/src/main/res/drawable/button_enabled_text_color.xml new file mode 100644 index 0000000..630c58a --- /dev/null +++ b/app/src/main/res/drawable/button_enabled_text_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/charge_power_on.gif b/app/src/main/res/drawable/charge_power_on.gif new file mode 100644 index 0000000..6cfed58 Binary files /dev/null and b/app/src/main/res/drawable/charge_power_on.gif differ diff --git a/app/src/main/res/drawable/check_box_agree_select.xml b/app/src/main/res/drawable/check_box_agree_select.xml new file mode 100644 index 0000000..0a32306 --- /dev/null +++ b/app/src/main/res/drawable/check_box_agree_select.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/check_box_icon_point_selector.xml b/app/src/main/res/drawable/check_box_icon_point_selector.xml new file mode 100644 index 0000000..d28401a --- /dev/null +++ b/app/src/main/res/drawable/check_box_icon_point_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/check_box_yellow_icon_selector.xml b/app/src/main/res/drawable/check_box_yellow_icon_selector.xml new file mode 100644 index 0000000..29bc8f8 --- /dev/null +++ b/app/src/main/res/drawable/check_box_yellow_icon_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/create_fence_help1.png b/app/src/main/res/drawable/create_fence_help1.png new file mode 100644 index 0000000..15fae56 Binary files /dev/null and b/app/src/main/res/drawable/create_fence_help1.png differ diff --git a/app/src/main/res/drawable/create_fence_help2.png b/app/src/main/res/drawable/create_fence_help2.png new file mode 100644 index 0000000..cb69ba1 Binary files /dev/null and b/app/src/main/res/drawable/create_fence_help2.png differ diff --git a/app/src/main/res/drawable/create_fence_help3.png b/app/src/main/res/drawable/create_fence_help3.png new file mode 100644 index 0000000..d29e310 Binary files /dev/null and b/app/src/main/res/drawable/create_fence_help3.png differ diff --git a/app/src/main/res/drawable/create_fence_help4.png b/app/src/main/res/drawable/create_fence_help4.png new file mode 100644 index 0000000..3291676 Binary files /dev/null and b/app/src/main/res/drawable/create_fence_help4.png differ diff --git a/app/src/main/res/drawable/credit_card_chip_yellow.xml b/app/src/main/res/drawable/credit_card_chip_yellow.xml new file mode 100644 index 0000000..1a9cc59 --- /dev/null +++ b/app/src/main/res/drawable/credit_card_chip_yellow.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/credit_card_chip_yellow_inner.xml b/app/src/main/res/drawable/credit_card_chip_yellow_inner.xml new file mode 100644 index 0000000..b660f3d --- /dev/null +++ b/app/src/main/res/drawable/credit_card_chip_yellow_inner.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/debug_ic.xml b/app/src/main/res/drawable/debug_ic.xml new file mode 100644 index 0000000..76c0b7c --- /dev/null +++ b/app/src/main/res/drawable/debug_ic.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/front_card_bg.png b/app/src/main/res/drawable/front_card_bg.png new file mode 100644 index 0000000..0da7e99 Binary files /dev/null and b/app/src/main/res/drawable/front_card_bg.png differ diff --git a/app/src/main/res/drawable/gif_shake_on.gif b/app/src/main/res/drawable/gif_shake_on.gif new file mode 100644 index 0000000..4704902 Binary files /dev/null and b/app/src/main/res/drawable/gif_shake_on.gif differ diff --git a/app/src/main/res/drawable/help_tracker_status.png b/app/src/main/res/drawable/help_tracker_status.png new file mode 100644 index 0000000..0a4224b Binary files /dev/null and b/app/src/main/res/drawable/help_tracker_status.png differ diff --git a/app/src/main/res/drawable/home_account_selector.xml b/app/src/main/res/drawable/home_account_selector.xml new file mode 100644 index 0000000..6bcab93 --- /dev/null +++ b/app/src/main/res/drawable/home_account_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_activity_selector.xml b/app/src/main/res/drawable/home_activity_selector.xml new file mode 100644 index 0000000..f02caed --- /dev/null +++ b/app/src/main/res/drawable/home_activity_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_map_selector_v2.xml b/app/src/main/res/drawable/home_map_selector_v2.xml new file mode 100644 index 0000000..2b5a6ce --- /dev/null +++ b/app/src/main/res/drawable/home_map_selector_v2.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_navigation_color_selector.xml b/app/src/main/res/drawable/home_navigation_color_selector.xml new file mode 100644 index 0000000..40440ea --- /dev/null +++ b/app/src/main/res/drawable/home_navigation_color_selector.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_pet_selector.xml b/app/src/main/res/drawable/home_pet_selector.xml new file mode 100644 index 0000000..faa7fba --- /dev/null +++ b/app/src/main/res/drawable/home_pet_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_route_selector.xml b/app/src/main/res/drawable/home_route_selector.xml new file mode 100644 index 0000000..b5a8c15 --- /dev/null +++ b/app/src/main/res/drawable/home_route_selector.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/home_v2_bottom_bg.xml b/app/src/main/res/drawable/home_v2_bottom_bg.xml new file mode 100644 index 0000000..734ad69 --- /dev/null +++ b/app/src/main/res/drawable/home_v2_bottom_bg.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ico_account.xml b/app/src/main/res/drawable/ico_account.xml new file mode 100644 index 0000000..04bab6c --- /dev/null +++ b/app/src/main/res/drawable/ico_account.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/ico_account_active.xml b/app/src/main/res/drawable/ico_account_active.xml new file mode 100644 index 0000000..c418c08 --- /dev/null +++ b/app/src/main/res/drawable/ico_account_active.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/ico_activity.xml b/app/src/main/res/drawable/ico_activity.xml new file mode 100644 index 0000000..6ab81d0 --- /dev/null +++ b/app/src/main/res/drawable/ico_activity.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/drawable/ico_activity_active.xml b/app/src/main/res/drawable/ico_activity_active.xml new file mode 100644 index 0000000..2a265d8 --- /dev/null +++ b/app/src/main/res/drawable/ico_activity_active.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/drawable/ico_map_active_v2.xml b/app/src/main/res/drawable/ico_map_active_v2.xml new file mode 100644 index 0000000..94b4047 --- /dev/null +++ b/app/src/main/res/drawable/ico_map_active_v2.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/drawable/ico_map_v2.xml b/app/src/main/res/drawable/ico_map_v2.xml new file mode 100644 index 0000000..a3cd729 --- /dev/null +++ b/app/src/main/res/drawable/ico_map_v2.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/app/src/main/res/drawable/ico_pet.xml b/app/src/main/res/drawable/ico_pet.xml new file mode 100644 index 0000000..3993f03 --- /dev/null +++ b/app/src/main/res/drawable/ico_pet.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ico_pet_active.xml b/app/src/main/res/drawable/ico_pet_active.xml new file mode 100644 index 0000000..2758442 --- /dev/null +++ b/app/src/main/res/drawable/ico_pet_active.xml @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ico_save_to_facebook.png b/app/src/main/res/drawable/ico_save_to_facebook.png new file mode 100644 index 0000000..9f6e5e6 Binary files /dev/null and b/app/src/main/res/drawable/ico_save_to_facebook.png differ diff --git a/app/src/main/res/drawable/ico_save_to_phone.png b/app/src/main/res/drawable/ico_save_to_phone.png new file mode 100644 index 0000000..f792dca Binary files /dev/null and b/app/src/main/res/drawable/ico_save_to_phone.png differ diff --git a/app/src/main/res/drawable/icon_1_year_hugger_lock_svg.xml b/app/src/main/res/drawable/icon_1_year_hugger_lock_svg.xml new file mode 100644 index 0000000..3c4af61 --- /dev/null +++ b/app/src/main/res/drawable/icon_1_year_hugger_lock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_1_year_hugger_unlock_svg.xml b/app/src/main/res/drawable/icon_1_year_hugger_unlock_svg.xml new file mode 100644 index 0000000..8d3faa6 --- /dev/null +++ b/app/src/main/res/drawable/icon_1_year_hugger_unlock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_2_year_companion_lock_svg.xml b/app/src/main/res/drawable/icon_2_year_companion_lock_svg.xml new file mode 100644 index 0000000..85d473a --- /dev/null +++ b/app/src/main/res/drawable/icon_2_year_companion_lock_svg.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_2_year_companion_unlock_svg.xml b/app/src/main/res/drawable/icon_2_year_companion_unlock_svg.xml new file mode 100644 index 0000000..fa757a6 --- /dev/null +++ b/app/src/main/res/drawable/icon_2_year_companion_unlock_svg.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_3_year_harmony_lock_svg.xml b/app/src/main/res/drawable/icon_3_year_harmony_lock_svg.xml new file mode 100644 index 0000000..51fb396 --- /dev/null +++ b/app/src/main/res/drawable/icon_3_year_harmony_lock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_3_year_harmony_unlock_svg.xml b/app/src/main/res/drawable/icon_3_year_harmony_unlock_svg.xml new file mode 100644 index 0000000..81471ff --- /dev/null +++ b/app/src/main/res/drawable/icon_3_year_harmony_unlock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_about_svg.xml b/app/src/main/res/drawable/icon_about_svg.xml new file mode 100644 index 0000000..98b8714 --- /dev/null +++ b/app/src/main/res/drawable/icon_about_svg.xml @@ -0,0 +1,25 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_active_time.xml b/app/src/main/res/drawable/icon_active_time.xml new file mode 100644 index 0000000..41b1cbd --- /dev/null +++ b/app/src/main/res/drawable/icon_active_time.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_advanced_set_svg.xml b/app/src/main/res/drawable/icon_advanced_set_svg.xml new file mode 100644 index 0000000..122d49f --- /dev/null +++ b/app/src/main/res/drawable/icon_advanced_set_svg.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_back_svg.xml b/app/src/main/res/drawable/icon_back_svg.xml new file mode 100644 index 0000000..86526da --- /dev/null +++ b/app/src/main/res/drawable/icon_back_svg.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/icon_badges_unlock_bg_svg.xml b/app/src/main/res/drawable/icon_badges_unlock_bg_svg.xml new file mode 100644 index 0000000..0f2d91f --- /dev/null +++ b/app/src/main/res/drawable/icon_badges_unlock_bg_svg.xml @@ -0,0 +1,485 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_black_back_svg.xml b/app/src/main/res/drawable/icon_black_back_svg.xml new file mode 100644 index 0000000..ab5d8f9 --- /dev/null +++ b/app/src/main/res/drawable/icon_black_back_svg.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/icon_black_cir_ring.xml b/app/src/main/res/drawable/icon_black_cir_ring.xml new file mode 100644 index 0000000..7936955 --- /dev/null +++ b/app/src/main/res/drawable/icon_black_cir_ring.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_bluetooth_svg.xml b/app/src/main/res/drawable/icon_bluetooth_svg.xml new file mode 100644 index 0000000..6e89ac5 --- /dev/null +++ b/app/src/main/res/drawable/icon_bluetooth_svg.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_calendar2_activity_svg.xml b/app/src/main/res/drawable/icon_calendar2_activity_svg.xml new file mode 100644 index 0000000..f743c58 --- /dev/null +++ b/app/src/main/res/drawable/icon_calendar2_activity_svg.xml @@ -0,0 +1,31 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_calendar2_svg.xml b/app/src/main/res/drawable/icon_calendar2_svg.xml new file mode 100644 index 0000000..8974fe7 --- /dev/null +++ b/app/src/main/res/drawable/icon_calendar2_svg.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_calendar_activity_svg.xml b/app/src/main/res/drawable/icon_calendar_activity_svg.xml new file mode 100644 index 0000000..db0f8c1 --- /dev/null +++ b/app/src/main/res/drawable/icon_calendar_activity_svg.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_calendar_svg.xml b/app/src/main/res/drawable/icon_calendar_svg.xml new file mode 100644 index 0000000..edac738 --- /dev/null +++ b/app/src/main/res/drawable/icon_calendar_svg.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_calender_big_left_svg.xml b/app/src/main/res/drawable/icon_calender_big_left_svg.xml new file mode 100644 index 0000000..ccaaee7 --- /dev/null +++ b/app/src/main/res/drawable/icon_calender_big_left_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_calender_big_right_svg.xml b/app/src/main/res/drawable/icon_calender_big_right_svg.xml new file mode 100644 index 0000000..424ea8f --- /dev/null +++ b/app/src/main/res/drawable/icon_calender_big_right_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_calender_small_right_svg.xml b/app/src/main/res/drawable/icon_calender_small_right_svg.xml new file mode 100644 index 0000000..26248a3 --- /dev/null +++ b/app/src/main/res/drawable/icon_calender_small_right_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_calories.xml b/app/src/main/res/drawable/icon_calories.xml new file mode 100644 index 0000000..98f5397 --- /dev/null +++ b/app/src/main/res/drawable/icon_calories.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_cat_default_head_svg.xml b/app/src/main/res/drawable/icon_cat_default_head_svg.xml new file mode 100644 index 0000000..f6a1186 --- /dev/null +++ b/app/src/main/res/drawable/icon_cat_default_head_svg.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_cat_height_tip_svg.xml b/app/src/main/res/drawable/icon_cat_height_tip_svg.xml new file mode 100644 index 0000000..65c4885 --- /dev/null +++ b/app/src/main/res/drawable/icon_cat_height_tip_svg.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_cat_image_svg.xml b/app/src/main/res/drawable/icon_cat_image_svg.xml new file mode 100644 index 0000000..6f85401 --- /dev/null +++ b/app/src/main/res/drawable/icon_cat_image_svg.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_cat_type_image.png b/app/src/main/res/drawable/icon_cat_type_image.png new file mode 100644 index 0000000..435ed7d Binary files /dev/null and b/app/src/main/res/drawable/icon_cat_type_image.png differ diff --git a/app/src/main/res/drawable/icon_cells_svg.xml b/app/src/main/res/drawable/icon_cells_svg.xml new file mode 100644 index 0000000..24cae55 --- /dev/null +++ b/app/src/main/res/drawable/icon_cells_svg.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_cellular_network_svg.xml b/app/src/main/res/drawable/icon_cellular_network_svg.xml new file mode 100644 index 0000000..c22b345 --- /dev/null +++ b/app/src/main/res/drawable/icon_cellular_network_svg.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_circle_ring_bg.xml b/app/src/main/res/drawable/icon_circle_ring_bg.xml new file mode 100644 index 0000000..8c72392 --- /dev/null +++ b/app/src/main/res/drawable/icon_circle_ring_bg.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_clock.xml b/app/src/main/res/drawable/icon_clock.xml new file mode 100644 index 0000000..ecaabe6 --- /dev/null +++ b/app/src/main/res/drawable/icon_clock.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/icon_clock_active.xml b/app/src/main/res/drawable/icon_clock_active.xml new file mode 100644 index 0000000..93c1c32 --- /dev/null +++ b/app/src/main/res/drawable/icon_clock_active.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_dark_green_star_svg.xml b/app/src/main/res/drawable/icon_dark_green_star_svg.xml new file mode 100644 index 0000000..f8443c1 --- /dev/null +++ b/app/src/main/res/drawable/icon_dark_green_star_svg.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/icon_database_restore_svg.xml b/app/src/main/res/drawable/icon_database_restore_svg.xml new file mode 100644 index 0000000..0ae2a93 --- /dev/null +++ b/app/src/main/res/drawable/icon_database_restore_svg.xml @@ -0,0 +1,36 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_date_svg.xml b/app/src/main/res/drawable/icon_date_svg.xml new file mode 100644 index 0000000..a34b13a --- /dev/null +++ b/app/src/main/res/drawable/icon_date_svg.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_date_time_svg.xml b/app/src/main/res/drawable/icon_date_time_svg.xml new file mode 100644 index 0000000..f010a0a --- /dev/null +++ b/app/src/main/res/drawable/icon_date_time_svg.xml @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_day_1_devotee_lock_svg.xml b/app/src/main/res/drawable/icon_day_1_devotee_lock_svg.xml new file mode 100644 index 0000000..15abaf3 --- /dev/null +++ b/app/src/main/res/drawable/icon_day_1_devotee_lock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_day_1_devotee_unlock_svg.xml b/app/src/main/res/drawable/icon_day_1_devotee_unlock_svg.xml new file mode 100644 index 0000000..c685e17 --- /dev/null +++ b/app/src/main/res/drawable/icon_day_1_devotee_unlock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_delete_svg.xml b/app/src/main/res/drawable/icon_delete_svg.xml new file mode 100644 index 0000000..f7c7ba3 --- /dev/null +++ b/app/src/main/res/drawable/icon_delete_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_device_battery_bg_svg.xml b/app/src/main/res/drawable/icon_device_battery_bg_svg.xml new file mode 100644 index 0000000..c527b6b --- /dev/null +++ b/app/src/main/res/drawable/icon_device_battery_bg_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_diners_club_svg.xml b/app/src/main/res/drawable/icon_diners_club_svg.xml new file mode 100644 index 0000000..92bd4a1 --- /dev/null +++ b/app/src/main/res/drawable/icon_diners_club_svg.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/icon_discover_card_svg.xml b/app/src/main/res/drawable/icon_discover_card_svg.xml new file mode 100644 index 0000000..6ae0d8e --- /dev/null +++ b/app/src/main/res/drawable/icon_discover_card_svg.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_distance.xml b/app/src/main/res/drawable/icon_distance.xml new file mode 100644 index 0000000..c85a70e --- /dev/null +++ b/app/src/main/res/drawable/icon_distance.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_dog_default_head_svg.xml b/app/src/main/res/drawable/icon_dog_default_head_svg.xml new file mode 100644 index 0000000..d31c85b --- /dev/null +++ b/app/src/main/res/drawable/icon_dog_default_head_svg.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_dog_image_svg.xml b/app/src/main/res/drawable/icon_dog_image_svg.xml new file mode 100644 index 0000000..df9cee4 --- /dev/null +++ b/app/src/main/res/drawable/icon_dog_image_svg.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_dog_type_image.png b/app/src/main/res/drawable/icon_dog_type_image.png new file mode 100644 index 0000000..019cfc4 Binary files /dev/null and b/app/src/main/res/drawable/icon_dog_type_image.png differ diff --git a/app/src/main/res/drawable/icon_drop_down_svg.xml b/app/src/main/res/drawable/icon_drop_down_svg.xml new file mode 100644 index 0000000..e6ad75c --- /dev/null +++ b/app/src/main/res/drawable/icon_drop_down_svg.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/icon_edit_svg.xml b/app/src/main/res/drawable/icon_edit_svg.xml new file mode 100644 index 0000000..bb9161c --- /dev/null +++ b/app/src/main/res/drawable/icon_edit_svg.xml @@ -0,0 +1,19 @@ + + + + diff --git a/app/src/main/res/drawable/icon_family_boy_avatar.png b/app/src/main/res/drawable/icon_family_boy_avatar.png new file mode 100644 index 0000000..eb6b5f7 Binary files /dev/null and b/app/src/main/res/drawable/icon_family_boy_avatar.png differ diff --git a/app/src/main/res/drawable/icon_family_girl_avatar.png b/app/src/main/res/drawable/icon_family_girl_avatar.png new file mode 100644 index 0000000..8f72338 Binary files /dev/null and b/app/src/main/res/drawable/icon_family_girl_avatar.png differ diff --git a/app/src/main/res/drawable/icon_family_invitation_bg.png b/app/src/main/res/drawable/icon_family_invitation_bg.png new file mode 100644 index 0000000..28c8d93 Binary files /dev/null and b/app/src/main/res/drawable/icon_family_invitation_bg.png differ diff --git a/app/src/main/res/drawable/icon_female_svg.xml b/app/src/main/res/drawable/icon_female_svg.xml new file mode 100644 index 0000000..fac87db --- /dev/null +++ b/app/src/main/res/drawable/icon_female_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_fence_circle_scale_red_svg.xml b/app/src/main/res/drawable/icon_fence_circle_scale_red_svg.xml new file mode 100644 index 0000000..3e3ec89 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_circle_scale_red_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_fence_circle_scale_svg.xml b/app/src/main/res/drawable/icon_fence_circle_scale_svg.xml new file mode 100644 index 0000000..f7fa1c8 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_circle_scale_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_fence_circle_shape_select_svg.xml b/app/src/main/res/drawable/icon_fence_circle_shape_select_svg.xml new file mode 100644 index 0000000..de9a175 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_circle_shape_select_svg.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/icon_fence_circle_shape_svg.xml b/app/src/main/res/drawable/icon_fence_circle_shape_svg.xml new file mode 100644 index 0000000..2e84b92 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_circle_shape_svg.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/icon_fence_dot_red_svg.xml b/app/src/main/res/drawable/icon_fence_dot_red_svg.xml new file mode 100644 index 0000000..b3c244f --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_dot_red_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_fence_dot_svg.xml b/app/src/main/res/drawable/icon_fence_dot_svg.xml new file mode 100644 index 0000000..06819e5 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_dot_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_fence_polygon_operate_red_svg.xml b/app/src/main/res/drawable/icon_fence_polygon_operate_red_svg.xml new file mode 100644 index 0000000..190ae66 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_polygon_operate_red_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_fence_polygon_operate_svg.xml b/app/src/main/res/drawable/icon_fence_polygon_operate_svg.xml new file mode 100644 index 0000000..61e1665 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_polygon_operate_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_fence_polygon_shape_select_svg.xml b/app/src/main/res/drawable/icon_fence_polygon_shape_select_svg.xml new file mode 100644 index 0000000..3cc7740 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_polygon_shape_select_svg.xml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_fence_polygon_shape_svg.xml b/app/src/main/res/drawable/icon_fence_polygon_shape_svg.xml new file mode 100644 index 0000000..fa3f65b --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_polygon_shape_svg.xml @@ -0,0 +1,41 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_fence_rect_red_rotate_svg.xml b/app/src/main/res/drawable/icon_fence_rect_red_rotate_svg.xml new file mode 100644 index 0000000..c3b1cac --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_rect_red_rotate_svg.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_fence_rect_rotate_svg.xml b/app/src/main/res/drawable/icon_fence_rect_rotate_svg.xml new file mode 100644 index 0000000..d5704f3 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_rect_rotate_svg.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_fence_rect_shape_select_svg.xml b/app/src/main/res/drawable/icon_fence_rect_shape_select_svg.xml new file mode 100644 index 0000000..ade400b --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_rect_shape_select_svg.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_fence_rect_shape_svg.xml b/app/src/main/res/drawable/icon_fence_rect_shape_svg.xml new file mode 100644 index 0000000..65b256e --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_rect_shape_svg.xml @@ -0,0 +1,31 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_fence_rect_zoom_red_svg.xml b/app/src/main/res/drawable/icon_fence_rect_zoom_red_svg.xml new file mode 100644 index 0000000..609a0ae --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_rect_zoom_red_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_fence_rect_zoom_svg.xml b/app/src/main/res/drawable/icon_fence_rect_zoom_svg.xml new file mode 100644 index 0000000..96b3207 --- /dev/null +++ b/app/src/main/res/drawable/icon_fence_rect_zoom_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_fences_zone_gps_green_svg.xml b/app/src/main/res/drawable/icon_fences_zone_gps_green_svg.xml new file mode 100644 index 0000000..0ad3bf3 --- /dev/null +++ b/app/src/main/res/drawable/icon_fences_zone_gps_green_svg.xml @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_fences_zone_gps_red_svg.xml b/app/src/main/res/drawable/icon_fences_zone_gps_red_svg.xml new file mode 100644 index 0000000..315df92 --- /dev/null +++ b/app/src/main/res/drawable/icon_fences_zone_gps_red_svg.xml @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_fences_zone_gps_svg.xml b/app/src/main/res/drawable/icon_fences_zone_gps_svg.xml new file mode 100644 index 0000000..8e57bf6 --- /dev/null +++ b/app/src/main/res/drawable/icon_fences_zone_gps_svg.xml @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_fresh_paws_lock_svg.xml b/app/src/main/res/drawable/icon_fresh_paws_lock_svg.xml new file mode 100644 index 0000000..00f9358 --- /dev/null +++ b/app/src/main/res/drawable/icon_fresh_paws_lock_svg.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_fresh_paws_unlock_svg.xml b/app/src/main/res/drawable/icon_fresh_paws_unlock_svg.xml new file mode 100644 index 0000000..e835801 --- /dev/null +++ b/app/src/main/res/drawable/icon_fresh_paws_unlock_svg.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_furr_timer_friend_lock_svg.xml b/app/src/main/res/drawable/icon_furr_timer_friend_lock_svg.xml new file mode 100644 index 0000000..03a3797 --- /dev/null +++ b/app/src/main/res/drawable/icon_furr_timer_friend_lock_svg.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_furr_timer_friend_unlock_svg.xml b/app/src/main/res/drawable/icon_furr_timer_friend_unlock_svg.xml new file mode 100644 index 0000000..7a13e22 --- /dev/null +++ b/app/src/main/res/drawable/icon_furr_timer_friend_unlock_svg.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_furry_friend_lock_svg.xml b/app/src/main/res/drawable/icon_furry_friend_lock_svg.xml new file mode 100644 index 0000000..87e6fe5 --- /dev/null +++ b/app/src/main/res/drawable/icon_furry_friend_lock_svg.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_furry_friend_unlock_svg.xml b/app/src/main/res/drawable/icon_furry_friend_unlock_svg.xml new file mode 100644 index 0000000..94848eb --- /dev/null +++ b/app/src/main/res/drawable/icon_furry_friend_unlock_svg.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_gray_delete_svg.xml b/app/src/main/res/drawable/icon_gray_delete_svg.xml new file mode 100644 index 0000000..af8ec92 --- /dev/null +++ b/app/src/main/res/drawable/icon_gray_delete_svg.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_gray_down_arrow.xml b/app/src/main/res/drawable/icon_gray_down_arrow.xml new file mode 100644 index 0000000..82c90d2 --- /dev/null +++ b/app/src/main/res/drawable/icon_gray_down_arrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_gray_right_arrow.xml b/app/src/main/res/drawable/icon_gray_right_arrow.xml new file mode 100644 index 0000000..1867d75 --- /dev/null +++ b/app/src/main/res/drawable/icon_gray_right_arrow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_help_square_svg.xml b/app/src/main/res/drawable/icon_help_square_svg.xml new file mode 100644 index 0000000..9dfdbc8 --- /dev/null +++ b/app/src/main/res/drawable/icon_help_square_svg.xml @@ -0,0 +1,24 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_hide_password_svg.xml b/app/src/main/res/drawable/icon_hide_password_svg.xml new file mode 100644 index 0000000..926a82f --- /dev/null +++ b/app/src/main/res/drawable/icon_hide_password_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_history_point_activity_svg.xml b/app/src/main/res/drawable/icon_history_point_activity_svg.xml new file mode 100644 index 0000000..aee5396 --- /dev/null +++ b/app/src/main/res/drawable/icon_history_point_activity_svg.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_history_point_svg.xml b/app/src/main/res/drawable/icon_history_point_svg.xml new file mode 100644 index 0000000..9f60240 --- /dev/null +++ b/app/src/main/res/drawable/icon_history_point_svg.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_history_track_activity_svg.xml b/app/src/main/res/drawable/icon_history_track_activity_svg.xml new file mode 100644 index 0000000..6b7937c --- /dev/null +++ b/app/src/main/res/drawable/icon_history_track_activity_svg.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_history_track_svg.xml b/app/src/main/res/drawable/icon_history_track_svg.xml new file mode 100644 index 0000000..4fbfd37 --- /dev/null +++ b/app/src/main/res/drawable/icon_history_track_svg.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_input_check_svg.xml b/app/src/main/res/drawable/icon_input_check_svg.xml new file mode 100644 index 0000000..b513044 --- /dev/null +++ b/app/src/main/res/drawable/icon_input_check_svg.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/icon_label_select_svg.xml b/app/src/main/res/drawable/icon_label_select_svg.xml new file mode 100644 index 0000000..a864203 --- /dev/null +++ b/app/src/main/res/drawable/icon_label_select_svg.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/drawable/icon_light_svg.xml b/app/src/main/res/drawable/icon_light_svg.xml new file mode 100644 index 0000000..21d9c4e --- /dev/null +++ b/app/src/main/res/drawable/icon_light_svg.xml @@ -0,0 +1,49 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_link_svg.xml b/app/src/main/res/drawable/icon_link_svg.xml new file mode 100644 index 0000000..95b696f --- /dev/null +++ b/app/src/main/res/drawable/icon_link_svg.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_location_svg.xml b/app/src/main/res/drawable/icon_location_svg.xml new file mode 100644 index 0000000..a0deec3 --- /dev/null +++ b/app/src/main/res/drawable/icon_location_svg.xml @@ -0,0 +1,17 @@ + + + + diff --git a/app/src/main/res/drawable/icon_login_v2_bg.png b/app/src/main/res/drawable/icon_login_v2_bg.png new file mode 100644 index 0000000..c9b27d8 Binary files /dev/null and b/app/src/main/res/drawable/icon_login_v2_bg.png differ diff --git a/app/src/main/res/drawable/icon_logout_svg.xml b/app/src/main/res/drawable/icon_logout_svg.xml new file mode 100644 index 0000000..972d4a5 --- /dev/null +++ b/app/src/main/res/drawable/icon_logout_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_lr_arrow_svg.xml b/app/src/main/res/drawable/icon_lr_arrow_svg.xml new file mode 100644 index 0000000..9dff686 --- /dev/null +++ b/app/src/main/res/drawable/icon_lr_arrow_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_male_svg.xml b/app/src/main/res/drawable/icon_male_svg.xml new file mode 100644 index 0000000..83fc8a0 --- /dev/null +++ b/app/src/main/res/drawable/icon_male_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_map_bluetooth.xml b/app/src/main/res/drawable/icon_map_bluetooth.xml new file mode 100644 index 0000000..7f218e8 --- /dev/null +++ b/app/src/main/res/drawable/icon_map_bluetooth.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_map_gps.xml b/app/src/main/res/drawable/icon_map_gps.xml new file mode 100644 index 0000000..c73951e --- /dev/null +++ b/app/src/main/res/drawable/icon_map_gps.xml @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_map_navigation_svg.xml b/app/src/main/res/drawable/icon_map_navigation_svg.xml new file mode 100644 index 0000000..f4b6868 --- /dev/null +++ b/app/src/main/res/drawable/icon_map_navigation_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_map_offline.xml b/app/src/main/res/drawable/icon_map_offline.xml new file mode 100644 index 0000000..525a797 --- /dev/null +++ b/app/src/main/res/drawable/icon_map_offline.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_map_online.xml b/app/src/main/res/drawable/icon_map_online.xml new file mode 100644 index 0000000..fcad34f --- /dev/null +++ b/app/src/main/res/drawable/icon_map_online.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_map_satellite_type.png b/app/src/main/res/drawable/icon_map_satellite_type.png new file mode 100644 index 0000000..086ea04 Binary files /dev/null and b/app/src/main/res/drawable/icon_map_satellite_type.png differ diff --git a/app/src/main/res/drawable/icon_map_signal.xml b/app/src/main/res/drawable/icon_map_signal.xml new file mode 100644 index 0000000..112cd75 --- /dev/null +++ b/app/src/main/res/drawable/icon_map_signal.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_map_signal_svg.xml b/app/src/main/res/drawable/icon_map_signal_svg.xml new file mode 100644 index 0000000..0ea824d --- /dev/null +++ b/app/src/main/res/drawable/icon_map_signal_svg.xml @@ -0,0 +1,42 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_map_standard_type.png b/app/src/main/res/drawable/icon_map_standard_type.png new file mode 100644 index 0000000..b2ae851 Binary files /dev/null and b/app/src/main/res/drawable/icon_map_standard_type.png differ diff --git a/app/src/main/res/drawable/icon_map_type.xml b/app/src/main/res/drawable/icon_map_type.xml new file mode 100644 index 0000000..c4771d9 --- /dev/null +++ b/app/src/main/res/drawable/icon_map_type.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_map_zone.xml b/app/src/main/res/drawable/icon_map_zone.xml new file mode 100644 index 0000000..cbc4997 --- /dev/null +++ b/app/src/main/res/drawable/icon_map_zone.xml @@ -0,0 +1,40 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_master_card_svg.xml b/app/src/main/res/drawable/icon_master_card_svg.xml new file mode 100644 index 0000000..a75c712 --- /dev/null +++ b/app/src/main/res/drawable/icon_master_card_svg.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_measurement_units_svg.xml b/app/src/main/res/drawable/icon_measurement_units_svg.xml new file mode 100644 index 0000000..d3b7133 --- /dev/null +++ b/app/src/main/res/drawable/icon_measurement_units_svg.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_month__mate_lock_svg.xml b/app/src/main/res/drawable/icon_month__mate_lock_svg.xml new file mode 100644 index 0000000..9ee7241 --- /dev/null +++ b/app/src/main/res/drawable/icon_month__mate_lock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_month_mate_unlock_svg.xml b/app/src/main/res/drawable/icon_month_mate_unlock_svg.xml new file mode 100644 index 0000000..3310ef8 --- /dev/null +++ b/app/src/main/res/drawable/icon_month_mate_unlock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_more_sleep_bg.png b/app/src/main/res/drawable/icon_more_sleep_bg.png new file mode 100644 index 0000000..92e3404 Binary files /dev/null and b/app/src/main/res/drawable/icon_more_sleep_bg.png differ diff --git a/app/src/main/res/drawable/icon_my_tracker_svg.xml b/app/src/main/res/drawable/icon_my_tracker_svg.xml new file mode 100644 index 0000000..0d4ec69 --- /dev/null +++ b/app/src/main/res/drawable/icon_my_tracker_svg.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_newbie_navigator_lock_svg.xml b/app/src/main/res/drawable/icon_newbie_navigator_lock_svg.xml new file mode 100644 index 0000000..fa3f975 --- /dev/null +++ b/app/src/main/res/drawable/icon_newbie_navigator_lock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_newbie_navigator_unlock_svg.xml b/app/src/main/res/drawable/icon_newbie_navigator_unlock_svg.xml new file mode 100644 index 0000000..6316f0a --- /dev/null +++ b/app/src/main/res/drawable/icon_newbie_navigator_unlock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_no_zone_type1_small_svg.xml b/app/src/main/res/drawable/icon_no_zone_type1_small_svg.xml new file mode 100644 index 0000000..082e6c3 --- /dev/null +++ b/app/src/main/res/drawable/icon_no_zone_type1_small_svg.xml @@ -0,0 +1,50 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_no_zone_type1_svg.xml b/app/src/main/res/drawable/icon_no_zone_type1_svg.xml new file mode 100644 index 0000000..22d53c8 --- /dev/null +++ b/app/src/main/res/drawable/icon_no_zone_type1_svg.xml @@ -0,0 +1,53 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_no_zone_type2_small_svg.xml b/app/src/main/res/drawable/icon_no_zone_type2_small_svg.xml new file mode 100644 index 0000000..8058f9e --- /dev/null +++ b/app/src/main/res/drawable/icon_no_zone_type2_small_svg.xml @@ -0,0 +1,51 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_no_zone_type2_svg.xml b/app/src/main/res/drawable/icon_no_zone_type2_svg.xml new file mode 100644 index 0000000..a0268c4 --- /dev/null +++ b/app/src/main/res/drawable/icon_no_zone_type2_svg.xml @@ -0,0 +1,54 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_no_zone_type3_small_svg.xml b/app/src/main/res/drawable/icon_no_zone_type3_small_svg.xml new file mode 100644 index 0000000..f3ab75e --- /dev/null +++ b/app/src/main/res/drawable/icon_no_zone_type3_small_svg.xml @@ -0,0 +1,43 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_no_zone_type3_svg.xml b/app/src/main/res/drawable/icon_no_zone_type3_svg.xml new file mode 100644 index 0000000..4b8fbfc --- /dev/null +++ b/app/src/main/res/drawable/icon_no_zone_type3_svg.xml @@ -0,0 +1,46 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_noti_svg.xml b/app/src/main/res/drawable/icon_noti_svg.xml new file mode 100644 index 0000000..5c7d6b9 --- /dev/null +++ b/app/src/main/res/drawable/icon_noti_svg.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/icon_notification_svg.xml b/app/src/main/res/drawable/icon_notification_svg.xml new file mode 100644 index 0000000..9fbc7e5 --- /dev/null +++ b/app/src/main/res/drawable/icon_notification_svg.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/icon_pair_tracker_image1.png b/app/src/main/res/drawable/icon_pair_tracker_image1.png new file mode 100644 index 0000000..0dcfad2 Binary files /dev/null and b/app/src/main/res/drawable/icon_pair_tracker_image1.png differ diff --git a/app/src/main/res/drawable/icon_pair_tracker_image2.png b/app/src/main/res/drawable/icon_pair_tracker_image2.png new file mode 100644 index 0000000..07db5ec Binary files /dev/null and b/app/src/main/res/drawable/icon_pair_tracker_image2.png differ diff --git a/app/src/main/res/drawable/icon_pawsome_partner_lock_svg.xml b/app/src/main/res/drawable/icon_pawsome_partner_lock_svg.xml new file mode 100644 index 0000000..ab24178 --- /dev/null +++ b/app/src/main/res/drawable/icon_pawsome_partner_lock_svg.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_pawsome_partner_unlock_svg.xml b/app/src/main/res/drawable/icon_pawsome_partner_unlock_svg.xml new file mode 100644 index 0000000..bf32dc2 --- /dev/null +++ b/app/src/main/res/drawable/icon_pawsome_partner_unlock_svg.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_pawtential_pal_lock_svg.xml b/app/src/main/res/drawable/icon_pawtential_pal_lock_svg.xml new file mode 100644 index 0000000..28d3a06 --- /dev/null +++ b/app/src/main/res/drawable/icon_pawtential_pal_lock_svg.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_pawtential_pal_unlock_svg.xml b/app/src/main/res/drawable/icon_pawtential_pal_unlock_svg.xml new file mode 100644 index 0000000..4585a97 --- /dev/null +++ b/app/src/main/res/drawable/icon_pawtential_pal_unlock_svg.xml @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_payment_credit_card_svg.xml b/app/src/main/res/drawable/icon_payment_credit_card_svg.xml new file mode 100644 index 0000000..d570985 --- /dev/null +++ b/app/src/main/res/drawable/icon_payment_credit_card_svg.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_payment_paypal_svg.xml b/app/src/main/res/drawable/icon_payment_paypal_svg.xml new file mode 100644 index 0000000..886f4ab --- /dev/null +++ b/app/src/main/res/drawable/icon_payment_paypal_svg.xml @@ -0,0 +1,24 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_permission_check_svg.xml b/app/src/main/res/drawable/icon_permission_check_svg.xml new file mode 100644 index 0000000..f6a449a --- /dev/null +++ b/app/src/main/res/drawable/icon_permission_check_svg.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/icon_permission_error_check_svg.xml b/app/src/main/res/drawable/icon_permission_error_check_svg.xml new file mode 100644 index 0000000..10a00a6 --- /dev/null +++ b/app/src/main/res/drawable/icon_permission_error_check_svg.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/icon_permission_no_check_svg.xml b/app/src/main/res/drawable/icon_permission_no_check_svg.xml new file mode 100644 index 0000000..d4957c0 --- /dev/null +++ b/app/src/main/res/drawable/icon_permission_no_check_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_permissions_svg.xml b/app/src/main/res/drawable/icon_permissions_svg.xml new file mode 100644 index 0000000..2b471fd --- /dev/null +++ b/app/src/main/res/drawable/icon_permissions_svg.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_pet_fence_svg.xml b/app/src/main/res/drawable/icon_pet_fence_svg.xml new file mode 100644 index 0000000..0411803 --- /dev/null +++ b/app/src/main/res/drawable/icon_pet_fence_svg.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_pet_foot_svg.xml b/app/src/main/res/drawable/icon_pet_foot_svg.xml new file mode 100644 index 0000000..e093a65 --- /dev/null +++ b/app/src/main/res/drawable/icon_pet_foot_svg.xml @@ -0,0 +1,35 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_pet_head_edit_svg.xml b/app/src/main/res/drawable/icon_pet_head_edit_svg.xml new file mode 100644 index 0000000..182ec20 --- /dev/null +++ b/app/src/main/res/drawable/icon_pet_head_edit_svg.xml @@ -0,0 +1,26 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_pet_height_tip_svg.xml b/app/src/main/res/drawable/icon_pet_height_tip_svg.xml new file mode 100644 index 0000000..b369c2f --- /dev/null +++ b/app/src/main/res/drawable/icon_pet_height_tip_svg.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_pet_share_svg.xml b/app/src/main/res/drawable/icon_pet_share_svg.xml new file mode 100644 index 0000000..11ee163 --- /dev/null +++ b/app/src/main/res/drawable/icon_pet_share_svg.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_placeholder_image_svg.xml b/app/src/main/res/drawable/icon_placeholder_image_svg.xml new file mode 100644 index 0000000..5c487b7 --- /dev/null +++ b/app/src/main/res/drawable/icon_placeholder_image_svg.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_point_bg_svg.xml b/app/src/main/res/drawable/icon_point_bg_svg.xml new file mode 100644 index 0000000..d043208 --- /dev/null +++ b/app/src/main/res/drawable/icon_point_bg_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_profile_birthday_edit_svg.xml b/app/src/main/res/drawable/icon_profile_birthday_edit_svg.xml new file mode 100644 index 0000000..172043f --- /dev/null +++ b/app/src/main/res/drawable/icon_profile_birthday_edit_svg.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_purrfect_partner_lock_svg.xml b/app/src/main/res/drawable/icon_purrfect_partner_lock_svg.xml new file mode 100644 index 0000000..89502e1 --- /dev/null +++ b/app/src/main/res/drawable/icon_purrfect_partner_lock_svg.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_purrfect_partner_unlock_svg.xml b/app/src/main/res/drawable/icon_purrfect_partner_unlock_svg.xml new file mode 100644 index 0000000..c92ba56 --- /dev/null +++ b/app/src/main/res/drawable/icon_purrfect_partner_unlock_svg.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_red_dot.xml b/app/src/main/res/drawable/icon_red_dot.xml new file mode 100644 index 0000000..bb578a3 --- /dev/null +++ b/app/src/main/res/drawable/icon_red_dot.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_refresh.xml b/app/src/main/res/drawable/icon_refresh.xml new file mode 100644 index 0000000..74a4c93 --- /dev/null +++ b/app/src/main/res/drawable/icon_refresh.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type1_small_svg.xml b/app/src/main/res/drawable/icon_save_zone_type1_small_svg.xml new file mode 100644 index 0000000..e784a39 --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type1_small_svg.xml @@ -0,0 +1,52 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type1_svg.xml b/app/src/main/res/drawable/icon_save_zone_type1_svg.xml new file mode 100644 index 0000000..1fc3f8c --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type1_svg.xml @@ -0,0 +1,55 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type2_small_svg.xml b/app/src/main/res/drawable/icon_save_zone_type2_small_svg.xml new file mode 100644 index 0000000..87b4a19 --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type2_small_svg.xml @@ -0,0 +1,49 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type2_svg.xml b/app/src/main/res/drawable/icon_save_zone_type2_svg.xml new file mode 100644 index 0000000..417d9f4 --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type2_svg.xml @@ -0,0 +1,52 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type3_small_svg.xml b/app/src/main/res/drawable/icon_save_zone_type3_small_svg.xml new file mode 100644 index 0000000..df8556b --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type3_small_svg.xml @@ -0,0 +1,41 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type3_svg.xml b/app/src/main/res/drawable/icon_save_zone_type3_svg.xml new file mode 100644 index 0000000..dcfb05f --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type3_svg.xml @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type4_small_svg.xml b/app/src/main/res/drawable/icon_save_zone_type4_small_svg.xml new file mode 100644 index 0000000..e5120c4 --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type4_small_svg.xml @@ -0,0 +1,48 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type4_svg.xml b/app/src/main/res/drawable/icon_save_zone_type4_svg.xml new file mode 100644 index 0000000..3c4e667 --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type4_svg.xml @@ -0,0 +1,51 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type5_small_svg.xml b/app/src/main/res/drawable/icon_save_zone_type5_small_svg.xml new file mode 100644 index 0000000..18a8c00 --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type5_small_svg.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_save_zone_type5_svg.xml b/app/src/main/res/drawable/icon_save_zone_type5_svg.xml new file mode 100644 index 0000000..7f6b03a --- /dev/null +++ b/app/src/main/res/drawable/icon_save_zone_type5_svg.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_say_hi_bg.png b/app/src/main/res/drawable/icon_say_hi_bg.png new file mode 100644 index 0000000..788a7a9 Binary files /dev/null and b/app/src/main/res/drawable/icon_say_hi_bg.png differ diff --git a/app/src/main/res/drawable/icon_selected_point_svg.xml b/app/src/main/res/drawable/icon_selected_point_svg.xml new file mode 100644 index 0000000..4575891 --- /dev/null +++ b/app/src/main/res/drawable/icon_selected_point_svg.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/icon_share_activity_svg1.xml b/app/src/main/res/drawable/icon_share_activity_svg1.xml new file mode 100644 index 0000000..528e792 --- /dev/null +++ b/app/src/main/res/drawable/icon_share_activity_svg1.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_share_activity_svg2.xml b/app/src/main/res/drawable/icon_share_activity_svg2.xml new file mode 100644 index 0000000..51559a1 --- /dev/null +++ b/app/src/main/res/drawable/icon_share_activity_svg2.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_share_svg1.xml b/app/src/main/res/drawable/icon_share_svg1.xml new file mode 100644 index 0000000..21f5c91 --- /dev/null +++ b/app/src/main/res/drawable/icon_share_svg1.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_share_svg2.xml b/app/src/main/res/drawable/icon_share_svg2.xml new file mode 100644 index 0000000..a7691a1 --- /dev/null +++ b/app/src/main/res/drawable/icon_share_svg2.xml @@ -0,0 +1,26 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_show_password_svg.xml b/app/src/main/res/drawable/icon_show_password_svg.xml new file mode 100644 index 0000000..5dc0252 --- /dev/null +++ b/app/src/main/res/drawable/icon_show_password_svg.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_sleep_time.xml b/app/src/main/res/drawable/icon_sleep_time.xml new file mode 100644 index 0000000..7cb7436 --- /dev/null +++ b/app/src/main/res/drawable/icon_sleep_time.xml @@ -0,0 +1,26 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_smart_phone_svg.xml b/app/src/main/res/drawable/icon_smart_phone_svg.xml new file mode 100644 index 0000000..dc883fa --- /dev/null +++ b/app/src/main/res/drawable/icon_smart_phone_svg.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_socialite_lock_svg.xml b/app/src/main/res/drawable/icon_socialite_lock_svg.xml new file mode 100644 index 0000000..d533ea6 --- /dev/null +++ b/app/src/main/res/drawable/icon_socialite_lock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_socialite_unlock_svg.xml b/app/src/main/res/drawable/icon_socialite_unlock_svg.xml new file mode 100644 index 0000000..13d5919 --- /dev/null +++ b/app/src/main/res/drawable/icon_socialite_unlock_svg.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_sound_off_svg.xml b/app/src/main/res/drawable/icon_sound_off_svg.xml new file mode 100644 index 0000000..29a065f --- /dev/null +++ b/app/src/main/res/drawable/icon_sound_off_svg.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_sound_on_svg.xml b/app/src/main/res/drawable/icon_sound_on_svg.xml new file mode 100644 index 0000000..12a6bce --- /dev/null +++ b/app/src/main/res/drawable/icon_sound_on_svg.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_store_svg.xml b/app/src/main/res/drawable/icon_store_svg.xml new file mode 100644 index 0000000..8a602c6 --- /dev/null +++ b/app/src/main/res/drawable/icon_store_svg.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_subscription_svg.xml b/app/src/main/res/drawable/icon_subscription_svg.xml new file mode 100644 index 0000000..39d8f84 --- /dev/null +++ b/app/src/main/res/drawable/icon_subscription_svg.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/app/src/main/res/drawable/icon_tailwag_explorer_lock_svg.xml b/app/src/main/res/drawable/icon_tailwag_explorer_lock_svg.xml new file mode 100644 index 0000000..a352579 --- /dev/null +++ b/app/src/main/res/drawable/icon_tailwag_explorer_lock_svg.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_tailwag_explorer_unlock_svg.xml b/app/src/main/res/drawable/icon_tailwag_explorer_unlock_svg.xml new file mode 100644 index 0000000..df25eab --- /dev/null +++ b/app/src/main/res/drawable/icon_tailwag_explorer_unlock_svg.xml @@ -0,0 +1,16 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_tip_image_svg.xml b/app/src/main/res/drawable/icon_tip_image_svg.xml new file mode 100644 index 0000000..282090d --- /dev/null +++ b/app/src/main/res/drawable/icon_tip_image_svg.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/icon_total_distance_svg.xml b/app/src/main/res/drawable/icon_total_distance_svg.xml new file mode 100644 index 0000000..18ce2e9 --- /dev/null +++ b/app/src/main/res/drawable/icon_total_distance_svg.xml @@ -0,0 +1,37 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_unselected_point_svg.xml b/app/src/main/res/drawable/icon_unselected_point_svg.xml new file mode 100644 index 0000000..cbdfb37 --- /dev/null +++ b/app/src/main/res/drawable/icon_unselected_point_svg.xml @@ -0,0 +1,11 @@ + + + diff --git a/app/src/main/res/drawable/icon_upload_image_svg.xml b/app/src/main/res/drawable/icon_upload_image_svg.xml new file mode 100644 index 0000000..b7ec900 --- /dev/null +++ b/app/src/main/res/drawable/icon_upload_image_svg.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/icon_user_head_svg.xml b/app/src/main/res/drawable/icon_user_head_svg.xml new file mode 100644 index 0000000..a2fbcd0 --- /dev/null +++ b/app/src/main/res/drawable/icon_user_head_svg.xml @@ -0,0 +1,18 @@ + + + + diff --git a/app/src/main/res/drawable/icon_weighting_scale_svg.xml b/app/src/main/res/drawable/icon_weighting_scale_svg.xml new file mode 100644 index 0000000..80490e1 --- /dev/null +++ b/app/src/main/res/drawable/icon_weighting_scale_svg.xml @@ -0,0 +1,18 @@ + + + + + diff --git a/app/src/main/res/drawable/icon_white_back_svg.xml b/app/src/main/res/drawable/icon_white_back_svg.xml new file mode 100644 index 0000000..e4b2f89 --- /dev/null +++ b/app/src/main/res/drawable/icon_white_back_svg.xml @@ -0,0 +1,16 @@ + + + + diff --git a/app/src/main/res/drawable/icon_white_time.xml b/app/src/main/res/drawable/icon_white_time.xml new file mode 100644 index 0000000..8d09c9a --- /dev/null +++ b/app/src/main/res/drawable/icon_white_time.xml @@ -0,0 +1,14 @@ + + + + diff --git a/app/src/main/res/drawable/icon_wif_location_svg.xml b/app/src/main/res/drawable/icon_wif_location_svg.xml new file mode 100644 index 0000000..7e2e3d9 --- /dev/null +++ b/app/src/main/res/drawable/icon_wif_location_svg.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/app/src/main/res/drawable/icon_wifi_zone_head_bg.png b/app/src/main/res/drawable/icon_wifi_zone_head_bg.png new file mode 100644 index 0000000..faff19a Binary files /dev/null and b/app/src/main/res/drawable/icon_wifi_zone_head_bg.png differ diff --git a/app/src/main/res/drawable/icon_wifi_zone_home_type_svg.xml b/app/src/main/res/drawable/icon_wifi_zone_home_type_svg.xml new file mode 100644 index 0000000..d2b5e03 --- /dev/null +++ b/app/src/main/res/drawable/icon_wifi_zone_home_type_svg.xml @@ -0,0 +1,58 @@ + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_wifi_zone_office_type_svg.xml b/app/src/main/res/drawable/icon_wifi_zone_office_type_svg.xml new file mode 100644 index 0000000..d0b4578 --- /dev/null +++ b/app/src/main/res/drawable/icon_wifi_zone_office_type_svg.xml @@ -0,0 +1,44 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/icon_wifi_zone_type1_svg.xml b/app/src/main/res/drawable/icon_wifi_zone_type1_svg.xml new file mode 100644 index 0000000..b675691 --- /dev/null +++ b/app/src/main/res/drawable/icon_wifi_zone_type1_svg.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_wifi_zone_type2_svg.xml b/app/src/main/res/drawable/icon_wifi_zone_type2_svg.xml new file mode 100644 index 0000000..df4ac2f --- /dev/null +++ b/app/src/main/res/drawable/icon_wifi_zone_type2_svg.xml @@ -0,0 +1,47 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/icon_yellow_no_check_svg.xml b/app/src/main/res/drawable/icon_yellow_no_check_svg.xml new file mode 100644 index 0000000..0039919 --- /dev/null +++ b/app/src/main/res/drawable/icon_yellow_no_check_svg.xml @@ -0,0 +1,7 @@ + + + + diff --git a/app/src/main/res/drawable/invite_family_animate_image1.png b/app/src/main/res/drawable/invite_family_animate_image1.png new file mode 100644 index 0000000..9c2ddbc Binary files /dev/null and b/app/src/main/res/drawable/invite_family_animate_image1.png differ diff --git a/app/src/main/res/drawable/invite_family_animate_image2.png b/app/src/main/res/drawable/invite_family_animate_image2.png new file mode 100644 index 0000000..8568577 Binary files /dev/null and b/app/src/main/res/drawable/invite_family_animate_image2.png differ diff --git a/app/src/main/res/drawable/invite_family_animate_image3.png b/app/src/main/res/drawable/invite_family_animate_image3.png new file mode 100644 index 0000000..f90308e Binary files /dev/null and b/app/src/main/res/drawable/invite_family_animate_image3.png differ diff --git a/app/src/main/res/drawable/invite_family_animate_image4.png b/app/src/main/res/drawable/invite_family_animate_image4.png new file mode 100644 index 0000000..9c2ddbc Binary files /dev/null and b/app/src/main/res/drawable/invite_family_animate_image4.png differ diff --git a/app/src/main/res/drawable/message_shape_bg.xml b/app/src/main/res/drawable/message_shape_bg.xml new file mode 100644 index 0000000..dbe17ad --- /dev/null +++ b/app/src/main/res/drawable/message_shape_bg.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progress_bar_progress_bg.xml b/app/src/main/res/drawable/progress_bar_progress_bg.xml new file mode 100644 index 0000000..7f9c60d --- /dev/null +++ b/app/src/main/res/drawable/progress_bar_progress_bg.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/pwd_reset_successful_done.gif b/app/src/main/res/drawable/pwd_reset_successful_done.gif new file mode 100644 index 0000000..37b9f34 Binary files /dev/null and b/app/src/main/res/drawable/pwd_reset_successful_done.gif differ diff --git a/app/src/main/res/drawable/qmui_s_icon_switch.xml b/app/src/main/res/drawable/qmui_s_icon_switch.xml new file mode 100644 index 0000000..88d5ab3 --- /dev/null +++ b/app/src/main/res/drawable/qmui_s_icon_switch.xml @@ -0,0 +1,9 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_button_enabled_text_color.xml b/app/src/main/res/drawable/selector_button_enabled_text_color.xml new file mode 100644 index 0000000..d2d763c --- /dev/null +++ b/app/src/main/res/drawable/selector_button_enabled_text_color.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_calendar_icon_pressed1.xml b/app/src/main/res/drawable/selector_calendar_icon_pressed1.xml new file mode 100644 index 0000000..f18bf57 --- /dev/null +++ b/app/src/main/res/drawable/selector_calendar_icon_pressed1.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_calendar_icon_pressed2.xml b/app/src/main/res/drawable/selector_calendar_icon_pressed2.xml new file mode 100644 index 0000000..bf6cf78 --- /dev/null +++ b/app/src/main/res/drawable/selector_calendar_icon_pressed2.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_dark_check_gray_bg.xml b/app/src/main/res/drawable/selector_dark_check_gray_bg.xml new file mode 100644 index 0000000..b2cc4a8 --- /dev/null +++ b/app/src/main/res/drawable/selector_dark_check_gray_bg.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_fence_circle_shape.xml b/app/src/main/res/drawable/selector_fence_circle_shape.xml new file mode 100644 index 0000000..b946ef6 --- /dev/null +++ b/app/src/main/res/drawable/selector_fence_circle_shape.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_fence_rectangle_shape.xml b/app/src/main/res/drawable/selector_fence_rectangle_shape.xml new file mode 100644 index 0000000..edd1d7d --- /dev/null +++ b/app/src/main/res/drawable/selector_fence_rectangle_shape.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_gray_icon_switch.xml b/app/src/main/res/drawable/selector_gray_icon_switch.xml new file mode 100644 index 0000000..5395027 --- /dev/null +++ b/app/src/main/res/drawable/selector_gray_icon_switch.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_green_yellow_btn_state.xml b/app/src/main/res/drawable/selector_green_yellow_btn_state.xml new file mode 100644 index 0000000..3c7d5c9 --- /dev/null +++ b/app/src/main/res/drawable/selector_green_yellow_btn_state.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/selector_history_point_check.xml b/app/src/main/res/drawable/selector_history_point_check.xml new file mode 100644 index 0000000..bd35d4d --- /dev/null +++ b/app/src/main/res/drawable/selector_history_point_check.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_history_point_rb.xml b/app/src/main/res/drawable/selector_history_point_rb.xml new file mode 100644 index 0000000..ccd594a --- /dev/null +++ b/app/src/main/res/drawable/selector_history_point_rb.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_history_track_check.xml b/app/src/main/res/drawable/selector_history_track_check.xml new file mode 100644 index 0000000..05c26b8 --- /dev/null +++ b/app/src/main/res/drawable/selector_history_track_check.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_history_track_rb.xml b/app/src/main/res/drawable/selector_history_track_rb.xml new file mode 100644 index 0000000..ea0973e --- /dev/null +++ b/app/src/main/res/drawable/selector_history_track_rb.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_home_data_gray_pressed1.xml b/app/src/main/res/drawable/selector_home_data_gray_pressed1.xml new file mode 100644 index 0000000..ee885a0 --- /dev/null +++ b/app/src/main/res/drawable/selector_home_data_gray_pressed1.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_home_data_gray_pressed2.xml b/app/src/main/res/drawable/selector_home_data_gray_pressed2.xml new file mode 100644 index 0000000..2cb6e16 --- /dev/null +++ b/app/src/main/res/drawable/selector_home_data_gray_pressed2.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_home_data_gray_pressed3.xml b/app/src/main/res/drawable/selector_home_data_gray_pressed3.xml new file mode 100644 index 0000000..3c2fb76 --- /dev/null +++ b/app/src/main/res/drawable/selector_home_data_gray_pressed3.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_man_check.xml b/app/src/main/res/drawable/selector_man_check.xml new file mode 100644 index 0000000..3821c00 --- /dev/null +++ b/app/src/main/res/drawable/selector_man_check.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_payment_method_check.xml b/app/src/main/res/drawable/selector_payment_method_check.xml new file mode 100644 index 0000000..fa5481a --- /dev/null +++ b/app/src/main/res/drawable/selector_payment_method_check.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_pet_type_switch_check.xml b/app/src/main/res/drawable/selector_pet_type_switch_check.xml new file mode 100644 index 0000000..538b227 --- /dev/null +++ b/app/src/main/res/drawable/selector_pet_type_switch_check.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_shape6_gray_bg_pressed.xml b/app/src/main/res/drawable/selector_shape6_gray_bg_pressed.xml new file mode 100644 index 0000000..f100353 --- /dev/null +++ b/app/src/main/res/drawable/selector_shape6_gray_bg_pressed.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_shape8_white_bg_pressed.xml b/app/src/main/res/drawable/selector_shape8_white_bg_pressed.xml new file mode 100644 index 0000000..dd4d6ee --- /dev/null +++ b/app/src/main/res/drawable/selector_shape8_white_bg_pressed.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_share_icon_pressed1.xml b/app/src/main/res/drawable/selector_share_icon_pressed1.xml new file mode 100644 index 0000000..55ca52e --- /dev/null +++ b/app/src/main/res/drawable/selector_share_icon_pressed1.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_share_icon_pressed2.xml b/app/src/main/res/drawable/selector_share_icon_pressed2.xml new file mode 100644 index 0000000..646f771 --- /dev/null +++ b/app/src/main/res/drawable/selector_share_icon_pressed2.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_transparent_pressed.xml b/app/src/main/res/drawable/selector_transparent_pressed.xml new file mode 100644 index 0000000..847cbd0 --- /dev/null +++ b/app/src/main/res/drawable/selector_transparent_pressed.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_woman_check.xml b/app/src/main/res/drawable/selector_woman_check.xml new file mode 100644 index 0000000..a73f351 --- /dev/null +++ b/app/src/main/res/drawable/selector_woman_check.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/selector_yellow_check_gray_bg.xml b/app/src/main/res/drawable/selector_yellow_check_gray_bg.xml new file mode 100644 index 0000000..ec6dda7 --- /dev/null +++ b/app/src/main/res/drawable/selector_yellow_check_gray_bg.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape100_yellow_color_bg.xml b/app/src/main/res/drawable/shape100_yellow_color_bg.xml new file mode 100644 index 0000000..4ed8053 --- /dev/null +++ b/app/src/main/res/drawable/shape100_yellow_color_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape10_feedback_dashed_gray_border_white_bg.xml b/app/src/main/res/drawable/shape10_feedback_dashed_gray_border_white_bg.xml new file mode 100644 index 0000000..5f778fb --- /dev/null +++ b/app/src/main/res/drawable/shape10_feedback_dashed_gray_border_white_bg.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape10_white_bg.xml b/app/src/main/res/drawable/shape10_white_bg.xml new file mode 100644 index 0000000..44544b8 --- /dev/null +++ b/app/src/main/res/drawable/shape10_white_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape10_white_border_gray_bg.xml b/app/src/main/res/drawable/shape10_white_border_gray_bg.xml new file mode 100644 index 0000000..bcc72e8 --- /dev/null +++ b/app/src/main/res/drawable/shape10_white_border_gray_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape10_yellow_bg.xml b/app/src/main/res/drawable/shape10_yellow_bg.xml new file mode 100644 index 0000000..7d57316 --- /dev/null +++ b/app/src/main/res/drawable/shape10_yellow_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape12_gray_border_white_bg.xml b/app/src/main/res/drawable/shape12_gray_border_white_bg.xml new file mode 100644 index 0000000..0e4c848 --- /dev/null +++ b/app/src/main/res/drawable/shape12_gray_border_white_bg.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape12_gray_white_bg.xml b/app/src/main/res/drawable/shape12_gray_white_bg.xml new file mode 100644 index 0000000..649995a --- /dev/null +++ b/app/src/main/res/drawable/shape12_gray_white_bg.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape16_gray_color_bg.xml b/app/src/main/res/drawable/shape16_gray_color_bg.xml new file mode 100644 index 0000000..05b918e --- /dev/null +++ b/app/src/main/res/drawable/shape16_gray_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape16_white_color_bg.xml b/app/src/main/res/drawable/shape16_white_color_bg.xml new file mode 100644 index 0000000..e23ec95 --- /dev/null +++ b/app/src/main/res/drawable/shape16_white_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape20_gray_bg.xml b/app/src/main/res/drawable/shape20_gray_bg.xml new file mode 100644 index 0000000..f42e09f --- /dev/null +++ b/app/src/main/res/drawable/shape20_gray_bg.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape20_white_bg.xml b/app/src/main/res/drawable/shape20_white_bg.xml new file mode 100644 index 0000000..e65213b --- /dev/null +++ b/app/src/main/res/drawable/shape20_white_bg.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape20_white_half_bottom_bg.xml b/app/src/main/res/drawable/shape20_white_half_bottom_bg.xml new file mode 100644 index 0000000..e8063fc --- /dev/null +++ b/app/src/main/res/drawable/shape20_white_half_bottom_bg.xml @@ -0,0 +1,10 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape20_yellow_border_cyan_bg.xml b/app/src/main/res/drawable/shape20_yellow_border_cyan_bg.xml new file mode 100644 index 0000000..4faa920 --- /dev/null +++ b/app/src/main/res/drawable/shape20_yellow_border_cyan_bg.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape22_green_yellow_bg.xml b/app/src/main/res/drawable/shape22_green_yellow_bg.xml new file mode 100644 index 0000000..8f3bc5e --- /dev/null +++ b/app/src/main/res/drawable/shape22_green_yellow_bg.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape24_white_bg.xml b/app/src/main/res/drawable/shape24_white_bg.xml new file mode 100644 index 0000000..8659e1b --- /dev/null +++ b/app/src/main/res/drawable/shape24_white_bg.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape28_yellow_bg.xml b/app/src/main/res/drawable/shape28_yellow_bg.xml new file mode 100644 index 0000000..20343dc --- /dev/null +++ b/app/src/main/res/drawable/shape28_yellow_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape30_gray_white_edit.xml b/app/src/main/res/drawable/shape30_gray_white_edit.xml new file mode 100644 index 0000000..33a30e4 --- /dev/null +++ b/app/src/main/res/drawable/shape30_gray_white_edit.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/shape30_white_edit.xml b/app/src/main/res/drawable/shape30_white_edit.xml new file mode 100644 index 0000000..346c9b9 --- /dev/null +++ b/app/src/main/res/drawable/shape30_white_edit.xml @@ -0,0 +1,7 @@ + + + + + + diff --git a/app/src/main/res/drawable/shape30_white_half_bottom_bg.xml b/app/src/main/res/drawable/shape30_white_half_bottom_bg.xml new file mode 100644 index 0000000..367dbde --- /dev/null +++ b/app/src/main/res/drawable/shape30_white_half_bottom_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape30_white_half_bottom_shadow_bg.xml b/app/src/main/res/drawable/shape30_white_half_bottom_shadow_bg.xml new file mode 100644 index 0000000..88c9241 --- /dev/null +++ b/app/src/main/res/drawable/shape30_white_half_bottom_shadow_bg.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape32_black_color_bg.xml b/app/src/main/res/drawable/shape32_black_color_bg.xml new file mode 100644 index 0000000..f5a719a --- /dev/null +++ b/app/src/main/res/drawable/shape32_black_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape32_white_color_bg.xml b/app/src/main/res/drawable/shape32_white_color_bg.xml new file mode 100644 index 0000000..a827c4e --- /dev/null +++ b/app/src/main/res/drawable/shape32_white_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape36_drak_green_color_bg.xml b/app/src/main/res/drawable/shape36_drak_green_color_bg.xml new file mode 100644 index 0000000..9c60d8c --- /dev/null +++ b/app/src/main/res/drawable/shape36_drak_green_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape36_gray_color_bg.xml b/app/src/main/res/drawable/shape36_gray_color_bg.xml new file mode 100644 index 0000000..c6eec75 --- /dev/null +++ b/app/src/main/res/drawable/shape36_gray_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape36_green_yellow_bg.xml b/app/src/main/res/drawable/shape36_green_yellow_bg.xml new file mode 100644 index 0000000..618e3fd --- /dev/null +++ b/app/src/main/res/drawable/shape36_green_yellow_bg.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape40_black70_toast_bg.xml b/app/src/main/res/drawable/shape40_black70_toast_bg.xml new file mode 100644 index 0000000..f96bf99 --- /dev/null +++ b/app/src/main/res/drawable/shape40_black70_toast_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape44_gray_border_white_bg.xml b/app/src/main/res/drawable/shape44_gray_border_white_bg.xml new file mode 100644 index 0000000..76c3735 --- /dev/null +++ b/app/src/main/res/drawable/shape44_gray_border_white_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape44_gray_yellow_bg.xml b/app/src/main/res/drawable/shape44_gray_yellow_bg.xml new file mode 100644 index 0000000..9ab0cef --- /dev/null +++ b/app/src/main/res/drawable/shape44_gray_yellow_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape4_gray_border_white_bg.xml b/app/src/main/res/drawable/shape4_gray_border_white_bg.xml new file mode 100644 index 0000000..4b8e2a1 --- /dev/null +++ b/app/src/main/res/drawable/shape4_gray_border_white_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape50_white_half_bottom_bg.xml b/app/src/main/res/drawable/shape50_white_half_bottom_bg.xml new file mode 100644 index 0000000..53cf1de --- /dev/null +++ b/app/src/main/res/drawable/shape50_white_half_bottom_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape50_yellow_color_bg.xml b/app/src/main/res/drawable/shape50_yellow_color_bg.xml new file mode 100644 index 0000000..b061d6e --- /dev/null +++ b/app/src/main/res/drawable/shape50_yellow_color_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape6_blue_half_bottom_bg.xml b/app/src/main/res/drawable/shape6_blue_half_bottom_bg.xml new file mode 100644 index 0000000..74b7b5d --- /dev/null +++ b/app/src/main/res/drawable/shape6_blue_half_bottom_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape6_gray_border_white_bg.xml b/app/src/main/res/drawable/shape6_gray_border_white_bg.xml new file mode 100644 index 0000000..1d56a49 --- /dev/null +++ b/app/src/main/res/drawable/shape6_gray_border_white_bg.xml @@ -0,0 +1,10 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape6_red_bg.xml b/app/src/main/res/drawable/shape6_red_bg.xml new file mode 100644 index 0000000..0af7936 --- /dev/null +++ b/app/src/main/res/drawable/shape6_red_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape6_yellow_half_bottom_bg.xml b/app/src/main/res/drawable/shape6_yellow_half_bottom_bg.xml new file mode 100644 index 0000000..210c836 --- /dev/null +++ b/app/src/main/res/drawable/shape6_yellow_half_bottom_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape8_black_bg.xml b/app/src/main/res/drawable/shape8_black_bg.xml new file mode 100644 index 0000000..c3672df --- /dev/null +++ b/app/src/main/res/drawable/shape8_black_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape8_dark_border_pale_yellow_bg.xml b/app/src/main/res/drawable/shape8_dark_border_pale_yellow_bg.xml new file mode 100644 index 0000000..ed5c1c9 --- /dev/null +++ b/app/src/main/res/drawable/shape8_dark_border_pale_yellow_bg.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape8_dark_color_bg.xml b/app/src/main/res/drawable/shape8_dark_color_bg.xml new file mode 100644 index 0000000..45e339a --- /dev/null +++ b/app/src/main/res/drawable/shape8_dark_color_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape8_dark_gray_border_white_yellow_bg.xml b/app/src/main/res/drawable/shape8_dark_gray_border_white_yellow_bg.xml new file mode 100644 index 0000000..f2e6570 --- /dev/null +++ b/app/src/main/res/drawable/shape8_dark_gray_border_white_yellow_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape8_gray_bg.xml b/app/src/main/res/drawable/shape8_gray_bg.xml new file mode 100644 index 0000000..dfcc015 --- /dev/null +++ b/app/src/main/res/drawable/shape8_gray_bg.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape8_gray_border_transparent_bg.xml b/app/src/main/res/drawable/shape8_gray_border_transparent_bg.xml new file mode 100644 index 0000000..d1c3727 --- /dev/null +++ b/app/src/main/res/drawable/shape8_gray_border_transparent_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape8_gray_border_white_bg.xml b/app/src/main/res/drawable/shape8_gray_border_white_bg.xml new file mode 100644 index 0000000..7bddfed --- /dev/null +++ b/app/src/main/res/drawable/shape8_gray_border_white_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape8_gray_white_bg.xml b/app/src/main/res/drawable/shape8_gray_white_bg.xml new file mode 100644 index 0000000..81f2fa2 --- /dev/null +++ b/app/src/main/res/drawable/shape8_gray_white_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape8_gray_yellow_bg.xml b/app/src/main/res/drawable/shape8_gray_yellow_bg.xml new file mode 100644 index 0000000..a15b699 --- /dev/null +++ b/app/src/main/res/drawable/shape8_gray_yellow_bg.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape8_red_border_white_bg.xml b/app/src/main/res/drawable/shape8_red_border_white_bg.xml new file mode 100644 index 0000000..0ac3423 --- /dev/null +++ b/app/src/main/res/drawable/shape8_red_border_white_bg.xml @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/shape8_white_bg.xml b/app/src/main/res/drawable/shape8_white_bg.xml new file mode 100644 index 0000000..d83693f --- /dev/null +++ b/app/src/main/res/drawable/shape8_white_bg.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape8_white_gray_bg.xml b/app/src/main/res/drawable/shape8_white_gray_bg.xml new file mode 100644 index 0000000..72547e9 --- /dev/null +++ b/app/src/main/res/drawable/shape8_white_gray_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape9_red_border_reddish_bg.xml b/app/src/main/res/drawable/shape9_red_border_reddish_bg.xml new file mode 100644 index 0000000..6655956 --- /dev/null +++ b/app/src/main/res/drawable/shape9_red_border_reddish_bg.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_back_ciecle.xml b/app/src/main/res/drawable/shape_back_ciecle.xml new file mode 100644 index 0000000..1fbd584 --- /dev/null +++ b/app/src/main/res/drawable/shape_back_ciecle.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_black50_circle_bg.xml b/app/src/main/res/drawable/shape_black50_circle_bg.xml new file mode 100644 index 0000000..7dac48a --- /dev/null +++ b/app/src/main/res/drawable/shape_black50_circle_bg.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_black_circle_bg.xml b/app/src/main/res/drawable/shape_black_circle_bg.xml new file mode 100644 index 0000000..1243471 --- /dev/null +++ b/app/src/main/res/drawable/shape_black_circle_bg.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_home_route_calendar_bg.xml b/app/src/main/res/drawable/shape_home_route_calendar_bg.xml new file mode 100644 index 0000000..37b336a --- /dev/null +++ b/app/src/main/res/drawable/shape_home_route_calendar_bg.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_light_grey_bg.xml b/app/src/main/res/drawable/shape_light_grey_bg.xml new file mode 100644 index 0000000..5af127b --- /dev/null +++ b/app/src/main/res/drawable/shape_light_grey_bg.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/drawable/shape_progress_bar_style.xml b/app/src/main/res/drawable/shape_progress_bar_style.xml new file mode 100644 index 0000000..305d9bf --- /dev/null +++ b/app/src/main/res/drawable/shape_progress_bar_style.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_red_circle_bg.xml b/app/src/main/res/drawable/shape_red_circle_bg.xml new file mode 100644 index 0000000..2ed3c1b --- /dev/null +++ b/app/src/main/res/drawable/shape_red_circle_bg.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_seek_bar_style.xml b/app/src/main/res/drawable/shape_seek_bar_style.xml new file mode 100644 index 0000000..05b2708 --- /dev/null +++ b/app/src/main/res/drawable/shape_seek_bar_style.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_transparent_black_bg.xml b/app/src/main/res/drawable/shape_transparent_black_bg.xml new file mode 100644 index 0000000..6335cc7 --- /dev/null +++ b/app/src/main/res/drawable/shape_transparent_black_bg.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/shape_verify_edittext_default_bg.xml b/app/src/main/res/drawable/shape_verify_edittext_default_bg.xml new file mode 100644 index 0000000..fc7a21b --- /dev/null +++ b/app/src/main/res/drawable/shape_verify_edittext_default_bg.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_white_alpha_circle_bg.xml b/app/src/main/res/drawable/shape_white_alpha_circle_bg.xml new file mode 100644 index 0000000..9270c03 --- /dev/null +++ b/app/src/main/res/drawable/shape_white_alpha_circle_bg.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_white_circle_bg.xml b/app/src/main/res/drawable/shape_white_circle_bg.xml new file mode 100644 index 0000000..dcc8e0e --- /dev/null +++ b/app/src/main/res/drawable/shape_white_circle_bg.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_white_cyan_gradient.xml b/app/src/main/res/drawable/shape_white_cyan_gradient.xml new file mode 100644 index 0000000..765c568 --- /dev/null +++ b/app/src/main/res/drawable/shape_white_cyan_gradient.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_white_gray_circle_bg.xml b/app/src/main/res/drawable/shape_white_gray_circle_bg.xml new file mode 100644 index 0000000..3c6a824 --- /dev/null +++ b/app/src/main/res/drawable/shape_white_gray_circle_bg.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_yellow_circle_bg.xml b/app/src/main/res/drawable/shape_yellow_circle_bg.xml new file mode 100644 index 0000000..430ea6a --- /dev/null +++ b/app/src/main/res/drawable/shape_yellow_circle_bg.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/share_default_bg_image.png b/app/src/main/res/drawable/share_default_bg_image.png new file mode 100644 index 0000000..b833ec7 Binary files /dev/null and b/app/src/main/res/drawable/share_default_bg_image.png differ diff --git a/app/src/main/res/drawable/sign_strip.png b/app/src/main/res/drawable/sign_strip.png new file mode 100644 index 0000000..129e870 Binary files /dev/null and b/app/src/main/res/drawable/sign_strip.png differ diff --git a/app/src/main/res/drawable/text_view_pressed.xml b/app/src/main/res/drawable/text_view_pressed.xml new file mode 100644 index 0000000..4604b4b --- /dev/null +++ b/app/src/main/res/drawable/text_view_pressed.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/v2_bg_color_gradient.xml b/app/src/main/res/drawable/v2_bg_color_gradient.xml new file mode 100644 index 0000000..7f85dad --- /dev/null +++ b/app/src/main/res/drawable/v2_bg_color_gradient.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..cebda87 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_credit_card.xml b/app/src/main/res/layout/activity_add_credit_card.xml new file mode 100644 index 0000000..9228941 --- /dev/null +++ b/app/src/main/res/layout/activity_add_credit_card.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_credit_card_payment.xml b/app/src/main/res/layout/activity_add_credit_card_payment.xml new file mode 100644 index 0000000..c2c8837 --- /dev/null +++ b/app/src/main/res/layout/activity_add_credit_card_payment.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_edit_fences_zone.xml b/app/src/main/res/layout/activity_add_edit_fences_zone.xml new file mode 100644 index 0000000..97d1cdf --- /dev/null +++ b/app/src/main/res/layout/activity_add_edit_fences_zone.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_email_valid.xml b/app/src/main/res/layout/activity_add_email_valid.xml new file mode 100644 index 0000000..ce4d736 --- /dev/null +++ b/app/src/main/res/layout/activity_add_email_valid.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_new_tracker1.xml b/app/src/main/res/layout/activity_add_new_tracker1.xml new file mode 100644 index 0000000..b592d44 --- /dev/null +++ b/app/src/main/res/layout/activity_add_new_tracker1.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_new_tracker2.xml b/app/src/main/res/layout/activity_add_new_tracker2.xml new file mode 100644 index 0000000..35e8342 --- /dev/null +++ b/app/src/main/res/layout/activity_add_new_tracker2.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_new_tracker3.xml b/app/src/main/res/layout/activity_add_new_tracker3.xml new file mode 100644 index 0000000..7863db5 --- /dev/null +++ b/app/src/main/res/layout/activity_add_new_tracker3.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_new_tracker4.xml b/app/src/main/res/layout/activity_add_new_tracker4.xml new file mode 100644 index 0000000..50e5004 --- /dev/null +++ b/app/src/main/res/layout/activity_add_new_tracker4.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_paired_success.xml b/app/src/main/res/layout/activity_add_paired_success.xml new file mode 100644 index 0000000..1dcb4eb --- /dev/null +++ b/app/src/main/res/layout/activity_add_paired_success.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_wifi_password.xml b/app/src/main/res/layout/activity_add_wifi_password.xml new file mode 100644 index 0000000..88e099d --- /dev/null +++ b/app/src/main/res/layout/activity_add_wifi_password.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_wifi_power_zone1.xml b/app/src/main/res/layout/activity_add_wifi_power_zone1.xml new file mode 100644 index 0000000..3b64760 --- /dev/null +++ b/app/src/main/res/layout/activity_add_wifi_power_zone1.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_wifi_power_zone2.xml b/app/src/main/res/layout/activity_add_wifi_power_zone2.xml new file mode 100644 index 0000000..cc0ebc3 --- /dev/null +++ b/app/src/main/res/layout/activity_add_wifi_power_zone2.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_add_wifi_power_zone3.xml b/app/src/main/res/layout/activity_add_wifi_power_zone3.xml new file mode 100644 index 0000000..eafa6e1 --- /dev/null +++ b/app/src/main/res/layout/activity_add_wifi_power_zone3.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_advanced_set.xml b/app/src/main/res/layout/activity_advanced_set.xml new file mode 100644 index 0000000..b989864 --- /dev/null +++ b/app/src/main/res/layout/activity_advanced_set.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_badge_detail.xml b/app/src/main/res/layout/activity_badge_detail.xml new file mode 100644 index 0000000..618e856 --- /dev/null +++ b/app/src/main/res/layout/activity_badge_detail.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_badges_list.xml b/app/src/main/res/layout/activity_badges_list.xml new file mode 100644 index 0000000..8101140 --- /dev/null +++ b/app/src/main/res/layout/activity_badges_list.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_change_email_success.xml b/app/src/main/res/layout/activity_change_email_success.xml new file mode 100644 index 0000000..31c1c9b --- /dev/null +++ b/app/src/main/res/layout/activity_change_email_success.xml @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_change_name.xml b/app/src/main/res/layout/activity_change_name.xml new file mode 100644 index 0000000..b8e2dae --- /dev/null +++ b/app/src/main/res/layout/activity_change_name.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_choose_plan.xml b/app/src/main/res/layout/activity_choose_plan.xml new file mode 100644 index 0000000..c4d7866 --- /dev/null +++ b/app/src/main/res/layout/activity_choose_plan.xml @@ -0,0 +1,28 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_create_account.xml b/app/src/main/res/layout/activity_create_account.xml new file mode 100644 index 0000000..4ff1b04 --- /dev/null +++ b/app/src/main/res/layout/activity_create_account.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_data_board.xml b/app/src/main/res/layout/activity_data_board.xml new file mode 100644 index 0000000..1dd2a86 --- /dev/null +++ b/app/src/main/res/layout/activity_data_board.xml @@ -0,0 +1,105 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_debug.xml b/app/src/main/res/layout/activity_debug.xml new file mode 100644 index 0000000..71f1240 --- /dev/null +++ b/app/src/main/res/layout/activity_debug.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_delete_tracker1.xml b/app/src/main/res/layout/activity_delete_tracker1.xml new file mode 100644 index 0000000..80ae410 --- /dev/null +++ b/app/src/main/res/layout/activity_delete_tracker1.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_delete_tracker2.xml b/app/src/main/res/layout/activity_delete_tracker2.xml new file mode 100644 index 0000000..198b1df --- /dev/null +++ b/app/src/main/res/layout/activity_delete_tracker2.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_detail.xml b/app/src/main/res/layout/activity_detail.xml new file mode 100644 index 0000000..7deb9f6 --- /dev/null +++ b/app/src/main/res/layout/activity_detail.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_edit_power_zone.xml b/app/src/main/res/layout/activity_edit_power_zone.xml new file mode 100644 index 0000000..9aa6601 --- /dev/null +++ b/app/src/main/res/layout/activity_edit_power_zone.xml @@ -0,0 +1,144 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_email_address.xml b/app/src/main/res/layout/activity_email_address.xml new file mode 100644 index 0000000..0bb6bbe --- /dev/null +++ b/app/src/main/res/layout/activity_email_address.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_family_members.xml b/app/src/main/res/layout/activity_family_members.xml new file mode 100644 index 0000000..d96a0f9 --- /dev/null +++ b/app/src/main/res/layout/activity_family_members.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_feedback.xml b/app/src/main/res/layout/activity_feedback.xml new file mode 100644 index 0000000..5d5653b --- /dev/null +++ b/app/src/main/res/layout/activity_feedback.xml @@ -0,0 +1,163 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_fences_add.xml b/app/src/main/res/layout/activity_fences_add.xml new file mode 100644 index 0000000..f1d2f37 --- /dev/null +++ b/app/src/main/res/layout/activity_fences_add.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_measurement_units.xml b/app/src/main/res/layout/activity_first_measurement_units.xml new file mode 100644 index 0000000..4601547 --- /dev/null +++ b/app/src/main/res/layout/activity_first_measurement_units.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_permissions.xml b/app/src/main/res/layout/activity_first_permissions.xml new file mode 100644 index 0000000..fdc95c4 --- /dev/null +++ b/app/src/main/res/layout/activity_first_permissions.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_pet_profile.xml b/app/src/main/res/layout/activity_first_pet_profile.xml new file mode 100644 index 0000000..6c876bb --- /dev/null +++ b/app/src/main/res/layout/activity_first_pet_profile.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_set_pet_height.xml b/app/src/main/res/layout/activity_first_set_pet_height.xml new file mode 100644 index 0000000..9ebe555 --- /dev/null +++ b/app/src/main/res/layout/activity_first_set_pet_height.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_set_pet_type.xml b/app/src/main/res/layout/activity_first_set_pet_type.xml new file mode 100644 index 0000000..96dadaa --- /dev/null +++ b/app/src/main/res/layout/activity_first_set_pet_type.xml @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_first_set_pet_weight.xml b/app/src/main/res/layout/activity_first_set_pet_weight.xml new file mode 100644 index 0000000..58d904b --- /dev/null +++ b/app/src/main/res/layout/activity_first_set_pet_weight.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_help_active_tracker.xml b/app/src/main/res/layout/activity_help_active_tracker.xml new file mode 100644 index 0000000..a51f075 --- /dev/null +++ b/app/src/main/res/layout/activity_help_active_tracker.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_help_create_pet_fence.xml b/app/src/main/res/layout/activity_help_create_pet_fence.xml new file mode 100644 index 0000000..26cd622 --- /dev/null +++ b/app/src/main/res/layout/activity_help_create_pet_fence.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_help_power_on_off.xml b/app/src/main/res/layout/activity_help_power_on_off.xml new file mode 100644 index 0000000..a36aa50 --- /dev/null +++ b/app/src/main/res/layout/activity_help_power_on_off.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_help_tracker_battery.xml b/app/src/main/res/layout/activity_help_tracker_battery.xml new file mode 100644 index 0000000..38421fb --- /dev/null +++ b/app/src/main/res/layout/activity_help_tracker_battery.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_help_tracker_status.xml b/app/src/main/res/layout/activity_help_tracker_status.xml new file mode 100644 index 0000000..7a80dae --- /dev/null +++ b/app/src/main/res/layout/activity_help_tracker_status.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_history_data.xml b/app/src/main/res/layout/activity_history_data.xml new file mode 100644 index 0000000..bb9f64b --- /dev/null +++ b/app/src/main/res/layout/activity_history_data.xml @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home_map_navigation.xml b/app/src/main/res/layout/activity_home_map_navigation.xml new file mode 100644 index 0000000..6602c87 --- /dev/null +++ b/app/src/main/res/layout/activity_home_map_navigation.xml @@ -0,0 +1,187 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_home_v2.xml b/app/src/main/res/layout/activity_home_v2.xml new file mode 100644 index 0000000..2d1a256 --- /dev/null +++ b/app/src/main/res/layout/activity_home_v2.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_how_work.xml b/app/src/main/res/layout/activity_how_work.xml new file mode 100644 index 0000000..8ea6a69 --- /dev/null +++ b/app/src/main/res/layout/activity_how_work.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_invite_family.xml b/app/src/main/res/layout/activity_invite_family.xml new file mode 100644 index 0000000..b905cd4 --- /dev/null +++ b/app/src/main/res/layout/activity_invite_family.xml @@ -0,0 +1,102 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_led_light.xml b/app/src/main/res/layout/activity_led_light.xml new file mode 100644 index 0000000..7c03a5f --- /dev/null +++ b/app/src/main/res/layout/activity_led_light.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_live_duration_set.xml b/app/src/main/res/layout/activity_live_duration_set.xml new file mode 100644 index 0000000..18d7098 --- /dev/null +++ b/app/src/main/res/layout/activity_live_duration_set.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_live_v2.xml b/app/src/main/res/layout/activity_live_v2.xml new file mode 100644 index 0000000..af9ef20 --- /dev/null +++ b/app/src/main/res/layout/activity_live_v2.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login_register_cn.xml b/app/src/main/res/layout/activity_login_register_cn.xml new file mode 100644 index 0000000..3567d59 --- /dev/null +++ b/app/src/main/res/layout/activity_login_register_cn.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login_v2.xml b/app/src/main/res/layout/activity_login_v2.xml new file mode 100644 index 0000000..539ccac --- /dev/null +++ b/app/src/main/res/layout/activity_login_v2.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_map_live.xml b/app/src/main/res/layout/activity_map_live.xml new file mode 100644 index 0000000..01eeb24 --- /dev/null +++ b/app/src/main/res/layout/activity_map_live.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_more_activity.xml b/app/src/main/res/layout/activity_more_activity.xml new file mode 100644 index 0000000..9c9b7b4 --- /dev/null +++ b/app/src/main/res/layout/activity_more_activity.xml @@ -0,0 +1,387 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_more_sleep.xml b/app/src/main/res/layout/activity_more_sleep.xml new file mode 100644 index 0000000..ed023a1 --- /dev/null +++ b/app/src/main/res/layout/activity_more_sleep.xml @@ -0,0 +1,215 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_my_crop_image.xml b/app/src/main/res/layout/activity_my_crop_image.xml new file mode 100644 index 0000000..62d4407 --- /dev/null +++ b/app/src/main/res/layout/activity_my_crop_image.xml @@ -0,0 +1,21 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_my_subscription.xml b/app/src/main/res/layout/activity_my_subscription.xml new file mode 100644 index 0000000..f9409da --- /dev/null +++ b/app/src/main/res/layout/activity_my_subscription.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_my_tracker_v2.xml b/app/src/main/res/layout/activity_my_tracker_v2.xml new file mode 100644 index 0000000..1b163f1 --- /dev/null +++ b/app/src/main/res/layout/activity_my_tracker_v2.xml @@ -0,0 +1,17 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notification_v2.xml b/app/src/main/res/layout/activity_notification_v2.xml new file mode 100644 index 0000000..f5b0a46 --- /dev/null +++ b/app/src/main/res/layout/activity_notification_v2.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_notifications_set.xml b/app/src/main/res/layout/activity_notifications_set.xml new file mode 100644 index 0000000..694edc1 --- /dev/null +++ b/app/src/main/res/layout/activity_notifications_set.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_payment_method.xml b/app/src/main/res/layout/activity_payment_method.xml new file mode 100644 index 0000000..318c900 --- /dev/null +++ b/app/src/main/res/layout/activity_payment_method.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_payment_result.xml b/app/src/main/res/layout/activity_payment_result.xml new file mode 100644 index 0000000..099ab8f --- /dev/null +++ b/app/src/main/res/layout/activity_payment_result.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_payment_success.xml b/app/src/main/res/layout/activity_payment_success.xml new file mode 100644 index 0000000..8f68507 --- /dev/null +++ b/app/src/main/res/layout/activity_payment_success.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_permissions_set.xml b/app/src/main/res/layout/activity_permissions_set.xml new file mode 100644 index 0000000..565b965 --- /dev/null +++ b/app/src/main/res/layout/activity_permissions_set.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pet_height.xml b/app/src/main/res/layout/activity_pet_height.xml new file mode 100644 index 0000000..0b302a8 --- /dev/null +++ b/app/src/main/res/layout/activity_pet_height.xml @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pet_profile.xml b/app/src/main/res/layout/activity_pet_profile.xml new file mode 100644 index 0000000..21395fd --- /dev/null +++ b/app/src/main/res/layout/activity_pet_profile.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_pet_weight.xml b/app/src/main/res/layout/activity_pet_weight.xml new file mode 100644 index 0000000..3705e52 --- /dev/null +++ b/app/src/main/res/layout/activity_pet_weight.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_power_off.xml b/app/src/main/res/layout/activity_power_off.xml new file mode 100644 index 0000000..a6ce37f --- /dev/null +++ b/app/src/main/res/layout/activity_power_off.xml @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_power_saving_set.xml b/app/src/main/res/layout/activity_power_saving_set.xml new file mode 100644 index 0000000..e326974 --- /dev/null +++ b/app/src/main/res/layout/activity_power_saving_set.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_preview_fences_zone.xml b/app/src/main/res/layout/activity_preview_fences_zone.xml new file mode 100644 index 0000000..36f72d2 --- /dev/null +++ b/app/src/main/res/layout/activity_preview_fences_zone.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_recharge_amount_cn.xml b/app/src/main/res/layout/activity_recharge_amount_cn.xml new file mode 100644 index 0000000..09e405d --- /dev/null +++ b/app/src/main/res/layout/activity_recharge_amount_cn.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_recharge_device_detail_cn.xml b/app/src/main/res/layout/activity_recharge_device_detail_cn.xml new file mode 100644 index 0000000..abf5ad0 --- /dev/null +++ b/app/src/main/res/layout/activity_recharge_device_detail_cn.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_recharge_history_cn.xml b/app/src/main/res/layout/activity_recharge_history_cn.xml new file mode 100644 index 0000000..4413194 --- /dev/null +++ b/app/src/main/res/layout/activity_recharge_history_cn.xml @@ -0,0 +1,18 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_recharge_order_detail_cn.xml b/app/src/main/res/layout/activity_recharge_order_detail_cn.xml new file mode 100644 index 0000000..62cde5a --- /dev/null +++ b/app/src/main/res/layout/activity_recharge_order_detail_cn.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_request_refund.xml b/app/src/main/res/layout/activity_request_refund.xml new file mode 100644 index 0000000..013a2e9 --- /dev/null +++ b/app/src/main/res/layout/activity_request_refund.xml @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_reset_password_success.xml b/app/src/main/res/layout/activity_reset_password_success.xml new file mode 100644 index 0000000..bf48c16 --- /dev/null +++ b/app/src/main/res/layout/activity_reset_password_success.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_say_hello.xml b/app/src/main/res/layout/activity_say_hello.xml new file mode 100644 index 0000000..9bc2244 --- /dev/null +++ b/app/src/main/res/layout/activity_say_hello.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_select_payment_method.xml b/app/src/main/res/layout/activity_select_payment_method.xml new file mode 100644 index 0000000..c590c40 --- /dev/null +++ b/app/src/main/res/layout/activity_select_payment_method.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_share_pet_activity.xml b/app/src/main/res/layout/activity_share_pet_activity.xml new file mode 100644 index 0000000..c83a15a --- /dev/null +++ b/app/src/main/res/layout/activity_share_pet_activity.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_show_invite_device.xml b/app/src/main/res/layout/activity_show_invite_device.xml new file mode 100644 index 0000000..e7f26f5 --- /dev/null +++ b/app/src/main/res/layout/activity_show_invite_device.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_signup_success.xml b/app/src/main/res/layout/activity_signup_success.xml new file mode 100644 index 0000000..16d9f35 --- /dev/null +++ b/app/src/main/res/layout/activity_signup_success.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 0000000..cebb404 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_subscription_detail.xml b/app/src/main/res/layout/activity_subscription_detail.xml new file mode 100644 index 0000000..489d69f --- /dev/null +++ b/app/src/main/res/layout/activity_subscription_detail.xml @@ -0,0 +1,127 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_subscription_history.xml b/app/src/main/res/layout/activity_subscription_history.xml new file mode 100644 index 0000000..c27842b --- /dev/null +++ b/app/src/main/res/layout/activity_subscription_history.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_subscription_plan.xml b/app/src/main/res/layout/activity_subscription_plan.xml new file mode 100644 index 0000000..6a298e2 --- /dev/null +++ b/app/src/main/res/layout/activity_subscription_plan.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_sure_subscription_plan.xml b/app/src/main/res/layout/activity_sure_subscription_plan.xml new file mode 100644 index 0000000..e525b5f --- /dev/null +++ b/app/src/main/res/layout/activity_sure_subscription_plan.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_swipe_action.xml b/app/src/main/res/layout/activity_swipe_action.xml new file mode 100644 index 0000000..ac8acdb --- /dev/null +++ b/app/src/main/res/layout/activity_swipe_action.xml @@ -0,0 +1,18 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_tracking_duration_set.xml b/app/src/main/res/layout/activity_tracking_duration_set.xml new file mode 100644 index 0000000..632e225 --- /dev/null +++ b/app/src/main/res/layout/activity_tracking_duration_set.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_user_profile.xml b/app/src/main/res/layout/activity_user_profile.xml new file mode 100644 index 0000000..e923ab5 --- /dev/null +++ b/app/src/main/res/layout/activity_user_profile.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_virtual_fences.xml b/app/src/main/res/layout/activity_virtual_fences.xml new file mode 100644 index 0000000..9526cca --- /dev/null +++ b/app/src/main/res/layout/activity_virtual_fences.xml @@ -0,0 +1,104 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_web_view.xml b/app/src/main/res/layout/activity_web_view.xml new file mode 100644 index 0000000..299003b --- /dev/null +++ b/app/src/main/res/layout/activity_web_view.xml @@ -0,0 +1,21 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_calender_and_time_layout.xml b/app/src/main/res/layout/dialog_calender_and_time_layout.xml new file mode 100644 index 0000000..91a254a --- /dev/null +++ b/app/src/main/res/layout/dialog_calender_and_time_layout.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_calender_layout.xml b/app/src/main/res/layout/dialog_calender_layout.xml new file mode 100644 index 0000000..94cb7ad --- /dev/null +++ b/app/src/main/res/layout/dialog_calender_layout.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_common_layout.xml b/app/src/main/res/layout/dialog_common_layout.xml new file mode 100644 index 0000000..d4d1661 --- /dev/null +++ b/app/src/main/res/layout/dialog_common_layout.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_dfu_new.xml b/app/src/main/res/layout/dialog_dfu_new.xml new file mode 100644 index 0000000..be736d9 --- /dev/null +++ b/app/src/main/res/layout/dialog_dfu_new.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_legal_info.xml b/app/src/main/res/layout/dialog_legal_info.xml new file mode 100644 index 0000000..e24510a --- /dev/null +++ b/app/src/main/res/layout/dialog_legal_info.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_list_item_layout.xml b/app/src/main/res/layout/dialog_list_item_layout.xml new file mode 100644 index 0000000..6751402 --- /dev/null +++ b/app/src/main/res/layout/dialog_list_item_layout.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/dialog_my_layout.xml b/app/src/main/res/layout/dialog_my_layout.xml new file mode 100644 index 0000000..91e86db --- /dev/null +++ b/app/src/main/res/layout/dialog_my_layout.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_package_upgrade.xml b/app/src/main/res/layout/dialog_package_upgrade.xml new file mode 100644 index 0000000..6a995c8 --- /dev/null +++ b/app/src/main/res/layout/dialog_package_upgrade.xml @@ -0,0 +1,45 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_select_map_layout.xml b/app/src/main/res/layout/dialog_select_map_layout.xml new file mode 100644 index 0000000..a378ed9 --- /dev/null +++ b/app/src/main/res/layout/dialog_select_map_layout.xml @@ -0,0 +1,37 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_select_map_type_layout.xml b/app/src/main/res/layout/dialog_select_map_type_layout.xml new file mode 100644 index 0000000..3f1f76b --- /dev/null +++ b/app/src/main/res/layout/dialog_select_map_type_layout.xml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_select_pet_label_layout.xml b/app/src/main/res/layout/dialog_select_pet_label_layout.xml new file mode 100644 index 0000000..e8c2b25 --- /dev/null +++ b/app/src/main/res/layout/dialog_select_pet_label_layout.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_set_pet_weight_layout.xml b/app/src/main/res/layout/dialog_set_pet_weight_layout.xml new file mode 100644 index 0000000..da95d56 --- /dev/null +++ b/app/src/main/res/layout/dialog_set_pet_weight_layout.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_tracker_dfu_list_layout.xml b/app/src/main/res/layout/dialog_tracker_dfu_list_layout.xml new file mode 100644 index 0000000..547df61 --- /dev/null +++ b/app/src/main/res/layout/dialog_tracker_dfu_list_layout.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_year_and_month_layout.xml b/app/src/main/res/layout/dialog_year_and_month_layout.xml new file mode 100644 index 0000000..89961c0 --- /dev/null +++ b/app/src/main/res/layout/dialog_year_and_month_layout.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_account_v2.xml b/app/src/main/res/layout/fragment_account_v2.xml new file mode 100644 index 0000000..a4a3bfd --- /dev/null +++ b/app/src/main/res/layout/fragment_account_v2.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_activity_v2.xml b/app/src/main/res/layout/fragment_activity_v2.xml new file mode 100644 index 0000000..9e454aa --- /dev/null +++ b/app/src/main/res/layout/fragment_activity_v2.xml @@ -0,0 +1,212 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_base_baidu_map.xml b/app/src/main/res/layout/fragment_base_baidu_map.xml new file mode 100644 index 0000000..6f566cb --- /dev/null +++ b/app/src/main/res/layout/fragment_base_baidu_map.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_base_email_valid.xml b/app/src/main/res/layout/fragment_base_email_valid.xml new file mode 100644 index 0000000..7f0e0b6 --- /dev/null +++ b/app/src/main/res/layout/fragment_base_email_valid.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_bind_phone_cn.xml b/app/src/main/res/layout/fragment_bind_phone_cn.xml new file mode 100644 index 0000000..c9637f5 --- /dev/null +++ b/app/src/main/res/layout/fragment_bind_phone_cn.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_create_account_v2_one.xml b/app/src/main/res/layout/fragment_create_account_v2_one.xml new file mode 100644 index 0000000..8223fa2 --- /dev/null +++ b/app/src/main/res/layout/fragment_create_account_v2_one.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_create_account_v2_three.xml b/app/src/main/res/layout/fragment_create_account_v2_three.xml new file mode 100644 index 0000000..1e5851e --- /dev/null +++ b/app/src/main/res/layout/fragment_create_account_v2_three.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_credit_card_payment.xml b/app/src/main/res/layout/fragment_credit_card_payment.xml new file mode 100644 index 0000000..3c94c52 --- /dev/null +++ b/app/src/main/res/layout/fragment_credit_card_payment.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forget_password_cn_finish.xml b/app/src/main/res/layout/fragment_forget_password_cn_finish.xml new file mode 100644 index 0000000..89a202e --- /dev/null +++ b/app/src/main/res/layout/fragment_forget_password_cn_finish.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forget_password_cn_start.xml b/app/src/main/res/layout/fragment_forget_password_cn_start.xml new file mode 100644 index 0000000..a8d7926 --- /dev/null +++ b/app/src/main/res/layout/fragment_forget_password_cn_start.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forgot_password_v2_one.xml b/app/src/main/res/layout/fragment_forgot_password_v2_one.xml new file mode 100644 index 0000000..4be491d --- /dev/null +++ b/app/src/main/res/layout/fragment_forgot_password_v2_one.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_forgot_password_v2_there.xml b/app/src/main/res/layout/fragment_forgot_password_v2_there.xml new file mode 100644 index 0000000..037964a --- /dev/null +++ b/app/src/main/res/layout/fragment_forgot_password_v2_there.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_google_map.xml b/app/src/main/res/layout/fragment_google_map.xml new file mode 100644 index 0000000..c7cf79c --- /dev/null +++ b/app/src/main/res/layout/fragment_google_map.xml @@ -0,0 +1,9 @@ + + + + + diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml new file mode 100644 index 0000000..4c37b68 --- /dev/null +++ b/app/src/main/res/layout/fragment_home.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_pet.xml b/app/src/main/res/layout/fragment_home_pet.xml new file mode 100644 index 0000000..deed188 --- /dev/null +++ b/app/src/main/res/layout/fragment_home_pet.xml @@ -0,0 +1,301 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_home_track.xml b/app/src/main/res/layout/fragment_home_track.xml new file mode 100644 index 0000000..ac3b0c6 --- /dev/null +++ b/app/src/main/res/layout/fragment_home_track.xml @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login_cn_account.xml b/app/src/main/res/layout/fragment_login_cn_account.xml new file mode 100644 index 0000000..e1eab11 --- /dev/null +++ b/app/src/main/res/layout/fragment_login_cn_account.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login_cn_code.xml b/app/src/main/res/layout/fragment_login_cn_code.xml new file mode 100644 index 0000000..3b6f386 --- /dev/null +++ b/app/src/main/res/layout/fragment_login_cn_code.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_login_cn_first.xml b/app/src/main/res/layout/fragment_login_cn_first.xml new file mode 100644 index 0000000..e6e26d7 --- /dev/null +++ b/app/src/main/res/layout/fragment_login_cn_first.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_map_v2.xml b/app/src/main/res/layout/fragment_map_v2.xml new file mode 100644 index 0000000..0029956 --- /dev/null +++ b/app/src/main/res/layout/fragment_map_v2.xml @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_mine.xml b/app/src/main/res/layout/fragment_mine.xml new file mode 100644 index 0000000..46735b2 --- /dev/null +++ b/app/src/main/res/layout/fragment_mine.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_payment_fail.xml b/app/src/main/res/layout/fragment_payment_fail.xml new file mode 100644 index 0000000..1802262 --- /dev/null +++ b/app/src/main/res/layout/fragment_payment_fail.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_payment_success.xml b/app/src/main/res/layout/fragment_payment_success.xml new file mode 100644 index 0000000..88ec8ec --- /dev/null +++ b/app/src/main/res/layout/fragment_payment_success.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_paypal_payment.xml b/app/src/main/res/layout/fragment_paypal_payment.xml new file mode 100644 index 0000000..9014bca --- /dev/null +++ b/app/src/main/res/layout/fragment_paypal_payment.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_pet_v2.xml b/app/src/main/res/layout/fragment_pet_v2.xml new file mode 100644 index 0000000..3c87a2f --- /dev/null +++ b/app/src/main/res/layout/fragment_pet_v2.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_record.xml b/app/src/main/res/layout/fragment_record.xml new file mode 100644 index 0000000..08b4693 --- /dev/null +++ b/app/src/main/res/layout/fragment_record.xml @@ -0,0 +1,20 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_register_cn_finish.xml b/app/src/main/res/layout/fragment_register_cn_finish.xml new file mode 100644 index 0000000..3579b6e --- /dev/null +++ b/app/src/main/res/layout/fragment_register_cn_finish.xml @@ -0,0 +1,32 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_register_cn_start.xml b/app/src/main/res/layout/fragment_register_cn_start.xml new file mode 100644 index 0000000..3debaa1 --- /dev/null +++ b/app/src/main/res/layout/fragment_register_cn_start.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_route_v2.xml b/app/src/main/res/layout/fragment_route_v2.xml new file mode 100644 index 0000000..bade677 --- /dev/null +++ b/app/src/main/res/layout/fragment_route_v2.xml @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/horizontal_calender_view.xml b/app/src/main/res/layout/horizontal_calender_view.xml new file mode 100644 index 0000000..c8df3f0 --- /dev/null +++ b/app/src/main/res/layout/horizontal_calender_view.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_associated_pet.xml b/app/src/main/res/layout/item_associated_pet.xml new file mode 100644 index 0000000..c9acc4a --- /dev/null +++ b/app/src/main/res/layout/item_associated_pet.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_baidu_map_poi_result.xml b/app/src/main/res/layout/item_baidu_map_poi_result.xml new file mode 100644 index 0000000..bf4b023 --- /dev/null +++ b/app/src/main/res/layout/item_baidu_map_poi_result.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_bottom_sheet_add_layout.xml b/app/src/main/res/layout/item_bottom_sheet_add_layout.xml new file mode 100644 index 0000000..0220a31 --- /dev/null +++ b/app/src/main/res/layout/item_bottom_sheet_add_layout.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_choose_plan_layout.xml b/app/src/main/res/layout/item_choose_plan_layout.xml new file mode 100644 index 0000000..3dd9185 --- /dev/null +++ b/app/src/main/res/layout/item_choose_plan_layout.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_credit_card_layout.xml b/app/src/main/res/layout/item_credit_card_layout.xml new file mode 100644 index 0000000..0263cec --- /dev/null +++ b/app/src/main/res/layout/item_credit_card_layout.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_duration_set_layout.xml b/app/src/main/res/layout/item_duration_set_layout.xml new file mode 100644 index 0000000..a9550ba --- /dev/null +++ b/app/src/main/res/layout/item_duration_set_layout.xml @@ -0,0 +1,26 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_family_member_layout.xml b/app/src/main/res/layout/item_family_member_layout.xml new file mode 100644 index 0000000..3fa73ad --- /dev/null +++ b/app/src/main/res/layout/item_family_member_layout.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feedback_image.xml b/app/src/main/res/layout/item_feedback_image.xml new file mode 100644 index 0000000..2c8b821 --- /dev/null +++ b/app/src/main/res/layout/item_feedback_image.xml @@ -0,0 +1,33 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_home_map_device_msg.xml b/app/src/main/res/layout/item_home_map_device_msg.xml new file mode 100644 index 0000000..a7a00fd --- /dev/null +++ b/app/src/main/res/layout/item_home_map_device_msg.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/layout/item_home_map_device_state.xml b/app/src/main/res/layout/item_home_map_device_state.xml new file mode 100644 index 0000000..6639ecb --- /dev/null +++ b/app/src/main/res/layout/item_home_map_device_state.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_home_pet_device_state.xml b/app/src/main/res/layout/item_home_pet_device_state.xml new file mode 100644 index 0000000..5e6f751 --- /dev/null +++ b/app/src/main/res/layout/item_home_pet_device_state.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_home_pet_family.xml b/app/src/main/res/layout/item_home_pet_family.xml new file mode 100644 index 0000000..1aac7f3 --- /dev/null +++ b/app/src/main/res/layout/item_home_pet_family.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_home_route_notification.xml b/app/src/main/res/layout/item_home_route_notification.xml new file mode 100644 index 0000000..c64260c --- /dev/null +++ b/app/src/main/res/layout/item_home_route_notification.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_horizontal_calender_day.xml b/app/src/main/res/layout/item_horizontal_calender_day.xml new file mode 100644 index 0000000..4b493f1 --- /dev/null +++ b/app/src/main/res/layout/item_horizontal_calender_day.xml @@ -0,0 +1,41 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_invite_family_avatar_layout.xml b/app/src/main/res/layout/item_invite_family_avatar_layout.xml new file mode 100644 index 0000000..fc0f883 --- /dev/null +++ b/app/src/main/res/layout/item_invite_family_avatar_layout.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/layout/item_invite_family_relation_layout.xml b/app/src/main/res/layout/item_invite_family_relation_layout.xml new file mode 100644 index 0000000..2b08d64 --- /dev/null +++ b/app/src/main/res/layout/item_invite_family_relation_layout.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/layout/item_led_color_select.xml b/app/src/main/res/layout/item_led_color_select.xml new file mode 100644 index 0000000..cbaaa5b --- /dev/null +++ b/app/src/main/res/layout/item_led_color_select.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/item_map_type_layout.xml b/app/src/main/res/layout/item_map_type_layout.xml new file mode 100644 index 0000000..b856316 --- /dev/null +++ b/app/src/main/res/layout/item_map_type_layout.xml @@ -0,0 +1,23 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_my_pet_dialog.xml b/app/src/main/res/layout/item_my_pet_dialog.xml new file mode 100644 index 0000000..f2e8ce0 --- /dev/null +++ b/app/src/main/res/layout/item_my_pet_dialog.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_my_subscription_layout.xml b/app/src/main/res/layout/item_my_subscription_layout.xml new file mode 100644 index 0000000..90cc764 --- /dev/null +++ b/app/src/main/res/layout/item_my_subscription_layout.xml @@ -0,0 +1,179 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_my_tracker_v2_layout.xml b/app/src/main/res/layout/item_my_tracker_v2_layout.xml new file mode 100644 index 0000000..2fb6022 --- /dev/null +++ b/app/src/main/res/layout/item_my_tracker_v2_layout.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_notifications_set_layout.xml b/app/src/main/res/layout/item_notifications_set_layout.xml new file mode 100644 index 0000000..af927fa --- /dev/null +++ b/app/src/main/res/layout/item_notifications_set_layout.xml @@ -0,0 +1,35 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_payment_method_layout.xml b/app/src/main/res/layout/item_payment_method_layout.xml new file mode 100644 index 0000000..f45761b --- /dev/null +++ b/app/src/main/res/layout/item_payment_method_layout.xml @@ -0,0 +1,29 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_permission_state_layout.xml b/app/src/main/res/layout/item_permission_state_layout.xml new file mode 100644 index 0000000..6a450f7 --- /dev/null +++ b/app/src/main/res/layout/item_permission_state_layout.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_pet_label_layout_v2.xml b/app/src/main/res/layout/item_pet_label_layout_v2.xml new file mode 100644 index 0000000..f52378c --- /dev/null +++ b/app/src/main/res/layout/item_pet_label_layout_v2.xml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/app/src/main/res/layout/item_pets_badges_layout.xml b/app/src/main/res/layout/item_pets_badges_layout.xml new file mode 100644 index 0000000..0311541 --- /dev/null +++ b/app/src/main/res/layout/item_pets_badges_layout.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_pets_parent_badges_layout.xml b/app/src/main/res/layout/item_pets_parent_badges_layout.xml new file mode 100644 index 0000000..1d8ad74 --- /dev/null +++ b/app/src/main/res/layout/item_pets_parent_badges_layout.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_qmui_common_item_view.xml b/app/src/main/res/layout/item_qmui_common_item_view.xml new file mode 100644 index 0000000..ad7775a --- /dev/null +++ b/app/src/main/res/layout/item_qmui_common_item_view.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recharge_amount.xml b/app/src/main/res/layout/item_recharge_amount.xml new file mode 100644 index 0000000..ddacad5 --- /dev/null +++ b/app/src/main/res/layout/item_recharge_amount.xml @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recharge_amount_cn.xml b/app/src/main/res/layout/item_recharge_amount_cn.xml new file mode 100644 index 0000000..b7d21e7 --- /dev/null +++ b/app/src/main/res/layout/item_recharge_amount_cn.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recharge_device.xml b/app/src/main/res/layout/item_recharge_device.xml new file mode 100644 index 0000000..9341e5c --- /dev/null +++ b/app/src/main/res/layout/item_recharge_device.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_recharge_history_cn.xml b/app/src/main/res/layout/item_recharge_history_cn.xml new file mode 100644 index 0000000..73b1c07 --- /dev/null +++ b/app/src/main/res/layout/item_recharge_history_cn.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_select_map.xml b/app/src/main/res/layout/item_select_map.xml new file mode 100644 index 0000000..44c7960 --- /dev/null +++ b/app/src/main/res/layout/item_select_map.xml @@ -0,0 +1,23 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_select_type_card_shade_layout.xml b/app/src/main/res/layout/item_select_type_card_shade_layout.xml new file mode 100644 index 0000000..1365b15 --- /dev/null +++ b/app/src/main/res/layout/item_select_type_card_shade_layout.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_shadow_image_view.xml b/app/src/main/res/layout/item_shadow_image_view.xml new file mode 100644 index 0000000..03481ac --- /dev/null +++ b/app/src/main/res/layout/item_shadow_image_view.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/app/src/main/res/layout/item_subscribe_summary_layout.xml b/app/src/main/res/layout/item_subscribe_summary_layout.xml new file mode 100644 index 0000000..22ab74f --- /dev/null +++ b/app/src/main/res/layout/item_subscribe_summary_layout.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_subscription_history_layout.xml b/app/src/main/res/layout/item_subscription_history_layout.xml new file mode 100644 index 0000000..1bd390b --- /dev/null +++ b/app/src/main/res/layout/item_subscription_history_layout.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_subscription_plan_detail_layout.xml b/app/src/main/res/layout/item_subscription_plan_detail_layout.xml new file mode 100644 index 0000000..f4d9f4c --- /dev/null +++ b/app/src/main/res/layout/item_subscription_plan_detail_layout.xml @@ -0,0 +1,28 @@ + + + + + + + + diff --git a/app/src/main/res/layout/item_subscription_plan_layout.xml b/app/src/main/res/layout/item_subscription_plan_layout.xml new file mode 100644 index 0000000..29d8de5 --- /dev/null +++ b/app/src/main/res/layout/item_subscription_plan_layout.xml @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_text_img_type.xml b/app/src/main/res/layout/item_text_img_type.xml new file mode 100644 index 0000000..4b0f339 --- /dev/null +++ b/app/src/main/res/layout/item_text_img_type.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_text_type.xml b/app/src/main/res/layout/item_text_type.xml new file mode 100644 index 0000000..ad1d6f8 --- /dev/null +++ b/app/src/main/res/layout/item_text_type.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_text_view.xml b/app/src/main/res/layout/item_text_view.xml new file mode 100644 index 0000000..9abade7 --- /dev/null +++ b/app/src/main/res/layout/item_text_view.xml @@ -0,0 +1,12 @@ + + diff --git a/app/src/main/res/layout/item_title_text_type.xml b/app/src/main/res/layout/item_title_text_type.xml new file mode 100644 index 0000000..c7d7148 --- /dev/null +++ b/app/src/main/res/layout/item_title_text_type.xml @@ -0,0 +1,6 @@ + + diff --git a/app/src/main/res/layout/item_tracker_dfu_state.xml b/app/src/main/res/layout/item_tracker_dfu_state.xml new file mode 100644 index 0000000..feec604 --- /dev/null +++ b/app/src/main/res/layout/item_tracker_dfu_state.xml @@ -0,0 +1,34 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_virtual_fences_layout.xml b/app/src/main/res/layout/item_virtual_fences_layout.xml new file mode 100644 index 0000000..d5a7e2d --- /dev/null +++ b/app/src/main/res/layout/item_virtual_fences_layout.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_wifi_info_layout.xml b/app/src/main/res/layout/item_wifi_info_layout.xml new file mode 100644 index 0000000..b0b8b11 --- /dev/null +++ b/app/src/main/res/layout/item_wifi_info_layout.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_wifi_zone_layout.xml b/app/src/main/res/layout/item_wifi_zone_layout.xml new file mode 100644 index 0000000..6ca589b --- /dev/null +++ b/app/src/main/res/layout/item_wifi_zone_layout.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_assign_options_list.xml b/app/src/main/res/layout/layout_assign_options_list.xml new file mode 100644 index 0000000..40b31e7 --- /dev/null +++ b/app/src/main/res/layout/layout_assign_options_list.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_associated_custom_qmui_group_right.xml b/app/src/main/res/layout/layout_associated_custom_qmui_group_right.xml new file mode 100644 index 0000000..3ee6caf --- /dev/null +++ b/app/src/main/res/layout/layout_associated_custom_qmui_group_right.xml @@ -0,0 +1,34 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_behind_of_credit_card.xml b/app/src/main/res/layout/layout_behind_of_credit_card.xml new file mode 100644 index 0000000..a378231 --- /dev/null +++ b/app/src/main/res/layout/layout_behind_of_credit_card.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_ble_connect_state.xml b/app/src/main/res/layout/layout_ble_connect_state.xml new file mode 100644 index 0000000..282c92b --- /dev/null +++ b/app/src/main/res/layout/layout_ble_connect_state.xml @@ -0,0 +1,15 @@ + + diff --git a/app/src/main/res/layout/layout_bluetooth_find_device.xml b/app/src/main/res/layout/layout_bluetooth_find_device.xml new file mode 100644 index 0000000..c1f1b6e --- /dev/null +++ b/app/src/main/res/layout/layout_bluetooth_find_device.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_create_account_policy_terms.xml b/app/src/main/res/layout/layout_create_account_policy_terms.xml new file mode 100644 index 0000000..90962cf --- /dev/null +++ b/app/src/main/res/layout/layout_create_account_policy_terms.xml @@ -0,0 +1,29 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_credit_card_no_data.xml b/app/src/main/res/layout/layout_credit_card_no_data.xml new file mode 100644 index 0000000..3343d5b --- /dev/null +++ b/app/src/main/res/layout/layout_credit_card_no_data.xml @@ -0,0 +1,29 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_data_board_item.xml b/app/src/main/res/layout/layout_data_board_item.xml new file mode 100644 index 0000000..421c980 --- /dev/null +++ b/app/src/main/res/layout/layout_data_board_item.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_device_light_image_button.xml b/app/src/main/res/layout/layout_device_light_image_button.xml new file mode 100644 index 0000000..c043658 --- /dev/null +++ b/app/src/main/res/layout/layout_device_light_image_button.xml @@ -0,0 +1,24 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_device_recharge_describe.xml b/app/src/main/res/layout/layout_device_recharge_describe.xml new file mode 100644 index 0000000..953a12f --- /dev/null +++ b/app/src/main/res/layout/layout_device_recharge_describe.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_device_recharge_describe_cn.xml b/app/src/main/res/layout/layout_device_recharge_describe_cn.xml new file mode 100644 index 0000000..feda353 --- /dev/null +++ b/app/src/main/res/layout/layout_device_recharge_describe_cn.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_dfu_custom_qmui_group_right.xml b/app/src/main/res/layout/layout_dfu_custom_qmui_group_right.xml new file mode 100644 index 0000000..1ab0ae2 --- /dev/null +++ b/app/src/main/res/layout/layout_dfu_custom_qmui_group_right.xml @@ -0,0 +1,31 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_empty_view.xml b/app/src/main/res/layout/layout_empty_view.xml new file mode 100644 index 0000000..bf0f523 --- /dev/null +++ b/app/src/main/res/layout/layout_empty_view.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_family_empty_view.xml b/app/src/main/res/layout/layout_family_empty_view.xml new file mode 100644 index 0000000..f8880d0 --- /dev/null +++ b/app/src/main/res/layout/layout_family_empty_view.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_front_of_credit_card.xml b/app/src/main/res/layout/layout_front_of_credit_card.xml new file mode 100644 index 0000000..69219f2 --- /dev/null +++ b/app/src/main/res/layout/layout_front_of_credit_card.xml @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_get_phone_code.xml b/app/src/main/res/layout/layout_get_phone_code.xml new file mode 100644 index 0000000..8cc8424 --- /dev/null +++ b/app/src/main/res/layout/layout_get_phone_code.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_head_image_view.xml b/app/src/main/res/layout/layout_head_image_view.xml new file mode 100644 index 0000000..4c5461e --- /dev/null +++ b/app/src/main/res/layout/layout_head_image_view.xml @@ -0,0 +1,14 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_help_image.xml b/app/src/main/res/layout/layout_help_image.xml new file mode 100644 index 0000000..c60ca16 --- /dev/null +++ b/app/src/main/res/layout/layout_help_image.xml @@ -0,0 +1,7 @@ + + + diff --git a/app/src/main/res/layout/layout_help_title1.xml b/app/src/main/res/layout/layout_help_title1.xml new file mode 100644 index 0000000..ee20d85 --- /dev/null +++ b/app/src/main/res/layout/layout_help_title1.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/layout_help_title2.xml b/app/src/main/res/layout/layout_help_title2.xml new file mode 100644 index 0000000..543dbe8 --- /dev/null +++ b/app/src/main/res/layout/layout_help_title2.xml @@ -0,0 +1,9 @@ + + diff --git a/app/src/main/res/layout/layout_home_detail_top.xml b/app/src/main/res/layout/layout_home_detail_top.xml new file mode 100644 index 0000000..caa224e --- /dev/null +++ b/app/src/main/res/layout/layout_home_detail_top.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_input_edit_view.xml b/app/src/main/res/layout/layout_input_edit_view.xml new file mode 100644 index 0000000..8de7d7a --- /dev/null +++ b/app/src/main/res/layout/layout_input_edit_view.xml @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_input_edit_view2.xml b/app/src/main/res/layout/layout_input_edit_view2.xml new file mode 100644 index 0000000..6693bb1 --- /dev/null +++ b/app/src/main/res/layout/layout_input_edit_view2.xml @@ -0,0 +1,32 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_list_item.xml b/app/src/main/res/layout/layout_list_item.xml new file mode 100644 index 0000000..befddf1 --- /dev/null +++ b/app/src/main/res/layout/layout_list_item.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_live_issue.xml b/app/src/main/res/layout/layout_live_issue.xml new file mode 100644 index 0000000..d517efd --- /dev/null +++ b/app/src/main/res/layout/layout_live_issue.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_live_v2_data_info.xml b/app/src/main/res/layout/layout_live_v2_data_info.xml new file mode 100644 index 0000000..26364ea --- /dev/null +++ b/app/src/main/res/layout/layout_live_v2_data_info.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_login_cn_policy_terms.xml b/app/src/main/res/layout/layout_login_cn_policy_terms.xml new file mode 100644 index 0000000..8aee98c --- /dev/null +++ b/app/src/main/res/layout/layout_login_cn_policy_terms.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_only_pull_refresh.xml b/app/src/main/res/layout/layout_only_pull_refresh.xml new file mode 100644 index 0000000..f05b3ac --- /dev/null +++ b/app/src/main/res/layout/layout_only_pull_refresh.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_pet_gps_battery.xml b/app/src/main/res/layout/layout_pet_gps_battery.xml new file mode 100644 index 0000000..a11ede1 --- /dev/null +++ b/app/src/main/res/layout/layout_pet_gps_battery.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_pull_refresh_and_load_more.xml b/app/src/main/res/layout/layout_pull_refresh_and_load_more.xml new file mode 100644 index 0000000..8454103 --- /dev/null +++ b/app/src/main/res/layout/layout_pull_refresh_and_load_more.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_sport_data.xml b/app/src/main/res/layout/layout_sport_data.xml new file mode 100644 index 0000000..62c6485 --- /dev/null +++ b/app/src/main/res/layout/layout_sport_data.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_sport_time.xml b/app/src/main/res/layout/layout_sport_time.xml new file mode 100644 index 0000000..1d5d98d --- /dev/null +++ b/app/src/main/res/layout/layout_sport_time.xml @@ -0,0 +1,33 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_subscribe_count_down.xml b/app/src/main/res/layout/layout_subscribe_count_down.xml new file mode 100644 index 0000000..041607a --- /dev/null +++ b/app/src/main/res/layout/layout_subscribe_count_down.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_sure_subscribe_plan_addons_layout.xml b/app/src/main/res/layout/layout_sure_subscribe_plan_addons_layout.xml new file mode 100644 index 0000000..3b17b74 --- /dev/null +++ b/app/src/main/res/layout/layout_sure_subscribe_plan_addons_layout.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_sure_subscribe_plan_detail_layout.xml b/app/src/main/res/layout/layout_sure_subscribe_plan_detail_layout.xml new file mode 100644 index 0000000..1924a7e --- /dev/null +++ b/app/src/main/res/layout/layout_sure_subscribe_plan_detail_layout.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_sure_subscribe_plan_summary_layout.xml b/app/src/main/res/layout/layout_sure_subscribe_plan_summary_layout.xml new file mode 100644 index 0000000..056dab6 --- /dev/null +++ b/app/src/main/res/layout/layout_sure_subscribe_plan_summary_layout.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_top_bar.xml b/app/src/main/res/layout/layout_top_bar.xml new file mode 100644 index 0000000..3388552 --- /dev/null +++ b/app/src/main/res/layout/layout_top_bar.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_top_pet_data_big.xml b/app/src/main/res/layout/layout_top_pet_data_big.xml new file mode 100644 index 0000000..8ec3a2b --- /dev/null +++ b/app/src/main/res/layout/layout_top_pet_data_big.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_top_pet_data_small.xml b/app/src/main/res/layout/layout_top_pet_data_small.xml new file mode 100644 index 0000000..1e42215 --- /dev/null +++ b/app/src/main/res/layout/layout_top_pet_data_small.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_upload_head_edit.xml b/app/src/main/res/layout/layout_upload_head_edit.xml new file mode 100644 index 0000000..cfd215c --- /dev/null +++ b/app/src/main/res/layout/layout_upload_head_edit.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_virtual_fences_empty_view.xml b/app/src/main/res/layout/layout_virtual_fences_empty_view.xml new file mode 100644 index 0000000..34a4c6d --- /dev/null +++ b/app/src/main/res/layout/layout_virtual_fences_empty_view.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_item_day.xml b/app/src/main/res/layout/list_item_day.xml new file mode 100644 index 0000000..efc5dc7 --- /dev/null +++ b/app/src/main/res/layout/list_item_day.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/mapbox_search_sdk_result_missing_feedback_layout.xml b/app/src/main/res/layout/mapbox_search_sdk_result_missing_feedback_layout.xml new file mode 100644 index 0000000..f24f896 --- /dev/null +++ b/app/src/main/res/layout/mapbox_search_sdk_result_missing_feedback_layout.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/marker_info_contents.xml b/app/src/main/res/layout/marker_info_contents.xml new file mode 100644 index 0000000..4599f58 --- /dev/null +++ b/app/src/main/res/layout/marker_info_contents.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pop_subscription_item.xml b/app/src/main/res/layout/pop_subscription_item.xml new file mode 100644 index 0000000..2d72299 --- /dev/null +++ b/app/src/main/res/layout/pop_subscription_item.xml @@ -0,0 +1,40 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/qmui_common_list_item.xml b/app/src/main/res/layout/qmui_common_list_item.xml new file mode 100644 index 0000000..2f08922 --- /dev/null +++ b/app/src/main/res/layout/qmui_common_list_item.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_dialog_auto_recharge.xml b/app/src/main/res/layout/sheet_dialog_auto_recharge.xml new file mode 100644 index 0000000..d2320ff --- /dev/null +++ b/app/src/main/res/layout/sheet_dialog_auto_recharge.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_dialog_select_date.xml b/app/src/main/res/layout/sheet_dialog_select_date.xml new file mode 100644 index 0000000..d779d8f --- /dev/null +++ b/app/src/main/res/layout/sheet_dialog_select_date.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/toast_my_layout.xml b/app/src/main/res/layout/toast_my_layout.xml new file mode 100644 index 0000000..66c1cee --- /dev/null +++ b/app/src/main/res/layout/toast_my_layout.xml @@ -0,0 +1,12 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_credit_card_layout.xml b/app/src/main/res/layout/view_credit_card_layout.xml new file mode 100644 index 0000000..5c07661 --- /dev/null +++ b/app/src/main/res/layout/view_credit_card_layout.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_home_bottom_nav_v2.xml b/app/src/main/res/menu/menu_home_bottom_nav_v2.xml new file mode 100644 index 0000000..69fa637 --- /dev/null +++ b/app/src/main/res/menu/menu_home_bottom_nav_v2.xml @@ -0,0 +1,27 @@ + +

+ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..a571e60 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..61da551 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..c41dd28 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..db5080a Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..6dba46d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..da31a87 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/logo.png b/app/src/main/res/mipmap-xhdpi/logo.png new file mode 100644 index 0000000..c9aa83a Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/logo.png differ diff --git a/app/src/main/res/mipmap-xhdpi/logo_abbidot.png b/app/src/main/res/mipmap-xhdpi/logo_abbidot.png new file mode 100644 index 0000000..a97961e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/logo_abbidot.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..15ac681 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..b216f2d Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/logo.png b/app/src/main/res/mipmap-xxhdpi/logo.png new file mode 100644 index 0000000..c842c67 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/logo.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/logo_abbidot.png b/app/src/main/res/mipmap-xxhdpi/logo_abbidot.png new file mode 100644 index 0000000..9a53c70 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/logo_abbidot.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..f25a419 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e96783c Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/logo.png b/app/src/main/res/mipmap-xxxhdpi/logo.png new file mode 100644 index 0000000..875789e Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/logo.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/logo_abbidot.png b/app/src/main/res/mipmap-xxxhdpi/logo_abbidot.png new file mode 100644 index 0000000..a7a4d03 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/logo_abbidot.png differ diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml new file mode 100644 index 0000000..1285489 --- /dev/null +++ b/app/src/main/res/values-de/strings.xml @@ -0,0 +1,930 @@ + + + Haben Sie bereits einen GPS-Tracker? + Noch nicht + Ja + Hallo! + Willkommen in der ABBIDOT Tracker App! + Registrieren + Anmelden + Willkommen zurück + Schön, Sie wiederzusehen! + E-Mail + Telefon + Passwort + Passwort vergessen? + Mit WeChat anmelden + Mit Verifizierungscode anmelden + Mit Apple anmelden + Mit Konto anmelden + WeChat Ein-Klick-Anmeldung + Neues Passwort festlegen + Bestätigungscode gesendet + Noch kein Konto? + Mit Passwort anmelden + Registrieren + Hallo, + Zurück zur Anmeldung + Anmeldepasswort festlegen + Los geht\'s! + Vollständiger Name + Los geht\'s + Wir haben den Bestätigungscode an Ihre E-Mail gesendet. + Bestätigungscode + Weiter + Erneut senden + E-Mail nicht erhalten? + Zustimmung fortsetzen. + Bitte lesen und zustimmen + und + Nutzungsbedingungen + Datenschutzerklärung + Absenden + Bindung + Abgeschlossen + + Copyright © %d ABBIDOT All rights reserved + Daten + Karte + Profil + Meine Bestellungen + Hilfe-Center + Feedback + Einstellungen + Einstellung + Benachrichtigungen + Sprache + Konto + Über + Cache leeren + Abmelden + Feedback + Fragetyp (erforderlich) + Über die App + Über den Tracker + Sonstiges + Bitte beschreiben Sie Ihr Problem oder Ihren Vorschlag + Bitte wählen Sie einen Fragetyp + Bitte hinterlassen Sie Ihre E-Mail für schnellere Hilfe + + Bild hochladen + Name + Geschlecht + Geburtstag + Standort + E-Mail + Telefon + Rasse + Gewicht + Höhe + Höhe richtig messen + Messen Sie vom Boden bis zum höchsten Punkt des Widerrists Ihres Haustieres + Speichern + Tags + + Tracker-ID eingeben + Aktivieren + Aktivieren Sie das Gerät, um fortzufahren + Nein, danke + Aktivieren + Mein Tracker + %d Trackers + Tracker + Mein Tracker + Autorisiert + LED-Licht + Zaun + Einstellung + Neues Haustier + Kopplung aufheben + Netzwerk + GPS + Akku: %s%% + Tage + Bluetooth verbunden + Bluetooth nicht verbunden + LED-Licht + Aus + Ein + Zaun + Zugehöriges Haustier + Zugeordnet + Keine Haustiere zum Zuordnen + Neues Haustier hinzufügen + Familie + Familie hinzufügen + Laden Sie Familienmitglieder ein, den Standort oder die Aktivität des Haustiers zu teilen. Familienmitglieder können keine Zäune erstellen oder Einstellungen ändern. + Einladen + Einladung erfolgreich, bitte benachrichtigen Sie Ihre Familie. + Zurück + GPS-Aktualisierungen + Ausschalten + Zäune + Zaundetails + Name + Form + Zaun aktivieren + Radius + Fertigstellen + Aktuell + Anhaltend + Atemmodus + Langsames Blinken + Blinken + + Mein Haustier + %d Haustiers + Haustiere + Meine Haustiere + Autorisiert + Männlich + Weiblich + %s Jahre + %d Jahr + %d Pfund + %d Zoll + Über + Bearbeiten + Aktivitätsabzeichen + %d/100 Verdient + Alle + Alle abbrechen + + Zuletzt: %s + Ich schlafe jetzt + Ich trainiere jetzt + ICH BIN VERLOREN!!! + Niedriger Akkustand + Live-Tracking beenden + Live-Tracking aktualisiert den Standort in Echtzeit, verbraucht jedoch mehr Akkuleistung + Karte auswählen + Google Maps + Gaode Karte + Abbrechen + App nicht installiert. Bitte zuerst herunterladen und installieren + + Aktive Zeit + Ziel + Schritte + Distanz + Kalorien + Tracking-Zeit + Schlafenszeit + Aktive Zeit + Verlauf + Aktivitätsdetails + Ziel festlegen + Ändern + Aktuell + Verfolgen + Punkt + min + s + Meilen + kcal + Tage + Bitte vollständig ausfüllen + E-Mail-Format ungültig + Erfolgreich + Erfolgreich hochgeladen + Erfolgreich hinzugefügt + Erfolgreich gespeichert + Anmeldung erfolgreich + Fertig + Hinweise + Berechtigungsabfrage fehlgeschlagen + Berechtigung erfolgreich erhalten + Zäune hinzufügen + Zäune bearbeiten + Passwort vergessen + Routenverlauf & Punkte anzeigen + Keine Daten + Keine Haustiere + Bitte zuerst ein Haustier koppeln + Erfolgreich gelöscht + Möchten Sie dies wirklich löschen? + Möchten Sie das Gerät wirklich entkoppeln? + Entkopplung erfolgreich + Kopplung erfolgreich + Gewicht muss über 0 liegen + Heute\t%s + Heute + min + s + %d%% + Meilen + kcal + Tage + Ihr Gerät ist abgelaufen, bitte erneuern Sie + Möchten Sie das Haustier wirklich entkoppeln? + Live-Tracking beenden? + Möchten Sie die Daten Ihres Haustieres wirklich mit \' %s \' teilen? + Möchten Sie dieses Haustier wirklich hinzufügen? + Löschen + Abbrechen + %dY %dMo + Ja + Mo + Ja + Benachrichtigungen + Alle auswählen + Alle abbrechen + Anfrage fehlgeschlagen, bitte versuchen Sie es erneut + Karte wird geladen… + Erfolgreich registriert + Hallo leeres Fragment + Alle Fotos + Fotos und Videos + Alle Videos + Auswählen + Abgeschlossen(%1$d/%2$d) + Vorschau + Bis zu %d Fotos/Video + Bis zu %d Fotos + Bis zu %d Videos + Es kann nur ein Dateityp ausgewählt werden + Kamera konnte nicht gestartet werden + Ungültiges Bild + Bitte gewähren Sie die entsprechenden Berechtigungen + Die Foto-Breite muss größer als %1$d und die Höhe größer als %1$d sein. + Dieses Bildformat wird nicht unterstützt + Bearbeiten + Löschen + Keine passenden Fotos + Keine passenden Videos + Bitte gewähren Sie die entsprechenden Berechtigungen + Bitte gewähren Sie die entsprechenden Berechtigungen + Original + %1$d/%2$d + Fertigstellen + Abbrechen + Vorlage + Text + Datum + Doppelklicken zum Bearbeiten + Anpassen + Fotoauswahl + Videoauswahl + Bitte wählen Sie 2 bis 9 Fotos aus + GIF + Video + + Meine Karte + Tage + Stand: %s + Automatische Verlängerung: + Aufladeverlauf + Aufladen + Aufladebetrag + Verlängerung zu %.2f/Jahr danach & jederzeit kündbar + Bestelldetails + Zwischensumme + Gutschein + Aktionscode + Gesamtsumme + Endbetrag + Bereits %d% reduziert + Bezahlen + Zahlungsmethode auswählen + Neue Karte hinzufügen + Bestätigen + Zahlung + Zahlung erfolgreich + Zahlung fehlgeschlagen, bitte erneut versuchen + Erneut versuchen + Zurück + Abonnementplan: + %d Jahre Verbindungsabonnement + Zahlungsintervall: + Ablaufdatum: + Abonnement aktiv bis %s + Autoverlängerung aus. Abonnement endet am %s + Englisch + Möchten Sie sich wirklich abmelden? + App-Cache löschen bestätigen? + Heller Modus + Firmware-Update + Erfolgreich übermittelt, danke für Ihr Feedback + Erfolgreich gelöscht + Lädt… + Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut + Automatisches Abonnement kündigen? Bestätigen? + Automatische Verlängerung erfolgreich gekündigt + Automatische Verlängerung: Kein Ablaufdatum, bequeme Nutzung + $%s/Monat + $\t%.2f + Speichern & Bestätigen + Kartennummer + Name des Karteninhabers + KARTENINHABER + ABLAUFDATUM + MM/JJ + CVV + Bitte Zahlungsmethode wählen + Benutzer bricht Zahlung ab + Zahlung erfolgreich + Zahlung fehlgeschlagen, bitte erneut versuchen + Ungültiges Ablaufdatum + Ungültiges Kreditkartenformat + Möchten Sie das Gerät wirklich ausschalten? + Zuschneiden + Verbindung zu Bluetooth wird hergestellt + Konto löschen + Kontolöschung löscht alle Daten dauerhaft. Sind Sie sicher? + Konto erfolgreich gelöscht + Zum Beenden erneut drücken + Falscher Benutzername oder Passwort + Bestätigungscode-Fehler + Bestätigungscode abgelaufen + Registrierung fehlgeschlagen + Benutzer existiert nicht + Benutzer existiert bereits + Ungültiges E-Mail-Format + Gerät trennen? + Verbindung erfolgreich + Verbindung fehlgeschlagen + Aktivierung erfolgreich + Aktivierung fehlgeschlagen, bitte erneut versuchen + Erstanmeldung + Bitte verbinden Sie Ihre Handynummer + Bitte geben Sie Ihre Handynummer ein + Passwort zurücksetzen + Bestellung absenden + $ + Verbleibende Nutzungstage + Gültigkeitsdauer + Tage + Gerät + Reaktivieren + Gesamtbetrag + Abbidot + Version%s + Allgemeine Geschäftsbedingungen + Datenschutzerklärung + Website + Passwort zurücksetzen + Zahlung stornieren + Bitte Bluetooth am Telefon aktivieren + Sie können nicht mit sich selbst teilen + Aktueller Benutzer hat dieses Gerät bereits gekoppelt + Vorname + Nachname + Online + Offline + %s%% + Rechtliche Informationen + Willkommen in der Abbidot App! Um Ihre Rechte besser zu schützen, erläutern wir in dieser Datenschutzerklärung, wie wir Ihre Daten erfassen, nutzen, verarbeiten und schützen. Bitte lesen Sie die Datenschutzerklärung und Nutzungsbedingungen sorgfältig. + Datenschutzerklärung & Nutzungsbedingungen akzeptieren + Ich akzeptiere + Ablehnen + Keine Download-Adresse + Firmware herunterladen und aktualisieren? + Bitte warten… + Versionsbeschreibung + Firmware-Name + Firmware-Version + Firmware-Dateigröße + Aktualisieren + Update erfolgreich + Update fehlgeschlagen + Firmware herunterladen + Wechsle in DFU-Modus + Suche nach DFU-Geräten + Gerät existiert nicht + Verbinde Geräte + Gerät erneut verbinden + Gerät getrennt + Getrennt + Aktualisierung läuft + Vorbereitung des Updates + Gerät erfolgreich verbunden + Aktuell neueste Version + Tracker ist mit anderem Konto gekoppelt + Aktivität + Route + Haustier + Mehr + km + Schlaf + Live + Zone + Tracker + Abzeichen + Alle anzeigen + Verwalten + Aktivitätsziel + Zeit + Startzeit + Endzeit + Virtuelle Zäune + WLAN-Zone + Verbunden + Schwaches Signal + Akku + Bluetooth + Licht + Von + Bis + Routenverlauf & Punkte prüfen + Alle lesen + Abonnement + Hilfe + Funktionsweise + FAQ + Maßeinheiten + Shop + Abmelden + Kalorien Ø + Zeit Ø + Gesamtdaten + Std + Gesamtdistanz + Letzte 7 Tage + Letzte 30 Tage + OK + Januar + Februar + März + April + Mai + Juni + Juli + August + September + Oktober + November + Dezember + Jan + Feb + Mär + Apr + Mai + Jun + Jul + Aug + Sep + Okt + Nov + Dez + Richtung + Je schneller der Ton, desto näher die Distanz. + Geschwindigkeit + Radar stoppen + WLAN-Zone bearbeiten + WLAN-Name: + Sicherheitszone + Neue hinzufügen + No-Go-Zone + Noch keine Zone hinzugefügt + Sicherheitszone hinzufügen + Sicherheitszone bearbeiten + Zaunname + Zonenname + No-Go-Zone hinzufügen + No-Go-Zone bearbeiten + Datenübersicht + Minimum + Empfohlen + Std + Tagesziel festlegen + Passen Sie den Tagesablauf Ihres Haustiers für mehr Freude an: Fokussierte Aktivität morgens und abends unter Berücksichtigung von Bedürfnissen, Art und Rasse. + Großartig! Bereit zu starten! + Gesamte Aktivitätsdistanz + km + Tagesdurchschnittliche Aktivitätszeit + Ø Kalorien + Tagesdurchschnittliche Ruhezeit + Profil + Pfund + Zoll + Typ + Geschlecht + Hund + Katze + Andere + kg + cm + Mitglieder + +\Einladen + Aktiv + Ausstehend + Beziehung + Geteilte Familienmitglieder/Freunde können Standort und Daten Ihres Haustiers einsehen, ohne sie zu bearbeiten. + Avatar wählen + Freund + Liebe + Neue Meilensteine freischalten! + Kollege + Sie haben Ihr Haustier erfolgreich eingerichtet und das Sicherheitsabzeichen verdient! + Herzlichen Glückwunsch! + Teilen + Anmelden + Registrierung + Konto erstellen + Mit Registrierung akzeptieren Sie die AGB und Datenschutzerklärung von Nation Remit. + Datenschutzerklärung. + Mindestens 6 Zeichen + Land + E-Mail-Adresse + Willkommen zurück + Geben Sie Ihre registrierte E-Mail-Adresse ein. Wir senden einen 4-stelligen Code an Ihre E-Mail. + Weiter + Bitte geben Sie den 4-stelligen Code ein, der an %s gesendet wurde. + OTP-Verifizierung + Verbleibende Zeit:\t + Sek. + Code erneut senden + Neues Passwort erstellen + Jetzt zurücksetzen + Passwort bestätigen + Passwort erfolgreich zurückgesetzt! + Zurücksetzen + Standard + Satellit + Zäune anzeigen + Genauigkeit anzeigen + Eingabe + Auswählen + Immer an Ihrer Seite! + Unterstützt von + Weniger anzeigen + Alle anzeigen + Abenteuer + Sozial + Sportlich + Neuer Tracker + Neuen Tracker hinzufügen + Laden und einschalten + Schritt 1: + Laden Sie den Tracker zuerst auf; rotes Licht leuchtet. + Kabel abziehen, 3 grüne Blitze – Gerät eingeschaltet. + Schritt 2: + Suche nach Tracker + Halten Sie das Telefon nahe an den Tracker. + Bestätigen Sie, dass die 6-stellige ID mit der auf der Tracker-Rückseite übereinstimmt. + Mit Tracker koppeln + ID-Nummer + Nein + Finden Sie die 6-stellige ID auf der Tracker-Rückseite. + ID zur Kopplung eingeben + Tracker-ID fehlerhaft, bitte prüfen und erneut versuchen + Gerät bereits gekoppelt, bitte prüfen und erneut versuchen + Tracker hinzufügen + Erfolgreich\ngekoppelt + Wählen Sie Ihren Plan + Vorteile + Monatliches Abonnement + $%s + /pro Monat + /pro Jahr + $%s/pro Monat + $%s/pro Jahr + Beliebt + Plan bestätigen + Gerätenummer + Allgemeine Geschäftsbedingungen + Automatische Verlängerung + Über automatische Verlängerung + Ihr Jahresabonnement verlängert sich automatisch am Ende jeder Periode für ununterbrochenen Service. + Zusammenfassung + Steuer + Gesamtbetrag inkl. Steuer + Zahlungsmethode + Karte auswählen + Keine Karte gefunden + Neue Karte hinzufügen + Primär + Zahlung durchführen + Weiterleitung zu PayPal + Zahlungsmethode hinzufügen + Neue Karte speichern + Zahlung erfolgreich + Läuft ab am + MM/JJ + CVC + Akzeptierte Zahlungskarten + Als primäre Karte festlegen + Name auf Karte + Berechtigungen + Überspringen + Aktivieren + Berechtigungen für besseres Erlebnis erteilen. + Abonnementdetails + Tracker-ID + Plan: + ($%s/Jahr) + (Verlängerung zu + pro Jahr) + Verlängert am: + Wohnortangabe verbessert Akkulaufzeit und Sicherheit + Energiesparzone einrichten + Dauerhafter Wohnort spart Energie, verbessert Ortung. + Ist dies das Zuhause Ihres Haustieres? + Dauerhafter Wohnsitz + WLAN-Netzwerk wählen, kein Passwort nötig. + Später + Welche Maßeinheiten für Ihr Haustier? + Größe Ihres Haustieres + Länge + Gewicht Ihres Haustieres + Benachrichtigungseinstellungen + Betreten/Verlassen der Sicherheitszone/No-Go-Zone + Tracker-Status + Abonnement/Akku-Warnungen/Ausschalten + Aktivitäts-/Schlaf-Erinnerung/Zielerreichung + WLAN-Zone + Über WLAN-Zone + Wählen Sie ein Netzwerk in einem vertrauten Bereich, in dem Ihr Haustier viel Zeit verbringt. Wie Ihr Zuhause. In Reichweite erkennt Ihr Tracker dieses Netzwerk als WLAN-Zone. + Büro + Solange der Tracker deinen Buddy sicher in einer WLAN-Zone erkennt, bleibt LIVE-Tracking, Licht und Ton aus, um die Akkulaufzeit zu verlängern. + Energie sparen + In Bluetooth-Reichweite? Ort einen nahen Tracker per Radar. Live wird verfügbar, beendet aber den Energiesparmodus. + Haustier-Tracking + WLAN-Zoneneinstellungen + Problem oder Vorschlag eingeben + Hinterlass deine E-Mail, wir helfen dir schneller + E-Mail-Adresse eingeben + Datei wählen + Ausschalten + Haustierart? + Name deines Haustiers? + Aktuelle LED + Sicherheitszone verlassen + In der WLAN-Zone + WLAN-Zone verlassen + In der No-Go-Zone + Starkes Signal + WLAN-Zone + %sFuß + %sMeilen/h + Deaktivieren + Bitte gültige E-Mail-Adresse verwenden. + Bitte vollständigen Namen eingeben. + Bitte gültiges Passwort verwenden. + Bitte Region auswählen. + An ABBIDOT + Die eingegebenen Passwörter stimmen nicht überein! + Noch kein Mitglied + +Einladen + Mitglieder einladen + Anderes Haustier zuweisen + Zuweisung aufheben %s + Anderen Tracker zuweisen + Dadurch werden alle %s-Daten dauerhaft von Ihrem Konto gelöscht. + Dadurch werden alle + -Daten dauerhaft von Ihrem Konto gelöscht. + Möchten Sie %s die Zuweisung zu diesem Tracker %s aufheben? + Zuhause + Möchten Sie die Familie %s wirklich löschen? + Live-Dauer + %s Min + Info zur Live-Dauer + Live-Modus sucht Haustiere mit Ortungsupdates alle 2-3 Sekunden, verbraucht aber mehr Akku. Standard-Tracking sendet weiterhin regelmäßige Updates. + Live-Tracking endet automatisch nach der eingestellten Dauer. Sie können es früher stoppen, um Akku zu sparen. + Kurz halten + Bluetooth + •\Erforderlich für präzise Aktivitätssynchronisierung + GPS-Standort + •\Zeigt Ihren Standort auf der Karte\n•\Erforderlich zur Aktivitätsdaten-Synchronisierung\n•\Erhöht die Tracker-Genauigkeit + •\Erlaube Benachrichtigungen, um wichtige Infos zu erhalten – z.B. wenn dein Buddy eine Sicherheitszone verlässt oder der Ladezustand kritisch ist. + Benachrichtigungen erhalten + AKTIVIEREN + Gerät nicht aktiviert. Abonniere zum Freischalten. + Aktivierung\nErfolgreich + Anmeldung\nerfolgreich! + Stunden + Wird gesucht… + Tracker an & nah halten zum Scannen. + Akku unter %s%% + Draußen + Drinnen + 7-Tage-Schnitt + Automatische Verlängerung stoppen + Automatische Verlängerung + Zu viele Geofences erstellt + LIVE-Modus beendet. Dauer unter „Live-Dauer“ einstellbar. + Schwaches GPS-Signal + Kein Netzsignal + Tracker ausgeschaltet + Starte Live… + Verbinde mit Netzwerk + GPS-Positionierung + Startet %s-min Live + Startet + -min Live + Zeitüberschreitung. Bitte erneut versuchen. + Schließen + Info zu Live + Live-Tracking endet in einer Stunde automatisch. Kann vorher gestoppt werden, um Akku zu sparen. + Ruhemodus + Ruhezeit + Ruhezeit + Kein Signal + WLAN + Maßeinheit wählen + Metrisch(cm, kg) + Imperial(in, lb) + km/h + mph + m + Fuß + Geofence-Benachrichtigungen + Mein Geofence + Dein Feedback hilft uns besser zu werden. + Nachricht eingeben + Tracker im Ruhemodus + Bitte E-Mail weiter verifizieren + Code gültig für %ss + Code gültig für + Minuten + Passwort erstellen + E-Mail-Änderung erfolgreich! + E-Mail ändern + Neue E-Mail eingeben + Reaktivierungsgebühr + App im Hintergrund laufen lassen für Tracker-Statusbenachrichtigungen. + Erinnerung + Gestern + Ein-/Ausschalten + Tracker vollständig laden, dann Kabel entfernen. Bei grünem Blinken ist er eingeschaltet. + Abonnement auswählen/abschließen, dann im App Tracker aktivieren. Aktivierter Tracker nutzt GPS und Mobilfunk. + Aktiver Tracker + (GPS/Mobilfunk variiert je Umgebung. Vor Anwendung an verschiedenen Orten prüfen.) + Tracker-Status prüfen + Bei 15 min Bewegungslosigkeit schaltet Tracker GPS/Mobilfunk ab (Ruhemodus). Bei Bewegung startet er automatisch neu. + Tracker mit Heim-WLAN verbinden (passwortfrei). In der WLAN-Zone ist GPS deaktiviert. Verbindet alle 10–60 Minuten bei minimalem Stromverbrauch mit dem Netz. + Wenn sich Ihr Tracker außerhalb der WLAN-Zone befindet, aktiviert er das GPS und verbindet sich alle 1/3/5/10/15 Minuten mit dem Netzwerk. + „Haustier“ > „Tracker“ wählen. „GPS-Update“ klicken und 1/3/5/10/15 Minuten auswählen. + (Kürzere Intervalle erhöhen den Akkuverbrauch. Standard: Alle 15 Minuten.) + Live-Modus startet Schnellortung in 15–20 Sekunden. Höchster Akkuverbrauch – nur im Notfall nutzen. + Tracker-Akku-Lebensdauer + 1. Geofence-Einstellung: „Haustier“ > „Mein Geofence“. „Verwalten“ > „+ Neu“ für Sicherheits-/No-Go-Zonen. + 2. Grenze festlegen: Ort auf Karte wählen, Radius/Form anpassen. + 3. Speichern: Geofence aktivieren. + 4. Benachrichtigungen: „Account“ > „Einstellungen“ > „Benachrichtigungen“ aktivieren. + Mein Geofence erstellen + Tracker im Ruhemodus + (Sehr geringer Verbrauch) + Tracker in WLAN-Zone + Tracker außerhalb WLAN-Zone + (Geringer Verbrauch) + (Standardverbrauch) + (Höchster Verbrauch) + Passendes GPS-Update-Intervall wählen + Tracker im Live-Modus + Frische Pfoten + Tag-1-Fan + Navigations-Neuling + Pelziger Freund + Potentieller Kumpel + Monatskamerad + Schwanzwedel-Entdecker + Pfotastischer Partner + 1-Jahres-Umarmer + Purrfekter Partner + 2-Jahres-Gefährte + Treuer Freund + 3-Jahres-Harmonie + Langzeit-Fellfreund + Willkommen! Starte mit deinem ersten Abzeichen. + Erster Tag gemeistert! Dein Einsatz ist notiert. + Logge dich am Tag 1 ein für dieses Abzeichen! + Du findest dich zurecht. Erkunde weiter für Meilensteine! + Drei Tage aktiv für diesen Meilenstein! + Eine Woche dabei – schon Teil unserer Fellfamilie! + Eine Woche aktiv für „Pelziger Freund“! + Großes Potenzial! Weiter so. + 15 Tage engagiert für dieses Abzeichen! + Ein Monat dabei – geschätztes Mitglied! + Ein Monat in der Community für dieses Abzeichen! + Drei Monate Abenteuer – gut gemacht! + Drei Monate dabei für „Schwanzwedel-Entdecker“! + Sechs Monate tolle Partnerschaft. Danke fürs Dabeibleiben! + Sechs Monate dabei für „Pfotastischer Partner“! + Ein ganzes Jahr Einsatz! Auf viele weitere! + Ein Jahr dabei für dieses Abzeichen! + 18 Monate und stark dabei. Du bist purrfekt! + 18 Monate engagiert für „Purrfekter Partner“! + Zwei Jahre Treue – hoch geschätzt! + Zwei Jahre dabei für den „2-Jahres-Gefährten“! + 30 Monate treue Begleitung. Du bist ein Schatz! + 30 Monate treu für dieses Abzeichen! + Drei Jahre Harmonie. Danke, dass du dabei bist! + Drei Jahre Harmonie für dieses Abzeichen! + Fünf Jahre wunderbare Freundschaft. Dein Engagement inspiriert! + Fünf Jahre Einsatz für „Langzeit-Fellfreund“! + Wie erhält man? + Familieneinladung + Aktive Zeit: + Dein Freund %s lädt dich zur Familie %s ein. + Dein Freund + lädt dich zur + Familie ein. + Läuft ab am: + Abgelaufen am: + JAHRE + MONATE + TAGE + Automatische Verlängerung + Abonnement-Bestellung + Bestell-ID: + Grund für Rückerstattung + Rückerstattung anfragen + Erstattung + Wartend + Rückzahlung läuft + Erstattet + Verlängerung: $%s/year on %s + Verlängerung: + /Jahr am + Verlängerung: $%s/month on %s + Verlängerung: + /Monat am + Jahresschutz + 12,90€/Jahr für Sorgenfreiheit + jährlichen Austausch. + Austauschgarantie + Rabattcode eingeben + Prüfen + Jahresschutz + Jahresschutz: + Bluetooth-Verbindung fehlgeschlagen + LED-Einstellungen + Tracker neben Handy? Per Bluetooth verbinden und LED anpassen. Änderungen sofort aktiv. + Bluetooth-Einrichtung + Tracker nicht in Reichweite? LED-Einstellungen übers Netz vornehmen. Dauert meist Minuten. + Doppeltes WLAN nicht erlaubt + Netzwerkeinrichtung + Genaues Gewicht für präzise Kalorienberechnung. + Autoverlängerung aktivieren + Info zu Gewicht + Bestellung + Premium festlegen + Basis + Premium + Abgelehnt + Abgelaufen + Tarif upgraden + Restwert + /%s Monat + (Verlängerung zu $%s jährlich danach) + (Verlängerung zu + jährlich danach) + (Verlängerung zu $%s monatlich danach) + (Verlängerung zu + monatlich danach) + Haustiere orten und trainieren, um Verlust/Gefahren vorzubeugen. + /%s Jahr + Erweiterte Einstellungen + Konto löschen + Namen ändern + Ihre E-Mail-Adresse: + Jetzt ändern + Ein Bestätigungscode wird an %s gesendet. + Upgrade zu Prime für exklusive Funktionen! + Jetzt upgraden + Aktivitätszeit + Fehlgeschlagen + Neue Firmware erkannt: %s + Nicht festgelegt + Fertig + Wetter speichern? + %s Min + Verbinden + Passwort überspringen + Verbinde... + Falsches Passwort. Bitte erneut versuchen. + Ihr Zugriff ist abgelaufen. + „%s“ lädt dich ein, den Status von „%s“ zu prüfen. + Solange der Tracker deinen Buddy sicher in der Energiesparzone erkennt, bleiben LIVE-Tracking, Licht und Ton aus. + Info zur Tracking-Dauer + Ihr Abonnement ist abgelaufen. Bitte aufladen. + %s Geofences gesetzt + %s ist in der Nähe + Tracker nur löschen, wenn du ihn zurückgibst oder weitergibst. + Tracker löschen + Wird gelöscht + LED-Einstellung + Reserve + Abonnementplan + Halte das Handy nahe an den Tracker %s. + Verbinde Tracker + Jahresschutz inklusive, nicht änderbar + Erfolgreich gelöscht + Zahlungsmethode bearbeiten + Karte wirklich löschen? + Konto existiert. Jetzt anmelden? + Datum außerhalb des Bereichs + Nächstes Mal Tracker schütteln zum Aufwecken. + Tracker einschalten: App > „Haustier“ > „Tracker“ > „Netzwerk“, „GPS“, „Akku“, „WLAN-Zone“, „Bluetooth“ oder „Licht“ für Statusprüfung. + Tracker lädt + Abonniert + Senden + Zum Einschalten Kabel anschließen. Nach erstem Start: Schütteln zum Einschalten. + Kein Signal. Prüfen, ob Haustier im Innenbereich. + Kartennummer existiert bereits. Überschreiben? + Passwort eingeben. + E-Mail-Adresse eingeben. + Wi-Fi gewährleistet Tracker-Verbindung bei schwachem LTE. + Updated: %s + + \ No newline at end of file diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml new file mode 100644 index 0000000..10125a3 --- /dev/null +++ b/app/src/main/res/values-land/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml new file mode 100644 index 0000000..64f6d3b --- /dev/null +++ b/app/src/main/res/values-night/colors.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..17a54dd --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values-w1240dp/dimens.xml b/app/src/main/res/values-w1240dp/dimens.xml new file mode 100644 index 0000000..ec434d3 --- /dev/null +++ b/app/src/main/res/values-w1240dp/dimens.xml @@ -0,0 +1,3 @@ + + 200dp + \ No newline at end of file diff --git a/app/src/main/res/values-w600dp/dimens.xml b/app/src/main/res/values-w600dp/dimens.xml new file mode 100644 index 0000000..10125a3 --- /dev/null +++ b/app/src/main/res/values-w600dp/dimens.xml @@ -0,0 +1,3 @@ + + 48dp + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml new file mode 100644 index 0000000..b646d3a --- /dev/null +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -0,0 +1,975 @@ + + + 奥地利 + 比利时 + 丹麦 + 法国 + 德国 + 希腊 + 匈牙利 + 爱尔兰 + 列支敦士登 + 卢森堡 + 荷兰 + 波兰 + 罗马尼亚 + 西班牙 + 瑞典 + 英国 + 捷克 + 冰岛 + 斯洛伐克 + 塞浦路斯 + 爱沙尼亚 + 芬兰 + 拉脱维亚 + 立陶宛 + 挪威 + 葡萄牙 + 斯洛文尼亚 + 乌克兰 + 以色列 + 美国 + 加拿大 + 墨西哥 + 澳大利亚 + 哥伦比亚 + 新加坡 + 韩国 + 中国台湾 + 中国 + 日本 + + + + + + 已有设备? + 还没有 + 是的 + Hello! + 欢迎使用ABBIDOT宠物定位APP + 注册 + 登录 + 欢迎 + 很高兴见到你 + 邮箱 + 手机号 + 密码 + 忘记密码? + 微信登录 + 验证码登录 + Apple登录 + 账号登录 + 微信一键登录 + 设置新密码 + 验证码已发送 + 没有账号? + 使用密码登录 + 注册 + 嗨, + 返回登录 + 设置登录密码 + 让我们开始吧 + 全名 + 获取 + 我们已将验证码发送到您的电子邮件 + 验证码 + 下一步 + 重发 + 还没有收到电子邮件? + 请阅读并同意《服务协议》和《隐私政策》 + 请阅读并同意 + + 《服务协议》 + 《隐私政策》 + 提交 + 绑定 + 完成 + + 数据 + 地图 + 我的 + 我的订单 + 帮助中心 + 反馈中心 + 设置 + 设置 + 消息中心 + 语言 + 帐户 + 关于 + 清除缓存 + 退出登录 + 反馈中心 + 问题类型(必填) + 关于APP + 关于设备 + 其它 + 请输入您的问题或建议 + 请选择问题类型 + 请留下您的电子邮件,以便我们更快地帮助您 + + 上传图片 + 名称 + 性别 + 生日 + 地点 + 邮箱 + 手机 + 品种 + 体重 + 肩高 + 如何测量身高 + 从地面测量到宠物肩胛骨的最高点 + 保存 + 标签 + + 输入设备ID + 激活 + 激活设备以继续 + 不用了,谢谢 + 激活 + 我的设备 + %d个设备 + 设备 + 我的设备 + 被授权的 + LED灯 + 围栏 + 设置 + 新宠物 + 解绑 + 网络 + GPS + 电量:%s%% + + 蓝牙已连接 + 蓝牙未连接 + LED灯 + + + 围栏 + 关联宠物 + 关联 + 没有宠物可关联 + 添加新宠物 + 家人 + 添加家人 + 邀请您的家人分享宠物的位置或活动。 家庭成员不能创建栅栏或修改设置。 + 邀请 + 邀请成功,请通知你的家人 + 返回 + 追踪时间 + 关机 + 围栏 + 围栏详情 + 名称 + 形式 + 启用围栏 + 半径 + 完成 + 当前 + 常亮 + 呼吸 + 慢闪 + 快闪 + + 我的宠物 + %d只 + 宠物 + 我的宠物 + 分享的 + + + %d年 + %d年 + %d磅 + %d 英寸 + 关于 + 编辑 + 活动勋章 + %d/100 已获得 + 全选 + 取消全选 + + 更新时间:%s + 我在睡觉 + 我在运动 + 我走丢了!!! + 电量低 + 停止直播 + 直播会及时更新位置,但是会比较耗电 + 选择地图 + 谷歌地图 + 高德地图 + 取消 + APP未安装,请先下载安装APP + + 运动时间 + 目标 + 步数 + 里程 + 卡路里 + 追踪时间 + 睡眠时间 + 运动时间 + 历史 + 历史记录 + 设置目标 + 修改 + 当前 + 轨迹 + + 分钟 + + + 大卡 + + 请完整填写 + 电子邮件格式不正确 + 成功 + 上传成功 + 添加成功 + 保存成功 + 登录成功 + 修改成功 + 提示 + 未能获得许可 + 成功获得许可 + 添加围栏 + 编辑围栏 + 忘记密码 + 显示轨迹/点 + 无数据 + 无宠物 + 请先绑定宠物 + 成功删除 + 确定要删除? + 确定要解绑设备? + 解绑成功 + 绑定成功 + 体重必须大于0 + 今天\t%s + 今天 + 分钟 + + %d%% + 英里 + 大卡 + + 您的设备已过期,请充值 + 确定要解绑宠物? + 停止监控宠物轨迹? + 确定要分享\'%s\'的宠物数据 + 确定要添加此宠物吗? + 删除 + 取消 + %d年%d月 + + + 确定 + 通知 + 全选 + 取消全选 + 请求失败,请重试 + 地图加载中… + 注册成功 + 你好,空白片段 + 全部图片 + 图片和视频 + 全部视频 + 选择 + 完成(%1$d/%2$d) + 预览 + 最多支持%d个图片或视频 + 最多支持%d个图片 + 最多支持%d个视频 + 只能选择一种类型的文件 + 无法启动相机 + 图片错误 + 请允许相关权限 + 照片的宽度应大于%1$d,高度应大于%1$d + 不支持该图像格式 + 编辑 + 清除 + 没有匹配的照片 + 没有匹配的视频 + 请允许相关权限 + 请允许相关权限 + 原始 + %1$d/%2$d + 完成 + 取消 + 模板 + 文本 + 日期 + 双击编辑 + 自定义 + 选择照片 + 选择视频 + 请选择2 \~ 9张照片 + GIF + 视频 + + 我的E-sim卡 + + 截止至%s + 自动订阅: + 充值记录 + 充值 + 选择套餐 + 此后以%.2f/年续订,可随时取消 + 订单详情 + 小计 + 优惠券 + 优惠码 + 积分 + 总计 + 已优惠%d + 支付 + 付款方式 + 添加新卡 + 确认 + 支付详情 + 支付成功 + 支付失败,请重试 + 重试 + 返回 + 订阅计划: + %d年连接订阅 + 付款间隔: + 截止日期: + 订阅计划有效期至%s + 自动续订已关闭,订阅计划将在%s之后不可用 + 简体中文 + 确定要退出登录吗? + 确定要清除缓存? + 灯光模式 + 固件升级 + 提交成功,谢谢您的反馈 + 清除成功 + 加载中... + 登录失败,请重新登录 + 确定要取消自动订阅? + 自动订阅已成功取消 + 自动订阅更方便,因为您不必担心订阅过期 + $%s/月 + ¥\t%.2f + 保存并确认 + 卡号 + 持卡人姓名 + 持卡人 + 截止日期 + MM/YY + CVV + 请选择支付方式 + 用户取消付款 + 支付成功 + 支付失败,请重试 + 失效日期 + 信用卡格式无效 + 您确定要关闭设备吗? + 裁剪图片 + 连接中… + 账户注销 + 注销账户将删除账户下所有的数据,是否继续? + 注销成功 + 再次返回退出程序 + 用户名或密码错误 + 验证码错误 + 验证码已过期 + 注册失败 + 用户不存在 + 用户已存在 + 邮箱格式不对 + 是否断开连接设备? + 连接成功 + 连接失败 + 激活成功 + 激活失败,请重试 + 首次登录 + 请绑定手机 + 请输入手机号 + 找回密码 + 提交订单 + $ + 剩余可用天数: + 有效期: + %d天 + 设备:%s + 重新激活:%s + 合计:%s + 爱彼宠物 + 版本%s + 服务协议 + 隐私政策 + 官网 + 修改密码 + 取消支付 + 请打开手机蓝牙 + 不能分享给自己 + 当前用户已经绑定该设备 + + + 在线 + 离线 + %s%% + 法律信息 + 欢迎使用Abbidot应用!为了更好地保障您的权益,我们将在本《隐私政策》声明中向您说明我们如何收集、使用、处理和保障您提供给我们的信息。请仔细阅读《隐私政策》和《服务条款》。 + 接受隐私政策与条款。 + 我接受 + 拒绝 + 没有下载地址 + 是否下载固件并升级? + 请稍等… + 版本说明 + 固件名称 + 固件版本 + 固件大小 + 升级 + 升级成功 + 升级失败 + 下载固件 + 进入DFU模式 + 搜索DUF设备 + 设备不存在 + 连接设备 + 重连设备 + 设备已断开 + 断开连接 + 升级中 + 准备升级 + 设备连接成功 + 当前是最新版本 + 该设备已被绑定 + 活动 + 路线 + 宠物 + 更多 + 公里 + 睡眠 + 实时 + 区域 + 追踪器 + 徽章 + 查看全部 + 管理 + 活动目标 + 时间 + 开始时间 + 结束时间 + 虚拟围栏 + WiFi区域 + 已连接 + 信号弱 + 电量 + 蓝牙 + 指示灯 + + + 检查轨迹和地点 + 全部已读 + 订阅 + 帮助 + 使用说明 + 常见问题 + 计量单位 + 商店 + 退出登录 + 平均卡路里 + 平均时间 + 总数据 + 小时 + 总距离 + 最近7天 + 最近30天 + 确定 + 一月 + 二月 + 三月 + 四月 + 五月 + 六月 + 七月 + 八月 + 九月 + 十月 + 十一月 + 十二月 + 1月 + 2月 + 3月 + 4月 + 5月 + 6月 + 7月 + 8月 + 9月 + 10月 + 11月 + 12月 + 方向 + 声音传播速度加快时,距离更近。 + 速度 + 停止雷达扫描 + 编辑WiFi区域 + WiFi名称: + 安全区域 + 添加新区域 + 禁入区域 + 尚未添加区域 + 添加安全区域 + 编辑安全区域 + 围栏名称 + 区域名称 + 添加禁入区域 + 编辑禁入区域 + 数据面板 + 最小值 + 推荐值 + 小时 + 设置每日目标 + 为宠物定制每日趣味活动,专注早晚互动,兼顾其需求、物种与品种 + 太棒了!开始吧! + 活动总距离 + 公里 + 日均活动时长 + 平均卡路里 + 日均休息时长 + 档案 + + 英寸 + 类型 + 性别 + + + 其他 + 千克 + 厘米 + 成员 + +\邀请 + 活跃中 + 待处理 + 关系 + 共享的家庭成员或朋友可查看宠物位置和日期,但无法编辑 + 选择头像 + 朋友 + 爱人 + 解锁新成就! + 同事 + 宠物信息设置成功,获得安全徽章! + 恭喜! + 分享 + 登录 + 注册 + 创建账户 + 注册即表示接受Nation Remit的条款与条件和隐私政策 + 隐私政策 + 至少6个字符 + 国家/地区 + 电子邮箱 + 欢迎回来 + 输入您档案的注册邮箱,我们将发送4位数验证码至该邮箱 + 继续 + 请输入发送至 %s 的4位数验证码 + OTP验证 + 剩余时间:\t + + 重发验证码 + 创建新密码 + 立即重置 + 确认密码 + 密码重置成功! + 重置 + 标准视图 + 卫星视图 + 显示围栏 + 显示精确度 + 输入 + 选择 + 时刻相伴! + 技术支持 + 收起 + 查看全部 + 探险家 + 社交达人 + 运动健将 + 新追踪器 + 添加追踪器 + 充电并开机 + 第一步: + 先为追踪器充电,红灯亮起 + 拔掉充电线后绿灯闪烁3次——设备已启动 + 第二步: + 搜索追踪器 + 将手机靠近追踪器 + 确认6字符ID与追踪器背面一致 + 配对追踪器 + ID号 + + 在追踪器背面找到6字符ID + 输入ID配对 + 追踪器ID错误,请核对后重试 + 设备已被绑定,请核对后重试 + 添加追踪器 + 配对成功 + 选择套餐 + 权益 + 月付 + $%s + /月 + /年 + $%s/月 + $%s/年 + 热门 + 确认您的套餐 + 设备编号 + 条款与条件 + 自动续订 + 关于自动续订 + 您的年度订阅将在每期结束时自动续订,以确保服务不间断。 + 摘要 + 税费 + 含税总计 + 支付方式 + 选择卡片 + 未找到卡片 + 添加新卡片 + 主要支付方式 + 立即支付 + 即将跳转至PayPal + 添加支付方式 + 保存新卡片 + 支付成功 + 有效期至 + 月/年 (MM/YY) + 安全码 (CVC) + 接受的支付卡 + 设为主要卡片 + 持卡人姓名 + 权限 + 跳过 + 启用 + 授予权限以提升体验。 + 订阅详情 + 追踪器ID + 套餐: + (按每年 $%s 续订) + (续订价 + /年) + 续订日期: + 添加居住地可提升电池续航并提供额外安全保障 + 建立省电区域 + 常住地点可省电并优化定位。 + 这是您宠物的家吗? + 常住地点 + 选择Wi-Fi网络,无需密码。 + 稍后 + 您宠物的计量单位是? + 您宠物的身高 + 身长 + 您宠物的体重 + 通知设置 + 进入或离开安全区域/禁入区域 + 追踪器状态 + 订阅/低电量警告/关机 + 活动/睡眠提醒/目标达成 + WiFi区域 + 关于WiFi区域 + 在您宠物经常停留的可信地点(如家中)选择一个网络。一旦进入覆盖范围,您的追踪器将识别此网络为WiFi区域。 + 办公室 + 只要追踪器检测到您的爱宠安全处于WiFi区域内,就会关闭实时追踪、指示灯和声音以延长电池续航。 + 省电模式 + 在蓝牙范围内?可使用雷达精确定位附近追踪器。实时追踪也将可用,但使用它会结束省电模式。 + 正在追踪您的宠物 + WiFi区域设置 + 输入您的问题或建议 + 请留下您的邮箱,我们能更快帮助您 + 输入您的电子邮箱地址 + 选择文件 + 关机 + 您的宠物类型是? + 您的宠物名字是? + 当前LED状态 + 已离开安全区域 + 处于WiFi区域内 + 已离开WiFi区域 + 处于禁入区域 + 信号强 + WiFi区域 + %s英尺 (ft) + %s英里/小时 (mph) + 停用 + 请使用有效的邮箱地址。 + 请输入全名。 + 请使用有效的密码。 + 请选择地区。 + 发送至ABBIDOT + 输入的密码不一致! + 尚无成员 + +邀请 + 邀请成员 + 绑定其他宠物 + 解绑 %s + 绑定其他追踪器 + 这将从您的账户中永久删除所有 %s 数据 + 这将从您的账户中永久删除所有 + 数据 + 您确定要将 %s 从追踪器%s上取消绑定吗? + + 您确定要删除家庭成员%s + 实时追踪时长 + %s 分钟 (Min) + 关于实时追踪时长 + 实时模式可帮助寻找宠物,每2~3秒更新一次位置,但会消耗更多电量。默认追踪仍会提供常规更新。 + 实时追踪将在设定的时长结束时停止,但您也可以提前停止以节省电量。 + 请尽量缩短时长 + 蓝牙 + •\t 确保活动数据同步准确的必要条件。 + GPS 位置 + •\t 在地图上显示您自己的位置 \n•\t 同步活动数据所必需 \n•\t 提升追踪器精度 + •\t 允许接收通知以掌握重要信息,例如您的爱宠是否离开了安全区域,或者您是否该考虑为设备充电了。 + 接收通知 + 开启 + 设备未激活。请订阅以激活。 + 激活成功! + 注册成功! + 小时 (hours) + 正在搜索… + 请保持追踪器开机并在附近以便扫描。 + 电量低于%s%% + 户外 + 室内 + 7日均值 + 停止自动续订 + 自动续订 + 您创建的围栏过多 + 实时追踪已结束。实时追踪时长可通过“实时追踪时长”设置。 + GPS信号弱 + 无网络信号 + 追踪器已关机 + 正在启动实时追踪… + 正在连接网络 + GPS定位中 + 正在启动 %s-分钟实时追踪 + 正在启动 + -分钟实时追踪 + 超时,请重试 + 关闭 + 关于实时追踪 + 实时追踪将在一小时后停止,但您也可以提前停止以节省电量。 + 休息中 + 休息时长 + 休息时间 + 无信号 + WiFi + 选择计量单位 + 公制 (厘米, 千克) + 英制 (英寸, 磅) + 公里/小时 (km/h) + 英里/小时 (mph) + 米 (m) + 英尺 (ft) + 围栏警报 + 我的宠物围栏 + 您的反馈帮助我们做得更好。 + 输入消息 (Type Message) + 追踪器处于睡眠模式 + 请继续验证您的邮箱 + 验证码有效期为 %ss + 验证码有效期为 + 分钟 + 创建密码 + 邮箱更改成功! + 更改邮箱 + 输入新邮箱 + 设备激活费 + 请保持应用在后台运行以接收追踪器状态通知。 + 提醒 + 昨天 + 开机/关机 + 为追踪器充满电,然后拔掉充电线。当绿灯闪烁时,追踪器即已开机。 + 选择并完成订阅,然后点击在应用中激活追踪器。激活后,追踪器即可用于GPS追踪和移动网络连接。 + 激活追踪器 + (GPS和移动网络状态可能因环境而异。在给宠物佩戴前,请检查您周围的不同位置以评估GPS和网络状态。) + 检查设备状态 + 当追踪器检测到15分钟无运动时,它会关闭GPS和移动网络,自动进入睡眠模式。一旦检测到运动,追踪器将激活并恢复正常运行。 + 将追踪器连接到家庭WiFi(无需密码)。在WiFi区域内,GPS激活将被禁用。它每10-60分钟连接一次网络,功耗极低。 + 当您的追踪器在WiFi区域外时,它会激活GPS并每1/3/5/10/15分钟连接一次网络。 + 选择‘宠物’ > ‘追踪器’。点击‘GPS更新’选择1/3/5/10/15分钟。 + (更短的网络连接间隔会更快耗尽追踪器电量。默认情况下,网络每15分钟重新连接一次。) + 当实时模式激活时,追踪器进入快速定位和联网状态,通常在15-20秒内完成。此模式耗电最快,因此最好仅在紧急情况下使用。 + 追踪器电池续航 + 围栏设置:选择“宠物” > “我的宠物围栏”。点击“管理“> ”+ 添加新围栏”为安全区域或禁入区域创建或编辑围栏。 + 定义边界:在地图上选择一个位置,调整围栏的半径或形状。 + 保存设置:保存以激活地理围栏。 + 开启通知:前往“账户” > "设置"。点击"通知"并开启。 + 创建我的宠物围栏 + 追踪器处于睡眠模式 + (超低功耗) + 追踪器处于WiFi区域内 + 追踪器在WiFi区域外 + (低功耗) + (标准功耗) + (最高功耗) + 选择合适的GPS更新间隔 + 追踪器处于实时追踪模式 + 萌新驾到 (Fresh Paws) + 一日守护者 (Day 1 Devotee) + 探索新秀 (Newbie Navigator) + 毛茸伙伴 (Furry Friend) + 潜力萌友 (Pawtential Pal) + 满月伙伴 (Month Mate) + 摇尾探险家 (Tailwag Explorer) + 超棒拍档 (Pawsome Partner) + 一年抱抱族 (1 Year Hugger) + 喵不可言 (Purrfect Partner - 适用于猫) / 汪星挚友 (适用于狗) + 两年同伴 (2 Year Companion) + 忠实好友 (Faithful Friend) + 三载和谐 (3 Year Harmony) + 资深毛友 (Furr-timer Friend) + 欢迎加入社区!开启您的萌爪之旅,领取首枚徽章。 + 您刚刚启程!首日的投入已被记录在册。 + 登录一天即可获得此徽章! + 您正在熟悉环境。持续探索,达成新的里程碑! + 连续探索三天,达成此里程碑! + 一周过去,您已成为我们毛茸大家庭的一员! + 保持活跃一周,即可获得“毛茸伙伴”徽章! + 您展现出巨大潜力!继续保持。 + 持续互动十五天,解锁此徽章! + 一个月了,您已成为我们社区的宝贵成员! + 加入社区满一个月,即可达成此徽章! + 三个月的探索与冒险——做得好! + 与我们共同探索三个月,赢得“摇尾探险家”徽章! + 六个月的超棒合作。感谢您的坚持! + 相伴六个月,即可获得“超棒拍档”徽章! + 整整一年的奉献!愿未来更多精彩! + 与我们相伴一年,达成此里程碑! + 十八个月依然出色。您真是喵不可言/汪星挚友! + 持续互动十八个月,赢得“喵不可言/汪星挚友”徽章! + 与我们共度两年——衷心感谢您的忠诚! + 与我们共度两年,即可获得“两年同伴”徽章! + 三十个月的忠实陪伴。您是珍贵的朋友! + 成为忠实朋友三十个月,赢得此徽章! + 和谐共处三载。感谢一路相伴! + 与我们和谐共处三年,将赢得此徽章! + 五年的美好情谊。您的坚持令人动容! + 坚持五年,将解锁“资深毛友”徽章! + 如何获取? + 邀请家人 + 活跃时长: + 您的朋友 %s 邀请您成为 %s 的家人。 + 您的朋友 + 邀请您成为 + 的家人。 + 有效期至: + 过期于: + + + + 自动续费 + 订阅订单 + 订单号: + 请输入您的退款原因 + 申请退款 + 退款 + 等待处理 + 退款中 + 已退款 + 续费:$%s/年,于%s + 续费: + /年,于 + 续费:$%s/月,于%s + 续费: + /月,于 + 年度护理保障 + $12.90/年,安心无忧,每年可轻松更换1台设备。 + 换新保障 + 输入折扣码 + 验证 + 年度护理 + 年度护理: + 蓝牙连接失败 + LED灯设置 + 当追踪器在手机旁时,建议通过蓝牙连接追踪器并调整LED设置。更改将立即生效。 + 蓝牙设置 + 当追踪器不在手机旁时,您可以通过网络远程设置LED灯。在此情况下,设置通常需要几分钟才能生效。 + 抱歉,不允许重复的WiFi + 网络设置 + 设置准确体重以提高卡路里计算精度。 + 开启自动续费 + 关于体重 + 订单 + 设置高级版 + 基础版 + 高级版 + 已拒绝 + 已过期 + 升级您的套餐 + 剩余价值 + /%s月 + (此后按每年 $%s 续订) + (此后按 + /年续费) + (此后按每月 $%s 续订) + (此后按 + /月续费) + 追踪并训练宠物,以防丢失或危险。 + /%s 年 + 高级设置 + 删除账户 + 更改名称 + 您的电子邮箱地址: + 立即更改 + 将发送验证码至 %s + 升级至高级版,解锁独家功能! + 立即升级 + 活动时间 + 失败 + 检测到新固件版本:%s + 未设置 + 完成 + 是否保存? + %s 分钟 + 连接 + 跳过密码 + 正在连接 + 密码错误,请重试。 + 您的查看权限已结束。 + “%s”邀请您查看“%s” + 只要追踪器检测到您的爱宠安全处于省电区域内,就会关闭实时追踪、指示灯和声音以延长电池续航。 + 关于追踪时长 + 您的订阅已过期,请续费 + %s 围栏设置 + %s 在附近 + 仅在您要退回或赠送追踪器时,才将其从您的账户中删除。 + 删除追踪器 + 将被删除 + LED灯设置 + 预留 + 订阅套餐 + 将手机靠近追踪器%s + 正在连接追踪器 + 包含年度护理,不可调整 + 删除成功 + 编辑付款方式 + 确定删除此卡片? + 账户已存在。立即登录? + 所选日期超出范围 + 下次请摇一摇唤醒您的追踪器。 + 开启追踪器,打开应用,前往仪表盘“宠物“>“追踪器“,然后点击“网络“、“GPS“、“电量“、“WiFi区域“、“蓝牙“或“指示灯“以检查连接、信号或电量状态。 + 追踪器正在充电中 + 已订阅 + 发送 + 首次开机请连接电源线。之后可通过摇动开机。 + 无信号。请检查宠物是否在室内。 + 卡号已存在。是否覆盖? + 请输入密码。 + 请输入邮箱。 + 当LTE信号弱时,Wi-Fi可确保追踪器保持连接 + 更新于:%s + + 低至 + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 0000000..409a6bf --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,31 @@ + + + + @string/txt_january + @string/txt_february + @string/txt_march + @string/txt_april + @string/txt_may + @string/txt_june + @string/txt_july + @string/txt_august + @string/txt_september + @string/txt_october + @string/txt_november + @string/txt_december + + + @string/txt_january_abbr + @string/txt_february_abbr + @string/txt_march_abbr + @string/txt_april_abbr + @string/txt_may_abbr + @string/txt_june_abbr + @string/txt_july_abbr + @string/txt_august_abbr + @string/txt_september_abbr + @string/txt_october_abbr + @string/txt_november_abbr + @string/txt_december_abbr + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..003910f --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,373 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + + + #00000000 + + + #FFFFFFFF + #F2FFFFFF + #E6FFFFFF + #D9FFFFFF + #CCFFFFFF + #BFFFFFFF + #B3FFFFFF + #A6FFFFFF + #99FFFFFF + #8CFFFFFF + #80FFFFFF + #73FFFFFF + #66FFFFFF + #59FFFFFF + #4DFFFFFF + #40FFFFFF + #33FFFFFF + #26FFFFFF + #1AFFFFFF + #0DFFFFFF + + + #FF000000 + #F2000000 + #E6000000 + #D9000000 + #CC000000 + #B000000F + #B3000000 + #A6000000 + #99000000 + #8C000000 + #80000000 + #73000000 + #66000000 + #59000000 + #4D000000 + #40000000 + #33000000 + #26000000 + #1A000000 + #0D000000 + + + #FF808080 + + #FFFF0000 + + #FFFFD700 + + #FFFFFF00 + + #FF008000 + + #FF0000FF + + #FF800080 + + #FFFFC0CB + + #FFFFA500 + + #FF5722 + #FF9800 + #FFC107 + #3FB830 + #00BAED + #263238 + #455A64 + #ECEFF1 + + #eeeeee + #ffffff + + #E44909 + #eb613b + #FF018786 + + + #fd9276 + + #2fbeff + #d2d2d2 + #a3a3a3 + #000000 + + #FF6345 + #1a2323 + #ffffff + #8b9797 + #000000 + #1a2323 + #0a0a0a + #1f1f1f + #212121 + #524279 + #333333 + #ff0000 + #10AFFF + + + #00ffff + #0000ff + #ff00ff + + + + + #60000000 + #B0000000 + #90FFFFFF + #C0FFFF00 + #0F0 + #00FF00 + #FEB748 + + + #f2f2f2 + #5b6b75 + + + + #f5f5f5 + #e8e8e8 + #ababb1 + #00b0ed + #0abd6f + #fb8b1e + #ff3e47 + + + #dcdcdc + #32b374 + #4aa5de + #524279 + + #80999999 + #1a1a2323 + + + #33B5E5 + #cccccc + + + #ffffff + #10000000 + #ff359ff2 + #00000000 + #DE000000 + + #702216 + #323232 + #8B8B8B + #237EAD + + + #4d4d4d + #e6e6e6 + + #32b374 + #7d7d7d + #CFCFCF + #e75a85 + #f8f8f8 + #e5e5e5 + + #666666 + #262b45 + + #364cbd + #bfbfbf + + #2f2b38 + #4e4e4e + #85899a + #262b45 + #545768 + #fec86e + + #f3f3f3 + #87AADC + + #d6d6d6 + #90bdda + #8879a8 + + #312952 + + #bdbcc1 + #1ea7de + #47b673 + #f08726 + #e84148 + + + #803cbcb7 + #800de6e8 + + #3ea2ff + #c4f0f1 + #191b26 + + + #0e86fe + #205c82 + #768ab1 + + #f5f5f5 + #25abff + + #12191d + #D3FF53 + #a0D3FF53 + #464747 + #727272 + #50585D + #566567 + #555555 + #1AA1ED + #0FCFDE + #00DCA9 + #FF6279 + #F6C82D + #37BA55 + #F5F8FB + #898989 + #EDEDED + #3B3B3B + #FFFF00 + #FF7F00 + #FF6D2E + #7F7F7F + #A5A5A5 + #808080 + #363636 + #0F1719 + #525252 + #8FEBFF + #F4F4F4 + #E5E5E5 + #6F6F6F + #849198 + #6C6C6C + #F6F6F6 + #9B9B9B + #A0A0A0 + #C2C2C2 + #7A7A7A + #420090FF + #009800 + #ED3636 + #4C4C4C + #FA1934 + #FF333D + #737373 + #EFEFEF + #1188FF + #fbe191 + #eed280 + #88FFFFFF + + #578fff + #393a3f + #00AA00 + #00ABEB + #595959 + #FE3A3A + #02494C + #B9FF4E + #2aB9FF4E + #26B9FF4E + #575D5E + #dddddd + #00484F + #EEEFEF + #F8F9F9 + #868A8B + #ACB3B6 + #FFF2E8 + #154C4B4B + #4C4B4B1A + #CFD1D1 + #004751 + #E0E3E5 + #FF6B00 + #124446 + #030F21 + #C1C7CB + #3AB7FF + #A9B2B8 + #E2EECC + #313B3E + #C2E1E3 + #638F74 + #4D3C3C43 + #567B7C + #1A262A + #364B52 + #1E2C30 + #FF3A0F + #FF2400 + #3aFF2400 + #1f4c4b4b + #F8FFE4 + #E7F9EF + #ECF3FB + #0CC25F + #FF7527 + #F1FFC9 + #9DFF07 + #E6F5F0 + #606C87 + #001239 + #1F3462 + #ADB4C1 + #F4FAFA + #C2E5E2 + #6b9898 + #3f00c400 + #6400c400 + #7f00c400 + #b400c400 + #00bf00 + #849199 + #FFC439 + #D9D9D9 + #98A2B3 + #667185 + #FFDBDB + #FF7511 + #FFD7D1 + #E3FAFF + #FFE5C6 + #D4EBFF + #EAFFBC + #DEEEF7 + #FFDFFA + #FFFB9C + #C2FFB9 + #B9FFDD + #D1E6FF + #F9D1FF + #DAE1E8 + #2DE3B5 + #0066FF + #2600D1FF + #455053 + #FFE9E9 + #00878C + #92C545 + #2999C5 + #EA7B2B + #101928 + #FF3D00 + #F6FEFF + #4d02494C + #E6E7EE + #FFE0E0 + #EEEFEF + #64000000 + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..e545693 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,101 @@ + + + + + 1px + + + 999dp + + + 15dp + + + 10dp + 0.5dp + 24dp + 22dp + 10dp + 18dp + 20dp + 16dp + 2dp + 3dp + 4dp + 6dp + 7dp + 8dp + 1dp + 30dp + 50dp + 53dp + 56dp + 58dp + 60dp + 64dp + 66dp + 68dp + 70dp + 72dp + 78dp + 80dp + 90dp + 32dp + 34dp + 100dp + 180dp + 190dp + 200dp + 220dp + 255dp + 260dp + 270dp + 280dp + 290dp + 300dp + 350dp + 28dp + 26dp + 110dp + 120dp + 130dp + 140dp + 150dp + 160dp + 170dp + 12dp + 13dp + 14dp + 38dp + 36dp + 40dp + 47.9dp + 47dp + 45dp + 42dp + 44dp + + + 8sp + 10sp + 11sp + 12sp + 14sp + 16sp + 18sp + 20sp + 22sp + 24sp + 25sp + 26sp + 28sp + 36sp + 34sp + 32sp + 49sp + 40sp + 18sp + 16dp + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml new file mode 100644 index 0000000..9a6d77c --- /dev/null +++ b/app/src/main/res/values/ids.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/qmui_themes.xml b/app/src/main/res/values/qmui_themes.xml new file mode 100644 index 0000000..02e792d --- /dev/null +++ b/app/src/main/res/values/qmui_themes.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/file_paths.xml b/app/src/main/res/xml/file_paths.xml new file mode 100644 index 0000000..6b8ade3 --- /dev/null +++ b/app/src/main/res/xml/file_paths.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/network_security_config.xml b/app/src/main/res/xml/network_security_config.xml new file mode 100644 index 0000000..9be597a --- /dev/null +++ b/app/src/main/res/xml/network_security_config.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/test/java/com/abbidot/tracker/ExampleUnitTest.kt b/app/src/test/java/com/abbidot/tracker/ExampleUnitTest.kt new file mode 100644 index 0000000..7f5acdf --- /dev/null +++ b/app/src/test/java/com/abbidot/tracker/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.abbidot.tracker + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/baselibrary/.gitignore b/baselibrary/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/baselibrary/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/baselibrary/build.gradle b/baselibrary/build.gradle new file mode 100644 index 0000000..604f243 --- /dev/null +++ b/baselibrary/build.gradle @@ -0,0 +1,62 @@ +plugins { + id 'com.android.library' + id 'kotlin-android' +} + +android { + compileSdkVersion 33 +// buildToolsVersion "30.0.3" + + defaultConfig { + minSdkVersion 21 + targetSdkVersion 33 + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + consumerProguardFiles "consumer-rules.pro" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + lint { + abortOnError false + disable 'RestrictedApi' + } + namespace 'com.abbidot.baselibrary' +} + +dependencies { + + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + + // Android官方库 + api "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + api 'androidx.core:core-ktx:1.13.1' + api 'androidx.appcompat:appcompat:1.7.0' + api 'com.google.android.material:material:1.10.0' + + //SharedPreferences的替代https://github.com/Tencent/MMKV + implementation 'com.tencent:mmkv-static:2.2.2' + + // Retrofit2 + api 'com.squareup.retrofit2:retrofit:3.0.0' +// implementation("com.squareup.retrofit2:adapter-rxjava2:${versions.retrofit2}") + implementation 'com.squareup.retrofit2:converter-gson:3.0.0' +// implementation("com.squareup.retrofit2:converter-scalars:${versions.retrofit2}") + + //okhttp3 + implementation 'com.squareup.okhttp3:okhttp:5.1.0' + implementation 'com.squareup.okhttp3:logging-interceptor:5.1.0' +} \ No newline at end of file diff --git a/baselibrary/consumer-rules.pro b/baselibrary/consumer-rules.pro new file mode 100644 index 0000000..e69de29 diff --git a/baselibrary/proguard-rules.pro b/baselibrary/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/baselibrary/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/baselibrary/src/androidTest/java/com/abbidot/baselibrary/ExampleInstrumentedTest.kt b/baselibrary/src/androidTest/java/com/abbidot/baselibrary/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..19afc98 --- /dev/null +++ b/baselibrary/src/androidTest/java/com/abbidot/baselibrary/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.abbidot.baselibrary + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.abbidot.baselibrary.test", appContext.packageName) + } +} \ No newline at end of file diff --git a/baselibrary/src/main/AndroidManifest.xml b/baselibrary/src/main/AndroidManifest.xml new file mode 100644 index 0000000..a5918e6 --- /dev/null +++ b/baselibrary/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/ConState.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/ConState.kt new file mode 100644 index 0000000..5880a2a --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/ConState.kt @@ -0,0 +1,45 @@ +package com.abbidot.baselibrary.constant + +import androidx.annotation.IntDef + +/** + *Created by .yzq on 2021/11/8/008. + * @link + * @description:设备连接状态 + */ +@IntDef( + ConState.DISCONNECTED, + ConState.CONNECTED, + ConState.CONNECTING, + ConState.CONNECTING_MAC, + ConState.CONNECTION_FAIL, + ConState.BLUETOOTH_ON, + ConState.BLUETOOTH_OFF, + ConState.DEVICE_NOT_FOUND +) +@Retention(AnnotationRetention.SOURCE) +annotation class ConState { + companion object { + //连接断开 + const val DISCONNECTED = 0 + + //已连接 + const val CONNECTED = 1 + + //连接中 + const val CONNECTING = 2 + + //连接指定Mac设备 + const val CONNECTING_MAC = 3 + + //没有找到设备 + const val DEVICE_NOT_FOUND = 4 + + //未连接,连接失败 + const val CONNECTION_FAIL = 5 + + //打开蓝牙 + const val BLUETOOTH_ON = 6 + const val BLUETOOTH_OFF = 7 + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/DeviceType.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/DeviceType.kt new file mode 100644 index 0000000..1813294 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/DeviceType.kt @@ -0,0 +1,17 @@ +package com.abbidot.baselibrary.constant + +import androidx.annotation.IntDef + +/** + *Created by .yzq on 2021/11/9/009. + * @link + * @description:设备类型 + */ +@IntDef(DeviceType.TYPE_ONE, DeviceType.TYPE_TWO) +@Retention(AnnotationRetention.SOURCE) +annotation class DeviceType { + companion object { + const val TYPE_ONE = 1 + const val TYPE_TWO = 2 + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/EventName.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/EventName.kt new file mode 100644 index 0000000..1570af3 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/EventName.kt @@ -0,0 +1,108 @@ +package com.abbidot.baselibrary.constant + +import androidx.annotation.StringDef + +/** + * EventBus发送消息名 + */ +@StringDef( + EventName.RefreshHomeList, + EventName.Test, + EventName.FinishActivity, + EventName.UpdateUserInfo, + EventName.UpdateData, + EventName.LogOut, + EventName.PayBack, + EventName.HistoryDataSnapshot, + EventName.ActiveGoal, + EventName.UpdateReportInterval, + EventName.DeviceReceiveData, + EventName.WXLoginResult, + EventName.ChangePassword, + EventName.ConnectDeviceState, + EventName.RefreshFences, + EventName.DeleteFences, + EventName.RefreshPet, + EventName.DeletePet, + EventName.RefreshWiFiZone, + EventName.RefreshDevice, + EventName.DeleteWiFiZone, + EventName.DeleteFamily, + EventName.RefreshFamily, + EventName.DeleteDevice, + EventName.GPSSwitchState, + EventName.RefreshPackage, + EventName.LiveAutoEnd, + EventName.LiveOpenTimeOut, + EventName.SetGoal, + EventName.RefreshMessage, + EventName.BluetoothSwitch, + EventName.BleReport, + EventName.BleReportData, + EventName.LogReport, + EventName.ActionConDeviceState +) +@Retention(AnnotationRetention.SOURCE) +annotation class EventName { + companion object { + const val RefreshHomeList = "refreshHomeList" + const val Test = "test" + const val FinishActivity = "finishActivity" + const val UpdateUserInfo = "updateUserInfo" + const val UpdateData = "updateData" + const val RefreshFences = "refreshFences" + const val RefreshMessage = "refreshMessage" + const val DeleteFences = "deleteFences" + const val RefreshDevice = "refreshDevice" + const val DeleteDevice = "deleteDevice" + const val DeletePet = "deletePet" + const val RefreshPet = "refreshPet" + const val RefreshPackage = "refreshPackage" + const val SetGoal = "setGoal" + const val DeleteFamily = "deleteFamily" + const val RefreshFamily = "refreshFamily" + const val RefreshWiFiZone = "refreshWiFiZone" + const val DeleteWiFiZone = "deleteWiFiZone" + const val LogOut = "logOut" + + //支付成功返回 + const val PayBack = "payBack" + const val HistoryDataSnapshot = "historyDataSnapshot" + const val ActiveGoal = "activeGoal" + const val UpdateReportInterval = "updateReportInterval" + + //已找到设备,正在去连接或已经连接上的设备状态 + const val ConnectDeviceState = "connectDeviceState" + + //去搜索查找连接设备,这种操作设备状态 + const val ActionConDeviceState = "actionConDeviceState" + + //蓝牙开关切换 + const val BluetoothSwitch = "bluetoothSwitch" + + //gps开关状态 + const val GPSSwitchState = "gpsSwitchState" + + //直播自动结束 + const val LiveAutoEnd = "liveAutoEnd" + const val LiveOpenTimeOut = "liveOpenTimeOut" + + //接收到设备返回的数据 + const val DeviceReceiveData = "deviceReceiveData" + + //微信登录成功返回 + const val WXLoginResult = "wxLoginResult" + + //修改密码成功返回 + const val ChangePassword = "changePassword" + + //蓝牙上报数据 + const val BleReport = "bleReport" + + //蓝牙上报的数据 + const val BleReportData = "bleReportData" + + //日志上报数据 + const val LogReport = "logReport" + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt new file mode 100644 index 0000000..f6b9920 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/MMKVKey.kt @@ -0,0 +1,99 @@ +package com.abbidot.baselibrary.constant + +import androidx.annotation.StringDef + +/** + * 存储SharedPreferences的key,枚举常量 + */ +@StringDef( + MMKVKey.UserId, + MMKVKey.Email, + MMKVKey.UserName, + MMKVKey.HeadUrl, + MMKVKey.Phone, + MMKVKey.Location, + MMKVKey.Gender, + MMKVKey.BirthdayDate, + MMKVKey.PetSelectPosition, + MMKVKey.Token, + MMKVKey.LineSelectPosition, + MMKVKey.isBindDevice, + MMKVKey.isBindPet, + MMKVKey.DebugIp, + MMKVKey.FirstOpen, + MMKVKey.FirstDeviceId, + MMKVKey.FirstDeviceOutId, + MMKVKey.FirstDeviceMac, + MMKVKey.HeightUnit, + MMKVKey.WeightUnit, + MMKVKey.MeasureUnit, + MMKVKey.ActivityGoal, + MMKVKey.CountryCode, + MMKVKey.MapShowDefaultLat, + MMKVKey.MapShowDefaultLon, + MMKVKey.MealType, + MMKVKey.isExistNewInvite, + MMKVKey.OnlyGoogleMap, + MMKVKey.MapType, + MMKVKey.ShowFence, + MMKVKey.Shared +) +@Retention(AnnotationRetention.SOURCE) +annotation class MMKVKey { + companion object { + // const val SPName = "abbidot_sp" + const val UserId = "userId" + const val Token = "token" + const val UserName = "username" + const val Email = "email" + const val HeadUrl = "imgurl" + const val isBindDevice = "isBindDevice" + const val isBindPet = "isBindPet" + const val isExistNewInvite = "isExistNewInvite" + const val Phone = "phone" + const val ActivityGoal = "activityGoal" + const val Location = "location" + const val Gender = "gender" + const val BirthdayDate = "birthdayDate" + const val CountryCode = "countryCode" + + //首次绑定流程 + const val FirstDeviceId = "firstDeviceId" + const val FirstDeviceOutId = "firstDeviceOutId" + const val FirstDeviceMac = "firstDeviceMac" + const val HeightUnit = "heightUnit" + const val WeightUnit = "weightUnit" + + //1:Imperial 2:Metric + const val MeasureUnit = "measureUnit" + + //保存首页弹窗选中的宠物下标 + const val PetSelectPosition = "petSelectPosition" + + //保存历史记录展示线还是点的类型 + const val LineSelectPosition = "lineSelectPosition" + + //国内外服务器端口切换 + const val DebugIp = "debugIp" + + //只使用谷歌地图 + const val OnlyGoogleMap = "onlyGoogleMap" + const val MapShowDefaultLat = "mapDefaultLat" + const val MapShowDefaultLon = "mapDefaultLon" + + //地图模式类型 + const val MapType = "mapType" + + //map页是否显示围栏 + const val ShowFence = "isShowFence" + + //是首次打开APP + const val FirstOpen = "firstOpen" + + //套餐类型 + const val MealType = "mealType" + + //是否分享的 + const val Shared = "shared" + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/constant/ResultCode.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/ResultCode.kt new file mode 100644 index 0000000..d9a8d14 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/constant/ResultCode.kt @@ -0,0 +1,21 @@ +package com.abbidot.baselibrary.constant + +import androidx.annotation.IntDef + +/** + *Created by .yzq on 2022/3/4/004. + * @link + * @description: + */ +@IntDef(ResultCode.ResultCode_1, ResultCode.ResultCode_2, ResultCode.ResultCode_3, + ResultCode.ResultCode_4, ResultCode.ResultCode_5) +@Retention(AnnotationRetention.SOURCE) +annotation class ResultCode() { + companion object { + const val ResultCode_1 = 1 + const val ResultCode_2 = 2 + const val ResultCode_3 = 3 + const val ResultCode_4 = 4 + const val ResultCode_5 = 5 + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/XEventBus.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/XEventBus.kt new file mode 100644 index 0000000..9e8a79e --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/XEventBus.kt @@ -0,0 +1,50 @@ +package com.abbidot.baselibrary.eventbus + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer +import com.abbidot.baselibrary.constant.EventName +import com.abbidot.baselibrary.eventbus.core.EmptyMessage +import com.abbidot.baselibrary.eventbus.core.EventLiveData + +object XEventBus { + + private val channels = HashMap>() + + private fun with(@EventName eventName: String): EventLiveData { + synchronized(channels) { + if (!channels.containsKey(eventName)) { + channels[eventName] = EventLiveData() + } + return (channels[eventName] as EventLiveData) + } + } + + fun post(@EventName eventName: String, message: T) { + val eventLiveData = with(eventName) + eventLiveData.postValue(message!!) + } + + fun observe(owner: LifecycleOwner, + @EventName eventName: String, + sticky: Boolean = false, + observer: Observer) { + with(eventName).observe(owner, sticky, observer) + } + + fun post(@EventName eventName: String) { + val eventLiveData = with(eventName) + eventLiveData.postValue(EmptyMessage) + } + + /** + * 空Message调用 + */ + fun observe(owner: LifecycleOwner, + @EventName eventName: String, + sticky: Boolean = false, + observer: () -> Unit) { + with(eventName).observe(owner, sticky) { + observer() + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EmptyMessage.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EmptyMessage.kt new file mode 100644 index 0000000..b862b48 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EmptyMessage.kt @@ -0,0 +1,4 @@ +package com.abbidot.baselibrary.eventbus.core + +object EmptyMessage { +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EventLiveData.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EventLiveData.kt new file mode 100644 index 0000000..37a87b5 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EventLiveData.kt @@ -0,0 +1,16 @@ +package com.abbidot.baselibrary.eventbus.core + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.Observer + + +class EventLiveData : MutableLiveData() { + + fun observe(owner: LifecycleOwner, sticky: Boolean, observer: Observer) { + observe(owner, wrapObserver(sticky, observer)) + } + + private fun wrapObserver(sticky: Boolean, observer: Observer): Observer { + return EventObserverWrapper(this, sticky, observer) + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EventObserverWrapper.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EventObserverWrapper.kt new file mode 100644 index 0000000..3b9277c --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/EventObserverWrapper.kt @@ -0,0 +1,30 @@ +package com.abbidot.baselibrary.eventbus.core + +import androidx.lifecycle.Observer +import com.abbidot.baselibrary.util.reflect.ReflectHelper + +class EventObserverWrapper(liveData: EventLiveData, + sticky: Boolean, + private val observerDelegate: Observer) : Observer { + + private var preventNextEvent = false + + companion object { + private const val START_VERSION = -1 + } + + init { + if (!sticky) { + val version = ReflectHelper.of(liveData).getField("mVersion") as? Int ?: START_VERSION + preventNextEvent = version > START_VERSION + } + } + + override fun onChanged(t: T) { + if (preventNextEvent) { + preventNextEvent = false + return + } + observerDelegate.onChanged(t) + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/LiveData.java b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/LiveData.java new file mode 100644 index 0000000..a2a1948 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/LiveData.java @@ -0,0 +1,495 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +package com.abbidot.baselibrary.eventbus.core; + +import androidx.annotation.MainThread; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.arch.core.executor.ArchTaskExecutor; +import androidx.arch.core.internal.SafeIterableMap; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.Observer; +import androidx.lifecycle.ViewModel; + +import java.util.Iterator; +import java.util.Map; + +import static androidx.lifecycle.Lifecycle.State.DESTROYED; +import static androidx.lifecycle.Lifecycle.State.STARTED; + +/** + * LiveData is a data holder class that can be observed within a given lifecycle. + * This means that an {@link Observer} can be added in a pair with a {@link LifecycleOwner}, and + * this observer will be notified about modifications of the wrapped data only if the paired + * LifecycleOwner is in active state. LifecycleOwner is considered as active, if its state is + * {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED}. An observer added via + * {@link #observeForever(Observer)} is considered as always active and thus will be always notified + * about modifications. For those observers, you should manually call + * {@link #removeObserver(Observer)}. + * + *

An observer added with a Lifecycle will be automatically removed if the corresponding + * Lifecycle moves to {@link Lifecycle.State#DESTROYED} state. This is especially useful for + * activities and fragments where they can safely observe LiveData and not worry about leaks: + * they will be instantly unsubscribed when they are destroyed. + * + *

+ * In addition, LiveData has {@link LiveData#onActive()} and {@link LiveData#onInactive()} methods + * to get notified when number of active {@link Observer}s change between 0 and 1. + * This allows LiveData to release any heavy resources when it does not have any Observers that + * are actively observing. + *

+ * This class is designed to hold individual data fields of {@link ViewModel}, + * but can also be used for sharing data between different modules in your application + * in a decoupled fashion. + * + * @param The type of data held by this instance + * @see ViewModel + */ +public abstract class LiveData { + @SuppressWarnings("WeakerAccess") /* synthetic access */ final Object mDataLock = new Object(); + static final int START_VERSION = -1; + @SuppressWarnings( + "WeakerAccess") /* synthetic access */ static final Object NOT_SET = new Object(); + + private SafeIterableMap, ObserverWrapper> mObservers = + new SafeIterableMap<>(); + + // how many observers are in active state + @SuppressWarnings("WeakerAccess") /* synthetic access */ int mActiveCount = 0; + // to handle active/inactive reentry, we guard with this boolean + private boolean mChangingActiveState; + private volatile Object mData; + // when setData is called, we set the pending data and actual data swap happens on the main + // thread + @SuppressWarnings("WeakerAccess") /* synthetic access */ volatile Object mPendingData = NOT_SET; + private int mVersion; + + private boolean mDispatchingValue; + @SuppressWarnings("FieldCanBeLocal") + private boolean mDispatchInvalidated; + private final Runnable mPostValueRunnable = new Runnable() { + @SuppressWarnings("unchecked") + @Override + public void run() { + Object newValue; + synchronized (mDataLock) { + newValue = mPendingData; + mPendingData = NOT_SET; + } + setValue((T) newValue); + } + }; + + /** + * Creates a LiveData initialized with the given {@code value}. + * + * @param value initial value + */ + public LiveData(T value) { + mData = value; + mVersion = START_VERSION + 1; + } + + /** + * Creates a LiveData with no value assigned to it. + */ + public LiveData() { + mData = NOT_SET; + mVersion = START_VERSION; + } + + @SuppressWarnings("unchecked") + private void considerNotify(ObserverWrapper observer) { + /* 修改源码,实现后台收消息功能 + if (!observer.mActive) { + return; + } + // Check latest state b4 dispatch. Maybe it changed state but we didn't get the event yet. + // + // we still first check observer.active to keep it as the entrance for events. So even if + // the observer moved to an active state, if we've not received that event, we better not + // notify for a more predictable notification order. + if (!observer.shouldBeActive()) { + observer.activeStateChanged(false); + return; + } + */ + + if (observer.mLastVersion >= mVersion) { + return; + } + observer.mLastVersion = mVersion; + observer.mObserver.onChanged((T) mData); + } + + @SuppressWarnings("WeakerAccess") /* synthetic access */ + void dispatchingValue(@Nullable ObserverWrapper initiator) { + if (mDispatchingValue) { + mDispatchInvalidated = true; + return; + } + mDispatchingValue = true; + do { + mDispatchInvalidated = false; + if (initiator != null) { + considerNotify(initiator); + initiator = null; + } else { + for (Iterator, ObserverWrapper>> iterator = + mObservers.iteratorWithAdditions(); iterator.hasNext(); ) { + considerNotify(iterator.next().getValue()); + if (mDispatchInvalidated) { + break; + } + } + } + } while (mDispatchInvalidated); + mDispatchingValue = false; + } + + /** + * Adds the given observer to the observers list within the lifespan of the given + * owner. The events are dispatched on the main thread. If LiveData already has data + * set, it will be delivered to the observer. + *

+ * The observer will only receive events if the owner is in {@link Lifecycle.State#STARTED} + * or {@link Lifecycle.State#RESUMED} state (active). + *

+ * If the owner moves to the {@link Lifecycle.State#DESTROYED} state, the observer will + * automatically be removed. + *

+ * When data changes while the {@code owner} is not active, it will not receive any updates. + * If it becomes active again, it will receive the last available data automatically. + *

+ * LiveData keeps a strong reference to the observer and the owner as long as the + * given LifecycleOwner is not destroyed. When it is destroyed, LiveData removes references to + * the observer & the owner. + *

+ * If the given owner is already in {@link Lifecycle.State#DESTROYED} state, LiveData + * ignores the call. + *

+ * If the given owner, observer tuple is already in the list, the call is ignored. + * If the observer is already in the list with another owner, LiveData throws an + * {@link IllegalArgumentException}. + * + * @param owner The LifecycleOwner which controls the observer + * @param observer The observer that will receive the events + */ + @MainThread + public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer) { + assertMainThread("observe"); + if (owner.getLifecycle().getCurrentState() == DESTROYED) { + // ignore + return; + } + LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer); + ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); + if (existing != null && !existing.isAttachedTo(owner)) { + throw new IllegalArgumentException("Cannot add the same observer" + " with different " + + "lifecycles"); + } + if (existing != null) { + return; + } + owner.getLifecycle().addObserver(wrapper); + } + + /** + * Adds the given observer to the observers list. This call is similar to + * {@link LiveData#observe(LifecycleOwner, Observer)} with a LifecycleOwner, which + * is always active. This means that the given observer will receive all events and will never + * be automatically removed. You should manually call {@link #removeObserver(Observer)} to stop + * observing this LiveData. + * While LiveData has one of such observers, it will be considered + * as active. + *

+ * If the observer was already added with an owner to this LiveData, LiveData throws an + * {@link IllegalArgumentException}. + * + * @param observer The observer that will receive the events + */ + @MainThread + public void observeForever(@NonNull Observer observer) { + assertMainThread("observeForever"); + AlwaysActiveObserver wrapper = new AlwaysActiveObserver(observer); + ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper); + if (existing instanceof LiveData.LifecycleBoundObserver) { + throw new IllegalArgumentException("Cannot add the same observer" + " with different " + + "lifecycles"); + } + if (existing != null) { + return; + } + wrapper.activeStateChanged(true); + } + + /** + * Removes the given observer from the observers list. + * + * @param observer The Observer to receive events. + */ + @MainThread + public void removeObserver(@NonNull final Observer observer) { + assertMainThread("removeObserver"); + ObserverWrapper removed = mObservers.remove(observer); + if (removed == null) { + return; + } + removed.detachObserver(); + removed.activeStateChanged(false); + } + + /** + * Removes all observers that are tied to the given {@link LifecycleOwner}. + * + * @param owner The {@code LifecycleOwner} scope for the observers to be removed. + */ + @SuppressWarnings("WeakerAccess") + @MainThread + public void removeObservers(@NonNull final LifecycleOwner owner) { + assertMainThread("removeObservers"); + for (Map.Entry, ObserverWrapper> entry : mObservers) { + if (entry.getValue().isAttachedTo(owner)) { + removeObserver(entry.getKey()); + } + } + } + + /** + * Posts a task to a main thread to set the given value. So if you have a following code + * executed in the main thread: + *

+     * liveData.postValue("a");
+     * liveData.setValue("b");
+     * 
+ * The value "b" would be set at first and later the main thread would override it with + * the value "a". + *

+ * If you called this method multiple times before a main thread executed a posted task, only + * the last value would be dispatched. + * + * @param value The new value + */ + protected void postValue(T value) { + boolean postTask; + synchronized (mDataLock) { + postTask = mPendingData == NOT_SET; + mPendingData = value; + } + if (!postTask) { + return; + } + ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable); + } + + /** + * Sets the value. If there are active observers, the value will be dispatched to them. + *

+ * This method must be called from the main thread. If you need set a value from a background + * thread, you can use {@link #postValue(Object)} + * + * @param value The new value + */ + @MainThread + protected void setValue(T value) { + assertMainThread("setValue"); + mVersion++; + mData = value; + dispatchingValue(null); + } + + /** + * Returns the current value. + * Note that calling this method on a background thread does not guarantee that the latest + * value set will be received. + * + * @return the current value + */ + @SuppressWarnings("unchecked") + @Nullable + public T getValue() { + Object data = mData; + if (data != NOT_SET) { + return (T) data; + } + return null; + } + + int getVersion() { + return mVersion; + } + + /** + * Called when the number of active observers change from 0 to 1. + *

+ * This callback can be used to know that this LiveData is being used thus should be kept + * up to date. + */ + protected void onActive() { + + } + + /** + * Called when the number of active observers change from 1 to 0. + *

+ * This does not mean that there are no observers left, there may still be observers but their + * lifecycle states aren't {@link Lifecycle.State#STARTED} or {@link Lifecycle.State#RESUMED} + * (like an Activity in the back stack). + *

+ * You can check if there are observers via {@link #hasObservers()}. + */ + protected void onInactive() { + + } + + /** + * Returns true if this LiveData has observers. + * + * @return true if this LiveData has observers + */ + @SuppressWarnings("WeakerAccess") + public boolean hasObservers() { + return mObservers.size() > 0; + } + + /** + * Returns true if this LiveData has active observers. + * + * @return true if this LiveData has active observers + */ + @SuppressWarnings("WeakerAccess") + public boolean hasActiveObservers() { + return mActiveCount > 0; + } + + @MainThread + void changeActiveCounter(int change) { + int previousActiveCount = mActiveCount; + mActiveCount += change; + if (mChangingActiveState) { + return; + } + mChangingActiveState = true; + try { + while (previousActiveCount != mActiveCount) { + boolean needToCallActive = previousActiveCount == 0 && mActiveCount > 0; + boolean needToCallInactive = previousActiveCount > 0 && mActiveCount == 0; + previousActiveCount = mActiveCount; + if (needToCallActive) { + onActive(); + } else if (needToCallInactive) { + onInactive(); + } + } + } finally { + mChangingActiveState = false; + } + } + + class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver { + @NonNull + final LifecycleOwner mOwner; + + LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer observer) { + super(observer); + mOwner = owner; + } + + @Override + boolean shouldBeActive() { + return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED); + } + + @Override + public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { + Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState(); + if (currentState == DESTROYED) { + removeObserver(mObserver); + return; + } + Lifecycle.State prevState = null; + while (prevState != currentState) { + prevState = currentState; + activeStateChanged(shouldBeActive()); + currentState = mOwner.getLifecycle().getCurrentState(); + } + } + + @Override + boolean isAttachedTo(LifecycleOwner owner) { + return mOwner == owner; + } + + @Override + void detachObserver() { + mOwner.getLifecycle().removeObserver(this); + } + } + + private abstract class ObserverWrapper { + final Observer mObserver; + boolean mActive; + int mLastVersion = START_VERSION; + + ObserverWrapper(Observer observer) { + mObserver = observer; + } + + abstract boolean shouldBeActive(); + + boolean isAttachedTo(LifecycleOwner owner) { + return false; + } + + void detachObserver() { + } + + void activeStateChanged(boolean newActive) { + if (newActive == mActive) { + return; + } + // immediately set active state, so we'd never dispatch anything to inactive + // owner + mActive = newActive; + changeActiveCounter(mActive ? 1 : -1); + if (mActive) { + dispatchingValue(this); + } + } + } + + private class AlwaysActiveObserver extends ObserverWrapper { + + AlwaysActiveObserver(Observer observer) { + super(observer); + } + + @Override + boolean shouldBeActive() { + return true; + } + } + + static void assertMainThread(String methodName) { + if (!ArchTaskExecutor.getInstance().isMainThread()) { + throw new IllegalStateException("Cannot invoke " + methodName + " on a background" + + " thread"); + } + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/MutableLiveData.java b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/MutableLiveData.java new file mode 100644 index 0000000..6a5f592 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/eventbus/core/MutableLiveData.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * 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 + * + * http://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. + */ + +package com.abbidot.baselibrary.eventbus.core; + +/** + * {@link LiveData} which publicly exposes {@link #setValue(T)} and {@link #postValue(T)} method. + * + * @param The type of data hold by this instance + */ +@SuppressWarnings("WeakerAccess") +public class MutableLiveData extends LiveData { + + /** + * Creates a MutableLiveData initialized with the given {@code value}. + * + * @param value initial value + */ + public MutableLiveData(T value) { + super(value); + } + + /** + * Creates a MutableLiveData with no value assigned to it. + */ + public MutableLiveData() { + super(); + } + + @Override + public void postValue(T value) { + super.postValue(value); + } + + @Override + public void setValue(T value) { + super.setValue(value); + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/list/BaseRecyclerAdapter.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/list/BaseRecyclerAdapter.kt new file mode 100644 index 0000000..f2c4619 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/list/BaseRecyclerAdapter.kt @@ -0,0 +1,247 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * 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. + */ +package com.abbidot.baselibrary.list + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView + +/** + * @author cginechen + * @date 2016-10-19 + * @description 配合DiffUtil使用取消notifyDataSetChanged()方法 + */ +abstract class BaseRecyclerAdapter(ctx: Context, list: MutableList?) : + RecyclerView.Adapter() { + + private var mData: MutableList = mutableListOf() + val mContext: Context + private val mInflater: LayoutInflater + private var mClickListener: OnItemClickListener? = null + private var mLongClickListener: OnItemLongClickListener? = null + + //是否显示没有数据的添加按钮 + var isShowNoDataAddButton = true + + // 是否显示空布局,默认显示 + private var showEmptyView = true + + //重试事件,整个view点击 + private var mAgainClickListener: OnAgainClickListener? = null + + /** + * viewType--分别为item以及空view + */ + private val mViewTypeEmpty = 0 + private val mViewTypeItem = 1 + + init { + list?.apply { + mData.addAll(this.toList()) + } + mContext = ctx + mInflater = LayoutInflater.from(ctx) + } + + open fun setData(list: MutableList?, isNeedNotifyData: Boolean = false) { + clearData() + if (null == list) { + if (mData.size == 0) { + showEmptyView(true) + } else { + showEmptyView(false) + } + } + list?.apply { + mData.addAll(list) + //出现没有数据就显示空布局 + if (mData.size == 0) { + showEmptyView(true) + } else { + showEmptyView(false) + } + if (isNeedNotifyData) { + notifyItemRangeInserted(0, list.size) +// notifyItemRangeChanged(0, list.size) + } + } + } + + private fun clearData() { + notifyItemRangeRemoved(0, mData.size) + mData.clear() + } + + fun getData() = mData + + fun remove(oldList: MutableList, pos: Int): MutableList { + val newList: MutableList = oldList.toMutableList() + newList.removeAt(pos) + return newList + } + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerViewHolder { + val holder: RecyclerViewHolder + //空布局 + if (getEmptyLayoutId(viewType) != 0 && viewType == mViewTypeEmpty) { + holder = RecyclerViewHolder( + mContext, mInflater.inflate(getEmptyLayoutId(viewType), parent, false) + ) + if (null != mAgainClickListener) { + holder.itemView.setOnClickListener { + mAgainClickListener!!.onAgainListener() + } + } + mEmptyViewHolder = holder + } else { + holder = RecyclerViewHolder( + mContext, mInflater.inflate(getItemLayoutId(viewType), parent, false) + ) + if (null != mClickListener) { + holder.itemView.setOnClickListener { + mClickListener!!.onItemClick(holder.itemView, holder.layoutPosition) + } + } + if (null != mLongClickListener) { + holder.itemView.setOnLongClickListener { + mLongClickListener!!.onItemLongClick(holder.itemView, holder.layoutPosition) + true + } + } + } + return holder + } + + override fun onBindViewHolder(holder: RecyclerViewHolder, position: Int) { + if (mData.size > 0) bindData(holder, position, mData[position]) + else noData(holder) + } + + private var mEmptyViewHolder: RecyclerViewHolder? = null + + override fun onBindViewHolder( + holder: RecyclerViewHolder, position: Int, payloads: MutableList + ) { + if (payloads.isEmpty()) { + onBindViewHolder(holder, position) + } else { + partBindData(holder, position, payloads[0] as T) + } + } + + /** + * 判断是否是空布局 + */ + private fun isEmptyPosition(position: Int): Boolean { + val count = mData.size + return position == 0 && showEmptyView && count == 0 + } + + /** + * 设置空布局显示。默认显示 + */ + private fun showEmptyView(isShow: Boolean) { + if (isShow != showEmptyView) { + showEmptyView = isShow + notifyDataSetChanged() + } + } + + fun getItem(pos: Int) = mData[pos] + + //只有空布局默认才有一个item + override fun getItemCount() = + if (mData.size == 0 && getEmptyLayoutId(mViewTypeEmpty) != 0) 1 else mData.size + + override fun getItemViewType(position: Int) = + if (isEmptyPosition(position)) mViewTypeEmpty else mViewTypeItem + + fun add(pos: Int, item: T) { + mData.add(pos, item) + notifyItemInserted(pos) + } + + fun prepend(items: List) { + mData.addAll(0, items) + notifyDataSetChanged() + } + + fun append(items: List) { + mData.addAll(items) + notifyDataSetChanged() + } + + fun delete(pos: Int) { + mData.removeAt(pos) + notifyItemRemoved(pos) + } + + fun showNoDataAddButton(show: Boolean) { + isShowNoDataAddButton = show + notifyDataSetChanged() + } + + /** + * 设置显示空布局 等于0不显示空布局 + */ + abstract fun getEmptyLayoutId(viewType: Int): Int + abstract fun getItemLayoutId(viewType: Int): Int + abstract fun bindData(holder: RecyclerViewHolder?, position: Int, item: T) + + //没有数据,空布局的情况 + open fun noData(holder: RecyclerViewHolder?) { + + } + + //局部刷新数据 + open fun partBindData(holder: RecyclerViewHolder, position: Int, item: T) { + bindData(holder, position, mData[position]) + } + + fun setOnItemClickListener(listener: OnItemClickListener?) { + mClickListener = listener + } + + fun setOnItemLongClickListener(listener: OnItemLongClickListener?) { + mLongClickListener = listener + } + + fun setOnAgainClickListener(listener: OnAgainClickListener?) { + mAgainClickListener = listener + } + + interface OnItemClickListener { + fun onItemClick(itemView: View?, pos: Int) + } + + interface OnItemLongClickListener { + fun onItemLongClick(itemView: View?, pos: Int) + } + + /** + * 点击屏幕重试 接口 + */ + interface OnAgainClickListener { + /** + * 点击屏幕重试 + */ + fun onAgainListener() + } + +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/list/RecyclerViewHolder.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/list/RecyclerViewHolder.kt new file mode 100644 index 0000000..83d4061 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/list/RecyclerViewHolder.kt @@ -0,0 +1,86 @@ +/* + * Tencent is pleased to support the open source community by making QMUI_Android available. + * + * Copyright (C) 2017-2018 THL A29 Limited, a Tencent company. All rights reserved. + * + * Licensed under the MIT License (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://opensource.org/licenses/MIT + * + * 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. + */ +package com.abbidot.baselibrary.list + +import android.content.Context +import android.util.SparseArray +import android.view.View +import android.widget.Button +import android.widget.EditText +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView + +/** + * @author cginechen + * @date 2016-10-19 + */ +class RecyclerViewHolder(ctx: Context?, itemView: View?) : RecyclerView.ViewHolder(itemView!!) { + + private val mViews: SparseArray = SparseArray() + + private fun findViewById(viewId: Int): T { + var view = mViews[viewId] + if (view == null) { + view = itemView.findViewById(viewId) + mViews.put(viewId, view) + } + return view as T + } + + fun getView(viewId: Int): View { + return findViewById(viewId) + } + + fun getTextView(viewId: Int): TextView { + return getView(viewId) as TextView + } + + fun getButton(viewId: Int): Button { + return getView(viewId) as Button + } + + fun getImageView(viewId: Int): ImageView { + return getView(viewId) as ImageView + } + + fun getImageButton(viewId: Int): ImageButton { + return getView(viewId) as ImageButton + } + + fun getEditText(viewId: Int): EditText { + return getView(viewId) as EditText + } + + fun setText(viewId: Int, value: String?): RecyclerViewHolder { + val view = findViewById(viewId) + view.text = value + return this + } + + fun setBackground(viewId: Int, resId: Int): RecyclerViewHolder { + val view = findViewById(viewId) + view.setBackgroundResource(resId) + return this + } + + fun setClickListener(viewId: Int, listener: View.OnClickListener?): RecyclerViewHolder { + val view = findViewById(viewId) + view.setOnClickListener(listener) + return this + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/BaseNetworkApi.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/BaseNetworkApi.kt new file mode 100644 index 0000000..bbff261 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/BaseNetworkApi.kt @@ -0,0 +1,133 @@ +package com.abbidot.baselibrary.network.base + +import com.abbidot.baselibrary.network.exception.ErrorCode +import com.abbidot.baselibrary.network.exception.NetworkErrorCodeException +import com.abbidot.baselibrary.network.exception.NetworkException +import com.abbidot.baselibrary.network.interceptor.CommonRequestInterceptor +import com.abbidot.baselibrary.network.interceptor.CommonResponseInterceptor +import com.abbidot.baselibrary.network.interceptor.DoubleDefault0Adapter +import com.abbidot.baselibrary.network.interceptor.StringNullAdapter +import com.abbidot.baselibrary.util.AppUtils +import com.abbidot.baselibrary.util.LogUtil +import com.google.gson.GsonBuilder +import okhttp3.Interceptor +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.HttpException +import retrofit2.Retrofit +import retrofit2.converter.gson.GsonConverterFactory +import java.lang.reflect.ParameterizedType +import java.util.concurrent.TimeUnit + +/** + * 网络请求封装 + */ +abstract class BaseNetworkApi(private val baseUrl: String) : IService { + + protected val service: I by lazy { + getRetrofit().create(getServiceClass()) + } + + protected open fun getRetrofit(): Retrofit { + //添加gson解析null拦截器 + val gson = GsonBuilder().registerTypeAdapter(String::class.java, StringNullAdapter()) + .registerTypeAdapter(Double::class.java, DoubleDefault0Adapter()).create() + + return Retrofit.Builder().baseUrl(baseUrl).client(getOkHttpClient()) + .addConverterFactory(GsonConverterFactory.create(gson)).build() + } + + private fun getServiceClass(): Class { + val genType = javaClass.genericSuperclass as ParameterizedType + return genType.actualTypeArguments[0] as Class + } + + private fun getOkHttpClient(): OkHttpClient { + val okHttpClient = getCustomOkHttpClient() + if (null != okHttpClient) { + return okHttpClient + } + return defaultOkHttpClient + } + + protected open fun getCustomOkHttpClient(): OkHttpClient? { + return null + } + + protected open fun getCustomInterceptor(): Interceptor? { + return null + } + + protected suspend fun getResult(block: suspend () -> BaseResponse): Result { + for (i in 1..RETRY_COUNT) { + try { + val response = block() + //处理”response.code != OK“情况 + if (response.code != ErrorCode.OK) { + + //处理信用卡支付返回结果 + if (response.code == ErrorCode.ERROR) { + val message = response.message + if (message == "支付失败") { + return Result.success("${ErrorCode.CREDIT_CARD_PAY_FAIL}" as T) + } + } + + + throw NetworkErrorCodeException.of( + response.code, "exception code is ${response.code},response code not 200" + ) + } + //适用于只管请求成功(T=String),不需要处理结果数据 + val data = response.data + if (null == data || data == "") { + + //处理信用卡支付返回结果 + if (response.code == ErrorCode.OK) { + val message = response.message + if (message == "支付成功") { + return Result.success("${ErrorCode.CREDIT_CARD_PAY_SUCCESS}" as T) + } + } + + //throw NetworkException.of(ErrorCode.VALUE_IS_NULL, "response data is null") + return Result.success("${response.code}" as T) + } + return Result.success(response.data) + } catch (throwable: Throwable) { + LogUtil.e("BaseNetworkApi-->Throwable", throwable.message.toString()) + if (throwable is NetworkException) { + return Result.failure(throwable) + } else if (throwable is NetworkErrorCodeException) { + return Result.failure(throwable) + } + if ((throwable is HttpException && throwable.code() == ErrorCode.UNAUTHORIZED)) { + // 这里刷新token,然后重试 + } + } + } + return Result.failure(NetworkException.of(ErrorCode.VALUE_IS_NULL, "unknown")) + } + + companion object { + const val TAG = "BaseNetworkApi" + + //失败重试请求次数 + private const val RETRY_COUNT = 1 + private val defaultOkHttpClient by lazy { + val builder = OkHttpClient.Builder().callTimeout(10L, TimeUnit.SECONDS) + .connectTimeout(10L, TimeUnit.SECONDS).readTimeout(10L, TimeUnit.SECONDS) + .writeTimeout(10L, TimeUnit.SECONDS).retryOnConnectionFailure(true) + //添加拦截器 + builder.addInterceptor(CommonRequestInterceptor()) + builder.addInterceptor(CommonResponseInterceptor()) + if (AppUtils.isDebug()) { + val loggingInterceptor = HttpLoggingInterceptor() + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY) + builder.addInterceptor(loggingInterceptor) + } + + builder.build() + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/BaseResponse.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/BaseResponse.kt new file mode 100644 index 0000000..b64dff1 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/BaseResponse.kt @@ -0,0 +1,11 @@ +package com.abbidot.baselibrary.network.base + +/** + * 网络数据返回基类 + */ +data class BaseResponse( + var code: Int = 0, + val message: String? = null, + val redirect: String? = null, + val data: T? = null +) diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/IService.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/IService.kt new file mode 100644 index 0000000..6a4f8a9 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/base/IService.kt @@ -0,0 +1,4 @@ +package com.abbidot.baselibrary.network.base + +interface IService { +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/ErrorCode.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/ErrorCode.kt new file mode 100644 index 0000000..532f9d3 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/ErrorCode.kt @@ -0,0 +1,79 @@ +package com.abbidot.baselibrary.network.exception + +import androidx.annotation.IntDef + +@IntDef( + ErrorCode.OK, + ErrorCode.UNAUTHORIZED, + ErrorCode.CUSTOM_FIRST, + ErrorCode.VALUE_IS_NULL, + ErrorCode.DEVICE_RENEWAL, + ErrorCode.TOKEN_OVERDUE, + ErrorCode.USER_PASSWORD_ERROR, + ErrorCode.VERIFY_CODE_ERROR, + ErrorCode.VERIFY_CODE_EXPIRE, + ErrorCode.REGISTER_FAIL, + ErrorCode.USER_NO_EXIST, + ErrorCode.USER_HAS_EXIST, + ErrorCode.EMAIL_FORMAT_ERROR, + ErrorCode.NO_SHARE_MYSELF, + ErrorCode.DEVICE_HAS_BOUND, + ErrorCode.DEVICE_HAS_BIND, + ErrorCode.DEVICE_NOT_EXIST +) +@Retention(AnnotationRetention.SOURCE) +annotation class ErrorCode { + companion object { + const val OK = 200 + const val ERROR = 500 + const val UNAUTHORIZED = 401 + const val CUSTOM_FIRST = 600 + const val VALUE_IS_NULL = CUSTOM_FIRST + 1 + + //505-注册成功 507-登录成功 + //设备已过期,请续费 + const val DEVICE_RENEWAL = 501 + + //TOKEN失效或者过期 + const val TOKEN_OVERDUE = 502 + + //503-用户名或密码错误 + const val USER_PASSWORD_ERROR = 503 + + //504-验证码错误 + const val VERIFY_CODE_ERROR = 504 + + //511-验证码已过期 + const val VERIFY_CODE_EXPIRE = 511 + + //506-注册失败 + const val REGISTER_FAIL = 506 + + //508-用户不存在 + const val USER_NO_EXIST = 508 + + //509-用户已存在 + const val USER_HAS_EXIST = 509 + + // 510-邮箱格式不对 + const val EMAIL_FORMAT_ERROR = 510 + + //不能分享给自己 + const val NO_SHARE_MYSELF = 512 + + //513 当前用户已经绑定该设备 + const val DEVICE_HAS_BOUND = 513 + + //514 设备已经存在,已绑定 + const val DEVICE_HAS_BIND = 514 + + //515 设备未存在 + const val DEVICE_NOT_EXIST = 515 + + //信用卡支付成功 + const val CREDIT_CARD_PAY_SUCCESS = 1 + + //信用卡支付失败 + const val CREDIT_CARD_PAY_FAIL = 2 + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/NetworkErrorCodeException.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/NetworkErrorCodeException.kt new file mode 100644 index 0000000..57033f7 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/NetworkErrorCodeException.kt @@ -0,0 +1,17 @@ +package com.abbidot.baselibrary.network.exception + +/** + * 网络错误码返回 + */ +class NetworkErrorCodeException private constructor(val code: Int, message: String) : + RuntimeException(message) { + + override fun toString(): String { + return "$code" + } + + companion object { + fun of(code: Int, message: String) = NetworkErrorCodeException(code, message) + } + +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/NetworkException.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/NetworkException.kt new file mode 100644 index 0000000..1baad21 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/exception/NetworkException.kt @@ -0,0 +1,13 @@ +package com.abbidot.baselibrary.network.exception + +class NetworkException private constructor(val code: Int, message: String) : RuntimeException(message) { + + override fun toString(): String { + return "exception code is $code msg is $message" + } + + companion object { + fun of(code: Int, message: String) = NetworkException(code, message) + } + +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt new file mode 100644 index 0000000..762e3a3 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonRequestInterceptor.kt @@ -0,0 +1,92 @@ +package com.abbidot.baselibrary.network.interceptor + +import android.os.Build +import com.abbidot.baselibrary.constant.MMKVKey +import com.abbidot.baselibrary.util.LogUtil +import com.abbidot.baselibrary.util.MMKVUtil +import com.abbidot.baselibrary.util.Utils +import okhttp3.FormBody +import okhttp3.HttpUrl +import okhttp3.Interceptor +import okhttp3.Response + + +/** + * 开始请求的拦截器 + * 拦截器获取GET/POST请求参数并添加公共请求头 + */ +class CommonRequestInterceptor : Interceptor { + + //https://blog.csdn.net/u014619545/article/details/98634994 +// companion object { +// //根据headers来动态切换BaseUrl +// const val HEADERS_NAME = "urlName" +// const val HEADERS_value = "WXBaseUrl" +// +// //微信BaseUrl +// const val WX_BASE_URL = "https://api.weixin.qq.com/sns/" +// } + + override fun intercept(chain: Interceptor.Chain): Response { + //获取原始的chain + val request = chain.request() + //获取chain的创建者builder + val builder = request.newBuilder() + // 手机型品牌 + builder.addHeader("brand", Build.BRAND) + //手机型号 + builder.addHeader("model", Build.MODEL) + + //请求参数的key+value添加list中 + val paramList = mutableListOf() + // GET方法 + if (request.method == "GET") { + val urlBuilder: HttpUrl.Builder = request.url.newBuilder() + val httpUrl = urlBuilder.build() + // 打印全部get参数 + val paramKeys: Set = httpUrl.queryParameterNames + for (key in paramKeys) { + val value = httpUrl.queryParameter(key) + LogUtil.e("${request.method}请求参数", "$key,$value") + paramList.add("$key$value") + } + + //动态更改BaseUrl + //获取当前的BaseUrl +// val oldBaseUrl = request.url +// //获取头信息的集合 +// val urlNameList: List = request.headers(HEADERS_NAME) +// if (urlNameList.isNotEmpty()) { +// val urlName = urlNameList[0] +// //删除原有配置中的值,就是namesAndValues集合里的值 +// builder.removeHeader(HEADERS_NAME) +// LogUtil.e("HeaderName$urlName") +// } + } else if (request.method == "POST") { + if (request.body is FormBody) { + val formBody = request.body as FormBody + // 打印全部post参数 + for (i in 0 until formBody.size) { + val key = formBody.encodedName(i) + val value = formBody.encodedValue(i) + LogUtil.e("${request.method}请求参数", "${key},${value}") + paramList.add("$key$value") + } + } + } + + val sign = if (paramList.size > 0) { + //按key的字母排序 + paramList.sort() + Utils.get32Md5Value(Utils.SECRET_KEY + paramList.joinToString("")) + } else { + Utils.get32Md5Value(Utils.SECRET_KEY) + } + + val token = MMKVUtil.getString(MMKVKey.Token) + builder.addHeader("token", token) + builder.addHeader("sign", sign) + + return chain.proceed(builder.build()) + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonResponseInterceptor.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonResponseInterceptor.kt new file mode 100644 index 0000000..8c87fc5 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/CommonResponseInterceptor.kt @@ -0,0 +1,21 @@ +package com.abbidot.baselibrary.network.interceptor + +import com.abbidot.baselibrary.network.base.BaseNetworkApi +import com.abbidot.baselibrary.util.LogUtil +import okhttp3.Interceptor +import okhttp3.Response + +/** + * 请求返回后的拦截器 + */ +class CommonResponseInterceptor : Interceptor { + + override fun intercept(chain: Interceptor.Chain): Response { + val startTime = System.currentTimeMillis() + val request = chain.request() + val response = chain.proceed(request) + LogUtil.d(BaseNetworkApi.TAG, + "url=${request.url}, requestTime=${System.currentTimeMillis() - startTime}ms") + return response + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/DoubleDefault0Adapter.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/DoubleDefault0Adapter.kt new file mode 100644 index 0000000..79da31f --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/DoubleDefault0Adapter.kt @@ -0,0 +1,36 @@ +package com.abbidot.baselibrary.network.interceptor + +import android.text.TextUtils +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter + +/** + *Created by .yzq on 2024/9/10/010. + * @link + * @description:处理gson解析服务器Double返回null或"" + */ +class DoubleDefault0Adapter : TypeAdapter() { + override fun write(writer: JsonWriter?, value: Double?) { + if (value == null) { + writer!!.nullValue() + return + } + writer!!.value(value) + } + + override fun read(reader: JsonReader): Double { + if (reader.peek() === JsonToken.NULL) { + reader.nextNull() + //原先是返回Null,这里改为返回空字符串 + return 0.0 + } + val jsonStr = reader.nextString() + return if (jsonStr == "null" || TextUtils.isEmpty(jsonStr)) { + 0.0 + } else { + jsonStr.toDouble() + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/StringNullAdapter.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/StringNullAdapter.kt new file mode 100644 index 0000000..ee6f057 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/network/interceptor/StringNullAdapter.kt @@ -0,0 +1,36 @@ +package com.abbidot.baselibrary.network.interceptor + +import com.google.gson.TypeAdapter +import com.google.gson.stream.JsonReader +import com.google.gson.stream.JsonToken +import com.google.gson.stream.JsonWriter + +/** + *Created by .yzq on 2022/2/18/018. + * @link + * @description:处理gson解析服务器返回null,变成"" + */ +class StringNullAdapter : TypeAdapter() { + + override fun write(writer: JsonWriter?, value: String?) { + if (value == null) { + writer!!.nullValue() + return + } + writer!!.value(value) + } + + override fun read(reader: JsonReader): String { + if (reader.peek() === JsonToken.NULL) { + reader.nextNull() + //原先是返回Null,这里改为返回空字符串 + return "" + } + val jsonStr = reader.nextString() + return if (jsonStr == "null") { + "" + } else { + jsonStr + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt new file mode 100644 index 0000000..44c42ff --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/AppUtils.kt @@ -0,0 +1,121 @@ +package com.abbidot.baselibrary.util + +import android.content.Context +import android.content.pm.ApplicationInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.content.res.Resources +import com.abbidot.baselibrary.constant.MMKVKey +import java.util.Locale + + +/** + *Created by .yzq on 2021/11/6/006. + * @link + * @description: app属性相关工具类 + */ +class AppUtils { + + companion object { + + private var isDebug: Boolean? = null + + /** + * 屏幕密度,系统源码注释不推荐使用 + */ + private val DENSITY = Resources.getSystem().displayMetrics.density + private val SCALED_DENSITY = Resources.getSystem().displayMetrics.scaledDensity + + fun isDebug(): Boolean { + return if (isDebug == null) false else isDebug!! + } + + fun syncIsDebug(context: Context) { + if (isDebug == null) { + isDebug = + context.applicationInfo != null && context.applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0 + } + } + + + /** + * 根据包名判断app是否安装 + */ + fun isAppInstalled(context: Context, packageName: String): Boolean { + val pm = context.packageManager + return try { + pm.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } + + /** + * 把以 dp 为单位的值,转化为以 px 为单位的值 + * + * @param dpValue 以 dp 为单位的值 + * @return px value + */ + fun dpToPx(dpValue: Float) = dpValue * DENSITY + 0.5f + + /** + * 把以 dp 为单位的值,转化为以 px 为单位的值 + * + * @param dpValue 以 dp 为单位的值 + * @return px value + */ + fun dpToPx(dpValue: Int) = (dpValue * DENSITY + 0.5f).toInt() + + /** + * 单位转换: sp -> px + * + * @param sp + * @return + */ + fun spToPx(sp: Int) = (SCALED_DENSITY * sp + 0.5).toInt() + + /** + * 单位转换: sp -> px + * + * @param sp + * @return + */ + fun spToPx(sp: Float) = SCALED_DENSITY * sp + 0.5f + + + const val SWITCH_MAP_TYPE = "Map" + + // const val SWITCH_BASE_URL_TYPE = "BaseUrl" + private const val SWITCH_PAGE_TYPE = "PageUrl" + + /** + * 是否是国内 + */ + fun isChina(type: String = SWITCH_PAGE_TYPE): Boolean { + return false + if (isDebug()) { + if (type == SWITCH_MAP_TYPE && MMKVUtil.getBoolean(MMKVKey.OnlyGoogleMap, false)) { + //只启用谷歌地图 + return false + } + return MMKVUtil.getBoolean(MMKVKey.DebugIp, true) + } + val locale = Locale.getDefault() + val lang = locale.language + "-" + locale.country + if (lang.contains("CN") || lang.contains("zh")) { + return true + } + return false + } + + /** + * 判断是否是深色模式 + */ + fun isDarkThemeEnabled(context: Context): Boolean { + val nightModeFlags = + context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + return nightModeFlags == Configuration.UI_MODE_NIGHT_YES + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/LogUtil.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/util/LogUtil.kt new file mode 100644 index 0000000..fb0997c --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/LogUtil.kt @@ -0,0 +1,75 @@ +package com.abbidot.baselibrary.util + +import android.util.Log +import java.util.* + +/** + *Created by .yzq on 2021/11/3/003. + * @link + * @description: 日志相关工具类 + */ +class LogUtil { + + companion object { + + private const val TAG = "AbbiDot" + + private fun isLoggable(): Boolean { + return AppUtils.isDebug() + } + + fun i(tag: String, fmt: String, vararg args: Any) { + if (isLoggable()) { + Log.i(tag, format(fmt, *args)) + } + } + + fun i(msg: String) { + i(TAG, msg) + } + + fun d(tag: String, fmt: String, vararg args: Any) { + if (isLoggable()) { + Log.d(tag, format(fmt, *args)) + } + } + + fun d(msg: String) { + d(TAG, msg) + } + + fun w(tag: String, fmt: String, vararg args: Any) { + if (isLoggable()) { + Log.w(tag, format(fmt, *args)) + } + } + + fun w(msg: String) { + w(TAG, msg) + } + + fun e(tag: String, fmt: String, vararg args: Any) { + if (isLoggable()) { + Log.e(tag, format(fmt, *args)) + } + } + + fun e(msg: String) { + e(TAG, msg) + } + + fun e(tag: String, t: Throwable, fmt: String, vararg args: Any) { + if (isLoggable()) { + Log.e(tag, format(fmt, *args), t) + } + } + + private fun format(fmt: String, vararg args: Any): String { + return if (args.isEmpty()) { + fmt + } else { + String.format(Locale.getDefault(), fmt, *args) + } + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/MMKVUtil.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/util/MMKVUtil.kt new file mode 100644 index 0000000..6391694 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/MMKVUtil.kt @@ -0,0 +1,98 @@ +package com.abbidot.baselibrary.util + +import android.app.Application +import android.os.Parcelable +import com.abbidot.baselibrary.constant.MMKVKey +import com.tencent.mmkv.MMKV + +/** + *Created by .yzq on 2021/11/6/006. + * @link + * @description:本类为MMKV的封装类,防止代码入侵 + */ +object MMKVUtil { + fun init(application: Application) { + MMKV.initialize(application) + } + + fun putBoolean(@MMKVKey key: String, value: Boolean) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getBoolean(@MMKVKey key: String, defaultValue: Boolean = false): Boolean { + return MMKV.defaultMMKV().decodeBool(key, defaultValue) + } + + fun putString(@MMKVKey key: String, value: String) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getString(@MMKVKey key: String, defaultValue: String = ""): String { + return MMKV.defaultMMKV().decodeString(key, defaultValue)!! + } + + fun putInt(@MMKVKey key: String, value: Int) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getInt(@MMKVKey key: String, defaultValue: Int = 0): Int { + return MMKV.defaultMMKV().decodeInt(key, defaultValue) + } + + fun putFloat(@MMKVKey key: String, value: Float) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getFloat(@MMKVKey key: String, defaultValue: Float = 0F): Float { + return MMKV.defaultMMKV().decodeFloat(key, defaultValue) + } + + fun putLong(@MMKVKey key: String, value: Long) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getLong(@MMKVKey key: String, defaultValue: Long = 0L): Long { + return MMKV.defaultMMKV().decodeLong(key, defaultValue) + } + + fun putDouble(@MMKVKey key: String, value: Double) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getDouble(@MMKVKey key: String, defaultValue: Double = 0.0): Double { + return MMKV.defaultMMKV().decodeDouble(key, defaultValue) + } + + fun putByteArray(@MMKVKey key: String, value: ByteArray) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getByteArray(@MMKVKey key: String, defaultValue: ByteArray = ByteArray(0)): ByteArray { + return MMKV.defaultMMKV().decodeBytes(key, defaultValue)!! + } + + fun putStringSet(@MMKVKey key: String, value: Set) { + MMKV.defaultMMKV().encode(key, value) + } + + fun getStringSet(@MMKVKey key: String, + defaultValue: Set = mutableSetOf()): Set { + return MMKV.defaultMMKV().decodeStringSet(key, defaultValue)!! + } + + fun putParcelable(@MMKVKey key: String, value: Parcelable) { + MMKV.defaultMMKV().encode(key, value) + } + + //清空所有sp + fun clearAll() { + //正式测试标识不清空 + val debugIp = getBoolean(MMKVKey.DebugIp, true) + MMKV.defaultMMKV().clearAll() + putBoolean(MMKVKey.DebugIp, debugIp) + } + + inline fun getParcelable(@MMKVKey key: String): T? { + return MMKV.defaultMMKV().decodeParcelable(key, T::class.java) + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/Utils.kt b/baselibrary/src/main/java/com/abbidot/baselibrary/util/Utils.kt new file mode 100644 index 0000000..8eff28e --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/Utils.kt @@ -0,0 +1,378 @@ +package com.abbidot.baselibrary.util + +import android.os.Build +import android.text.TextUtils +import java.security.MessageDigest +import java.security.NoSuchAlgorithmException +import java.text.SimpleDateFormat +import java.time.LocalDate +import java.time.Period +import java.util.Calendar +import java.util.Date +import java.util.Locale +import java.util.regex.Pattern +import kotlin.math.abs +import kotlin.math.atan2 +import kotlin.math.cos +import kotlin.math.roundToInt +import kotlin.math.sin + + +/** + *Created by .yzq on 2022/1/19/019. + * @link + * @description:工具类 + */ +class Utils { + + companion object { + + //HTTP请求加密key + var SECRET_KEY = "" + const val DATE_FORMAT_PATTERN_CN = "yyyy-MM-dd" + const val DATE_FORMAT_PATTERN_CN2 = "yyyy-MM-dd HH:mm:ss" + const val DATE_FORMAT_PATTERN_CN3 = "yyyy/MM/dd HH:mm:ss" + const val DATE_FORMAT_PATTERN_EN1 = "MMM d, yyyy | HH:mm" + const val DATE_FORMAT_PATTERN_EN2 = "d MMM yyyy hh:mm a" + const val DATE_FORMAT_PATTERN_EN3 = "MMM d, yyyy" + const val DATE_FORMAT_PATTERN_EN4 = "MMM d yyyy" + const val DATE_FORMAT_PATTERN_EN5 = "d/M" + const val DATE_FORMAT_PATTERN_EN6 = "dd/MM/yyyy HH:mm" + const val DATE_FORMAT_PATTERN_EN7 = "dd/MM/yyyy" + const val DATE_FORMAT_PATTERN_EN8 = "hh:mm a" + const val DATE_FORMAT_PATTERN_EN9 = "MMM d, yyyy hh:mm a" + const val DATE_FORMAT_PATTERN_EN10 = "dd/MM/yyyy HH:mm:ss" + + //返回星期几 简写 + const val WEEK_FORMAT_PATTERN = "E" + + /** + * 时间戳转指定格式时间 + * + * @param time 13位时间戳 + * @return 时间 + */ + fun formatTime(time: Long, format: String): String { + val sdf = SimpleDateFormat(format, Locale.getDefault()) + return if (time < 1000000000000) { + sdf.format(Date(time * 1000)) + } else { + sdf.format(Date(time)) + } + } + + /** + * 获取当前时间 + * @param format 返回时间格式 + */ + fun getCurrentTime(format: String): String { + val date = Calendar.getInstance().time + val sdf = SimpleDateFormat(format, Locale.getDefault()) + return sdf.format(date) + } + + /** + * 把字符串转成日期 + * @param parseFormat 字符串是什么格式的日期就传什么格式 + * @param resultFormat 转换后的格式 + */ + fun stringToDate( + dateString: String, + parseFormat: String = DATE_FORMAT_PATTERN_CN2, + resultFormat: String = DATE_FORMAT_PATTERN_CN2 + ): String { + return try { + formatTime(stringToTimestamp(dateString, parseFormat), resultFormat) + } catch (e: Exception) { + e.printStackTrace() + LogUtil.e("解析失败,$e") + "" + } + } + + /** + * 把字符串转成时间戳,返回13位时间戳 + * @param parseFormat 字符串是什么格式的日期就传什么格式 + */ + fun stringToTimestamp( + dateString: String, parseFormat: String = DATE_FORMAT_PATTERN_CN2 + ): Long { + return try { + val formatter = SimpleDateFormat(parseFormat, Locale.getDefault()) + val date = formatter.parse(dateString) + date?.time ?: 0 + } catch (e: Exception) { + e.printStackTrace() + LogUtil.e("解析失败,$e") + 0 + } + } + + /** + * 获取这个时间戳的,前几天13位时间戳 + */ + fun getBeforeHowTimestamp(cTimestamp: Long, before: Long): Long { + return cTimestamp - (before * 24 * 60 * 60 * 1000L) + } + + /** + * 获取这个时间戳的,后几天13位时间戳 + */ + fun getAfterHowTimestamp(cTimestamp: Long, after: Long): Long { + return cTimestamp + (after * 24 * 60 * 60 * 1000L) + } + + /** + * 计算2个日期相册多少年,多少月 + */ + fun differYear(startDate: String, endDate: String): Array { + val result = arrayOfNulls(2) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val period = Period.between(LocalDate.parse(startDate), LocalDate.parse(endDate)) + result[0] = period.years + result[1] = period.months + } else { + val dfs = SimpleDateFormat(DATE_FORMAT_PATTERN_CN, Locale.getDefault()) + val sDate = dfs.parse(startDate) + val eDate = dfs.parse(endDate) + // 得到两者的毫秒数 +// val between = (eDate.time - sDate.time) + val year = eDate.year - sDate.year + val month = eDate.month - sDate.month + //2021-8-1,2122-1-1 + if (month < 0) { + result[0] = year - 1 + //取绝对值 + result[1] = 12 - abs(month) + } else { + result[0] = year + result[1] = month + } + } + return result + } + + /** + * 秒转换为 时分秒 + */ + fun timeConversion(time: Int): String { + var hour = 0 + var minutes = 0 + var second = 0 + val temp = time % 3600 + if (time > 3600) { + hour = time / 3600 + if (temp != 0) { + if (temp > 60) { + minutes = temp / 60 + if (temp % 60 != 0) { + second = temp % 60 + } + } else { + second = temp + } + } + } else { + minutes = time / 60 + if (time % 60 != 0) { + second = time % 60 + } + } + return "${fill2Digits(hour)}:${fill2Digits(minutes)}:${fill2Digits(second)}" + } + + /** + * 把秒转换为 day hour min + */ + fun getSecondToDayHourMin(second: Long): Array { + val day = second / (24 * 60 * 60) + val hour = (second % (24 * 60 * 60)) / (60 * 60) + val min = ((second % (24 * 60 * 60)) % (60 * 60)) / 60 + return arrayOf( + fill2Digits(day.toInt()), fill2Digits(hour.toInt()), fill2Digits(min.toInt()) + ) + } + + /** + * 把秒转换为 year month day + */ + fun getDayToYearMonthDay(second: Long): Array { + if (second <= 0) return arrayOf("0", "0", "0") + val year = second / 365 + val month = (second % 365) / 30 + val day = (second % 365) % 30 + return arrayOf( + fill2Digits(year.toInt()), fill2Digits(month.toInt()), fill2Digits(day.toInt()) + ) + } + + + /** + * @param d 需要处理的数字 + * @param num 保留位数(只能是1或者2) + * @return 保留num位小数后的字符串 + */ + fun formatDecimal(d: Double, num: Int = 2): String { + return if (num == 2) { + String.format(Locale("en", "US"), "%.2f", d) + } else { + String.format(Locale("en", "US"), "%.1f", d) + } + } + + /** + * @param f 需要处理的数字 + * @param num 保留位数(只能是1或者2) + * @return 保留num位小数后的字符串 + */ + fun formatDecimal(f: Float, num: Int): String { + return if (num == 2) { + String.format(Locale("en", "US"), "%.2f", f) + } else { + String.format(Locale("en", "US"), "%.1f", f) + } + } + + /** + * 四舍五入取整 + */ + fun roundOffToInt(f: Double): Int { + return f.roundToInt() + } + + const val KM = 0 + const val KCAL = 1 + + /** + * 处理转换为千 不足千不转换 + * + * @param d 需要处理的数字 double类型才会精准四舍五入 + * @param num 保留位数(只能是1或者2) + * @param unitType 单位类型 0里程m,km 1卡路里 cal kcal + * @return + */ + fun convertThousand(d: Double, num: Int, unitType: Int): String { + return if (d < 1000) { + var unit = " m" + when (unitType) { + KM -> unit = " m" + KCAL -> unit = " Cal" + } + formatDecimal(d, num) + unit + } else { + var unit = " Km" + when (unitType) { + KM -> unit = " Km" + KCAL -> unit = " KCal" + } + formatDecimal(d / 1000, num) + unit + } + } + + /** + * 个位数补0转两位数 + */ + fun fill2Digits(value: Int): String { + var str = value.toString() + if (value < 10) { + str = "0$value" + } + return str + } + + /** + * md5加密、32位小写加密 + */ + fun get32Md5Value(sSecret: String): String { + if (TextUtils.isEmpty(sSecret)) { + return "" + } + try { + val bmd5 = MessageDigest.getInstance("MD5") + bmd5.update(sSecret.toByteArray()) + var i: Int + val buf = StringBuffer() + val b = bmd5.digest() + for (offset in b.indices) { + i = b[offset].toInt() + if (i < 0) i += 256 + if (i < 16) buf.append("0") + buf.append(Integer.toHexString(i)) + } + return buf.toString() + } catch (e: NoSuchAlgorithmException) { + e.printStackTrace() + } + return "" + } + + /** + * 判断邮箱email格式是否正确 + */ + fun isValidEmail(email: String): Boolean { + val str = + "^([a-zA-Z0-9_\\-.+]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(]?)$" + return isRegularExpression(email, str) + } + + /** + * 是否符合该正则表达式 + */ + fun isRegularExpression(value: String, regex: String): Boolean { + val p = Pattern.compile(regex) + val m = p.matcher(value) + return m.matches() + } + + /** + *求圆弧上某个点的坐标 + * @angle 角度是以圆的右方开始计算角度,顺时针增加 + */ + fun getPointOnCircleArc( + centerX: Float, centerY: Float, radius: Float, angle: Double + ): FloatArray { + val angleInRadians = Math.toRadians(angle) + val x = (centerX + radius * cos(angleInRadians)).toFloat() + val y = (centerY + radius * sin(angleInRadians)).toFloat() + return floatArrayOf(x, y) + } + + /** + * 获取圆弧上某个点和圆心角度 + */ + fun getAngleFromCenter(x: Float, y: Float, centerX: Float, centerY: Float): Double { + val angle = atan2((y - centerY).toDouble(), (x - centerX).toDouble()) + // 转换为角度 + return Math.toDegrees(angle) + } + + /** + *根据信号区间转换为信号级别 + * @param numLevels 级别个数 + */ + fun calculateSignalLevel( + rssi: Int, numLevels: Int, minRssi: Int = -100, maxRssi: Int = -55 + ): Int { + return if (rssi <= minRssi) { + 0 + } else if (rssi >= maxRssi) { + numLevels - 1 + } else { + val inputRange: Float = (maxRssi - minRssi).toFloat() + val outputRange = (numLevels - 1).toFloat() + ((rssi - minRssi).toFloat() * outputRange / inputRange).toInt() + } + } + + /** + * 根据电量计算级别 + */ + fun calculatePowerLevel(power: Int): Int { + return if (power in 10..24) 1 + else if (power in 25..49) 2 + else if (power in 50..74) 3 + else if (power >= 75) 4 + else 0 + } + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/reflect/ReflectArgument.java b/baselibrary/src/main/java/com/abbidot/baselibrary/util/reflect/ReflectArgument.java new file mode 100644 index 0000000..4724f2e --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/reflect/ReflectArgument.java @@ -0,0 +1,46 @@ +package com.abbidot.baselibrary.util.reflect; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public final class ReflectArgument { + private final Class clazz; + private final T value; + + private ReflectArgument(@NonNull Class clazz, @Nullable T value) { + this.clazz = clazz; + this.value = value; + } + + public static ReflectArgument of(@NonNull String className, @Nullable T value) { + Class clazz = ReflectHelper.getClassForName(className); + return new ReflectArgument<>(clazz, value); + } + + public static ReflectArgument of(@NonNull Class clazz, @Nullable T value) { + return new ReflectArgument<>(clazz, value); + } + + public static ReflectArgument of(@NonNull T value) { + Class clazz = (Class) value.getClass(); + return new ReflectArgument<>(clazz, value); + } + + static Class[] getClasses(@NonNull ReflectArgument[] args) { + final int size = args.length; + Class[] result = new Class[size]; + for (int i = 0; i < size; i++) { + result[i] = args[i].clazz; + } + return result; + } + + static Object[] getValues(@NonNull ReflectArgument[] args) { + final int size = args.length; + Object[] result = new Object[size]; + for (int i = 0; i < size; i++) { + result[i] = args[i].value; + } + return result; + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/reflect/ReflectHelper.java b/baselibrary/src/main/java/com/abbidot/baselibrary/util/reflect/ReflectHelper.java new file mode 100644 index 0000000..c79732e --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/reflect/ReflectHelper.java @@ -0,0 +1,239 @@ +package com.abbidot.baselibrary.util.reflect; + +import androidx.annotation.NonNull; + + +import com.abbidot.baselibrary.util.LogUtil; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 反射调用帮助类 + */ +public final class ReflectHelper { + private static final String TAG = "ReflectHelper"; + + private static final Object NOT_FOUND = new Object(); + + private static final Map CACHE = new ConcurrentHashMap<>(); + + private Object inst; + private Class clazz; + + private ReflectHelper(Class clazz) { + this.clazz = clazz; + } + + public ReflectHelper setInstance(Object o) { + this.inst = o; + return this; + } + + public Object getField(@NonNull String fieldName) { + Field field = getClassField(this.clazz, fieldName); + if (field != null) { + try { + return field.get(this.inst); + } catch (Exception e) { + LogUtil.Companion.e(TAG, "getField error=%s", e); + } + } + return null; + } + + public boolean setField(String fieldName, Object value) { + Field field = getClassField(this.clazz, fieldName); + if (field != null) { + try { + field.set(this.inst, value); + return true; + } catch (Exception e) { + LogUtil.Companion.e(TAG, "setField error=%s", e); + } + } + return false; + } + + /** + * 执行反射方法,并返回执行状态和函数返回值 + * + * @param outResult 方法返回值 + * @param methodName 方法名称 + * @param args 参数列表,包含类型和值 + * @return 是否调用成功 + */ + public boolean invoke(Object[] outResult, String methodName, ReflectArgument... args) { + Method method = getClassMethod(this.clazz, methodName, ReflectArgument.getClasses(args)); + if (method != null) { + try { + Object result = method.invoke(this.inst, ReflectArgument.getValues(args)); + if (outResult != null && outResult.length > 0) { + outResult[0] = result; + } + return true; + } catch (Exception e) { + LogUtil.Companion.e(TAG, "invoke error=%s", e); + } + } + return false; + } + + /** + * 执行反射方法 + * + * @param methodName 方法名称 + * @param args 参数列表,包含类型和值 + * @return 函数返回值 + */ + public Object invoke(String methodName, ReflectArgument... args) { + Object[] result = new Object[1]; + if (invoke(result, methodName, args)) { + return result[0]; + } + return null; + } + + /** + * 新建对象实例 + * + * @param args 构造方法参数 + * @return 新实例 + */ + public Object newInstance(ReflectArgument... args) { + Constructor constructor = getClassConstructor(this.clazz, ReflectArgument.getClasses(args)); + if (constructor != null) { + try { + return constructor.newInstance(ReflectArgument.getValues(args)); + } catch (Exception e) { + LogUtil.Companion.e(TAG, "newInstance error=%s", e); + } + } + return null; + } + + private static Field getClassField(Class target, String fieldName) { + if (target == null || target == Object.class) { + return null; + } + String key = target.getName() + '#' + fieldName; + Object value = CACHE.get(key); + if (value == NOT_FOUND) { + LogUtil.Companion.d(TAG, "getClassField NOT_FOUND %s", key); + return null; + } + Field field = (Field) value; + if (field == null) { + try { + field = target.getDeclaredField(fieldName); + field.setAccessible(true); + } catch (Exception e) { + //中间过程不记录 + } + if (field == null) { + // 递归查找父类 + field = getClassField(target.getSuperclass(), fieldName); + } + putCache(key, field); + } + return field; + } + + private static Constructor getClassConstructor(Class target, Class[] paramTypes) { + if (target == null) { + return null; + } + String key = target.getName() + "#(" + Arrays.toString(paramTypes) + ')'; + Object value = CACHE.get(key); + if (value == NOT_FOUND) { + LogUtil.Companion.d(TAG, "getClassConstructor NOT_FOUND %s", key); + return null; + } + Constructor constructor = (Constructor) value; + if (constructor == null) { + try { + constructor = target.getDeclaredConstructor(paramTypes); + constructor.setAccessible(true); + } catch (Exception e) { + LogUtil.Companion.e(TAG, "getClassConstructor NOT_FOUND %s error=%s", key, e); + } + putCache(key, constructor); + } + return constructor; + } + + private static Method getClassMethod(Class target, String methodName, Class[] paramTypes) { + if (target == null || target == Object.class) { + return null; + } + String key = target.getName() + '#' + methodName + '(' + Arrays.toString(paramTypes) + ')'; + Object value = CACHE.get(key); + if (value == NOT_FOUND) { + LogUtil.Companion.d(TAG, "getClassMethod NOT_FOUND %s", key); + return null; + } + Method method = (Method) value; + if (method == null) { + try { + method = target.getDeclaredMethod(methodName, paramTypes); + method.setAccessible(true); + } catch (Exception e) { + //中间过程不记录 + } + if (method == null) { + // 递归查找父类 + method = getClassMethod(target.getSuperclass(), methodName, paramTypes); + } + putCache(key, method); + } + return method; + } + + static Class getClassForName(String className) { + Object value = CACHE.get(className); + if (value == NOT_FOUND) { + LogUtil.Companion.d(TAG, "getClassForName NOT_FOUND %s", className); + return null; + } + + Class clazz = (Class) value; + if (clazz == null) { + try { + clazz = Class.forName(className); + } catch (Exception e) { + LogUtil.Companion.e(TAG, "getClassForName NOT_FOUND %s error=%s", className, e); + } + putCache(className, clazz); + } + return clazz; + } + + public static ReflectHelper of(Class clazz) { + return new ReflectHelper(clazz); + } + + public static ReflectHelper of(String className) { + return new ReflectHelper(getClassForName(className)); + } + + public static ReflectHelper of(Object inst) { + return new ReflectHelper(inst != null ? inst.getClass() : null).setInstance(inst); + } + + public static int getFieldCount(@NonNull Class clazz) { + return clazz.getFields().length; + } + + private static void putCache(String key, Object value) { + if (value == null || value == NOT_FOUND) { + LogUtil.Companion.e(TAG, "NOT_FOUND %s", key); + value = NOT_FOUND; + } + CACHE.put(key, value); + } +} \ No newline at end of file diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/Base64.java b/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/Base64.java new file mode 100644 index 0000000..e0cf317 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/Base64.java @@ -0,0 +1,604 @@ +package com.abbidot.baselibrary.util.rsa; + +import java.io.UnsupportedEncodingException; + +public class Base64 { + /** + * Chunk size per RFC 2045 section 6.8. + * + *

The {@value} character limit does not count the trailing CRLF, but counts + * all other characters, including any equal signs.

+ * + * @see
RFC 2045 section 6.8 + */ + static final int CHUNK_SIZE = 76; + + /** + * Chunk separator per RFC 2045 section 2.1. + * + * @see RFC 2045 section 2.1 + */ + static final byte[] CHUNK_SEPARATOR = "\r\n".getBytes(); + + /** + * The base length. + */ + static final int BASELENGTH = 255; + + /** + * Lookup length. + */ + static final int LOOKUPLENGTH = 64; + + /** + * Used to calculate the number of bits in a byte. + */ + static final int EIGHTBIT = 8; + + /** + * Used when encoding something which has fewer than 24 bits. + */ + static final int SIXTEENBIT = 16; + + /** + * Used to determine how many bits data contains. + */ + static final int TWENTYFOURBITGROUP = 24; + + /** + * Used to get the number of Quadruples. + */ + static final int FOURBYTE = 4; + + /** + * Used to test the sign of a byte. + */ + static final int SIGN = -128; + + /** + * Byte used to pad output. + */ + static final byte PAD = (byte) '='; + + // Create arrays to hold the base64 characters and a + // lookup for base64 chars + private static byte[] base64Alphabet = new byte[BASELENGTH]; + private static byte[] lookUpBase64Alphabet = new byte[LOOKUPLENGTH]; + + // Populating the lookup and character arrays + static { + for (int i = 0; i < BASELENGTH; i++) { + base64Alphabet[i] = (byte) -1; + } + for (int i = 'Z'; i >= 'A'; i--) { + base64Alphabet[i] = (byte) (i - 'A'); + } + for (int i = 'z'; i >= 'a'; i--) { + base64Alphabet[i] = (byte) (i - 'a' + 26); + } + for (int i = '9'; i >= '0'; i--) { + base64Alphabet[i] = (byte) (i - '0' + 52); + } + + base64Alphabet['+'] = 62; + base64Alphabet['/'] = 63; + + for (int i = 0; i <= 25; i++) { + lookUpBase64Alphabet[i] = (byte) ('A' + i); + } + + for (int i = 26, j = 0; i <= 51; i++, j++) { + lookUpBase64Alphabet[i] = (byte) ('a' + j); + } + + for (int i = 52, j = 0; i <= 61; i++, j++) { + lookUpBase64Alphabet[i] = (byte) ('0' + j); + } + + lookUpBase64Alphabet[62] = (byte) '+'; + lookUpBase64Alphabet[63] = (byte) '/'; + } + + private static boolean isBase64(byte octect) { + if (octect == PAD) { + return true; + } else if (base64Alphabet[octect] == -1) { + return false; + } else { + return true; + } + } + + /** + * Tests a given byte array to see if it contains + * only valid characters within the Base64 alphabet. + * + * @param arrayOctect byte array to test + * @return true if all bytes are valid characters in the Base64 + * alphabet or if the byte array is empty; false, otherwise + */ + public static boolean isArrayByteBase64(byte[] arrayOctect) { + + arrayOctect = discardWhitespace(arrayOctect); + + int length = arrayOctect.length; + if (length == 0) { + // shouldn't a 0 length array be valid base64 data? + // return false; + return true; + } + for (int i = 0; i < length; i++) { + if (!isBase64(arrayOctect[i])) { + return false; + } + } + return true; + } + + /** + * Encodes binary data using the base64 algorithm but + * does not chunk the output. + * + * @param binaryData binary data to encode + * @return Base64 characters + */ + public static byte[] encodeBase64(byte[] binaryData) { + return encodeBase64(binaryData, false); + } + + /** + * Encodes binary data using the base64 algorithm and chunks + * the encoded output into 76 character blocks + * + * @param binaryData binary data to encode + * @return Base64 characters chunked in 76 character blocks + */ + public static byte[] encodeBase64Chunked(byte[] binaryData) { + return encodeBase64(binaryData, true); + } + + /** + * Decodes a byte[] containing containing + * characters in the Base64 alphabet. + * + * @param pArray A byte array containing Base64 character data + * @return a byte array containing binary data + */ + public static byte[] decode(byte[] pArray) { + return decodeBase64(pArray); + } + + /** + * Encodes binary data using the base64 algorithm, optionally + * chunking the output into 76 character blocks. + * + * @param binaryData Array containing binary data to encode. + * @param isChunked if isChunked is true this encoder will chunk + * the base64 output into 76 character blocks + * @return Base64-encoded data. + */ + public static byte[] encodeBase64(byte[] binaryData, boolean isChunked) { + int lengthDataBits = binaryData.length * EIGHTBIT; + int fewerThan24bits = lengthDataBits % TWENTYFOURBITGROUP; + int numberTriplets = lengthDataBits / TWENTYFOURBITGROUP; + byte[] encodedData = null; + int encodedDataLength = 0; + int nbrChunks = 0; + + if (fewerThan24bits != 0) { + //data not divisible by 24 bit + encodedDataLength = (numberTriplets + 1) * 4; + } else { + // 16 or 8 bit + encodedDataLength = numberTriplets * 4; + } + + // If the output is to be "chunked" into 76 character sections, + // for compliance with RFC 2045 MIME, then it is important to + // allow for extra length to account for the separator(s) + if (isChunked) { + + nbrChunks = + (CHUNK_SEPARATOR.length == 0 ? 0 : (int) Math.ceil((float) encodedDataLength / CHUNK_SIZE)); + encodedDataLength += nbrChunks * CHUNK_SEPARATOR.length; + } + + encodedData = new byte[encodedDataLength]; + + byte k = 0, l = 0, b1 = 0, b2 = 0, b3 = 0; + + int encodedIndex = 0; + int dataIndex = 0; + int i; + int nextSeparatorIndex = CHUNK_SIZE; + int chunksSoFar = 0; + + //log.debug("number of triplets = " + numberTriplets); + for (i = 0; i < numberTriplets; i++) { + dataIndex = i * 3; + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + b3 = binaryData[dataIndex + 2]; + + //log.debug("b1= " + b1 +", b2= " + b2 + ", b3= " + b3); + + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = + ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = + ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + byte val3 = + ((b3 & SIGN) == 0) ? (byte) (b3 >> 6) : (byte) ((b3) >> 6 ^ 0xfc); + + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + //log.debug( "val2 = " + val2 ); + //log.debug( "k4 = " + (k<<4) ); + //log.debug( "vak = " + (val2 | (k<<4)) ); + encodedData[encodedIndex + 1] = + lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex + 2] = + lookUpBase64Alphabet[(l << 2) | val3]; + encodedData[encodedIndex + 3] = lookUpBase64Alphabet[b3 & 0x3f]; + + encodedIndex += 4; + + // If we are chunking, let's put a chunk separator down. + if (isChunked) { + // this assumes that CHUNK_SIZE % 4 == 0 + if (encodedIndex == nextSeparatorIndex) { + System.arraycopy( + CHUNK_SEPARATOR, + 0, + encodedData, + encodedIndex, + CHUNK_SEPARATOR.length); + chunksSoFar++; + nextSeparatorIndex = + (CHUNK_SIZE * (chunksSoFar + 1)) + + (chunksSoFar * CHUNK_SEPARATOR.length); + encodedIndex += CHUNK_SEPARATOR.length; + } + } + } + + // form integral number of 6-bit groups + dataIndex = i * 3; + + if (fewerThan24bits == EIGHTBIT) { + b1 = binaryData[dataIndex]; + k = (byte) (b1 & 0x03); + //log.debug("b1=" + b1); + //log.debug("b1<<2 = " + (b1>>2) ); + byte val1 = + ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex + 1] = lookUpBase64Alphabet[k << 4]; + encodedData[encodedIndex + 2] = PAD; + encodedData[encodedIndex + 3] = PAD; + } else if (fewerThan24bits == SIXTEENBIT) { + + b1 = binaryData[dataIndex]; + b2 = binaryData[dataIndex + 1]; + l = (byte) (b2 & 0x0f); + k = (byte) (b1 & 0x03); + + byte val1 = + ((b1 & SIGN) == 0) ? (byte) (b1 >> 2) : (byte) ((b1) >> 2 ^ 0xc0); + byte val2 = + ((b2 & SIGN) == 0) ? (byte) (b2 >> 4) : (byte) ((b2) >> 4 ^ 0xf0); + + encodedData[encodedIndex] = lookUpBase64Alphabet[val1]; + encodedData[encodedIndex + 1] = + lookUpBase64Alphabet[val2 | (k << 4)]; + encodedData[encodedIndex + 2] = lookUpBase64Alphabet[l << 2]; + encodedData[encodedIndex + 3] = PAD; + } + + if (isChunked) { + // we also add a separator to the end of the final chunk. + if (chunksSoFar < nbrChunks) { + System.arraycopy( + CHUNK_SEPARATOR, + 0, + encodedData, + encodedDataLength - CHUNK_SEPARATOR.length, + CHUNK_SEPARATOR.length); + } + } + + return encodedData; + } + + /** + * Decodes Base64 data into octects + * + * @param base64Data Byte array containing Base64 data + * @return Array containing decoded data. + */ + public static byte[] decodeBase64(byte[] base64Data) { + // RFC 2045 requires that we discard ALL non-Base64 characters + base64Data = discardNonBase64(base64Data); + + // handle the edge case, so we don't have to worry about it later + if (base64Data.length == 0) { + return new byte[0]; + } + + int numberQuadruple = base64Data.length / FOURBYTE; + byte[] decodedData = null; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0, marker0 = 0, marker1 = 0; + + // Throw away anything not in base64Data + + int encodedIndex = 0; + int dataIndex = 0; + { + // this sizes the output array properly - rlw + int lastData = base64Data.length; + // ignore the '=' padding + while (base64Data[lastData - 1] == PAD) { + if (--lastData == 0) { + return new byte[0]; + } + } + decodedData = new byte[lastData - numberQuadruple]; + } + + for (int i = 0; i < numberQuadruple; i++) { + dataIndex = i * 4; + marker0 = base64Data[dataIndex + 2]; + marker1 = base64Data[dataIndex + 3]; + + b1 = base64Alphabet[base64Data[dataIndex]]; + b2 = base64Alphabet[base64Data[dataIndex + 1]]; + + if (marker0 != PAD && marker1 != PAD) { + //No PAD e.g 3cQl + b3 = base64Alphabet[marker0]; + b4 = base64Alphabet[marker1]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex + 2] = (byte) (b3 << 6 | b4); + } else if (marker0 == PAD) { + //Two PAD e.g. 3c[Pad][Pad] + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + } else if (marker1 == PAD) { + //One PAD e.g. 3cQ[Pad] + b3 = base64Alphabet[marker0]; + + decodedData[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex + 1] = + (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + } + encodedIndex += 3; + } + return decodedData; + } + + /** + * Discards any whitespace from a base-64 encoded block. + * + * @param data The base-64 encoded data to discard the whitespace + * from. + * @return The data, less whitespace (see RFC 2045). + */ + static byte[] discardWhitespace(byte[] data) { + byte[] groomedData = new byte[data.length]; + int bytesCopied = 0; + + for (byte datum : data) { + switch (datum) { + case (byte) ' ': + case (byte) '\n': + case (byte) '\r': + case (byte) '\t': + break; + default: + groomedData[bytesCopied++] = datum; + } + } + + byte[] packedData = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + + /** + * Discards any characters outside of the base64 alphabet, per + * the requirements on page 25 of RFC 2045 - "Any characters + * outside of the base64 alphabet are to be ignored in base64 + * encoded data." + * + * @param data The base-64 encoded data to groom + * @return The data, less non-base64 characters (see RFC 2045). + */ + static byte[] discardNonBase64(byte[] data) { + byte[] groomedData = new byte[data.length]; + int bytesCopied = 0; + + for (byte datum : data) { + if (isBase64(datum)) { + groomedData[bytesCopied++] = datum; + } + } + + byte[] packedData = new byte[bytesCopied]; + + System.arraycopy(groomedData, 0, packedData, 0, bytesCopied); + + return packedData; + } + + /** + * Encodes a byte[] containing binary data, into a byte[] containing + * characters in the Base64 alphabet. + * + * @param pArray a byte array containing binary data + * @return A byte array containing only Base64 character data + */ + public static byte[] encode(byte[] pArray) { + return encodeBase64(pArray, false); + } + + public static String encode(String str) throws UnsupportedEncodingException + { + String baseStr = new String(encode(str.getBytes("UTF-8"))); + String tempStr = Digest.digest(str).toUpperCase(); + String result = tempStr+baseStr; + return new String(encode(result.getBytes("UTF-8"))); + } + + public static String decode(String cryptoStr) throws + UnsupportedEncodingException { + if(cryptoStr.length()<40) + return ""; + try + { + String tempStr = new String(decode(cryptoStr.getBytes("UTF-8"))); + String result = tempStr.substring(40, tempStr.length()); + return new String(decode(result.getBytes("UTF-8"))); + } + catch(ArrayIndexOutOfBoundsException ex) + { + return ""; + } + } + + /** + * Decodes Base64 data into octects + * + * @param encoded string containing Base64 data + * @return Array containind decoded data. + */ + public static byte[] decode2(String encoded) { + + if (encoded == null) { + return null; + } + + char[] base64Data = encoded.toCharArray(); + // remove white spaces + int len = removeWhiteSpace(base64Data); + + if (len % FOURBYTE != 0) { + return null;//should be divisible by four + } + + int numberQuadruple = (len / FOURBYTE); + + if (numberQuadruple == 0) { + return new byte[0]; + } + + byte[] decodedData; + byte b1 = 0, b2 = 0, b3 = 0, b4 = 0; + char d1 = 0, d2 = 0, d3 = 0, d4 = 0; + + int i = 0; + int encodedIndex = 0; + int dataIndex = 0; + decodedData = new byte[(numberQuadruple) * 3]; + + for (; i < numberQuadruple - 1; i++) { + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++])) + || !isData((d3 = base64Data[dataIndex++])) + || !isData((d4 = base64Data[dataIndex++]))) { + return null; + }//if found "no data" just return null + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + } + + if (!isData((d1 = base64Data[dataIndex++])) || !isData((d2 = base64Data[dataIndex++]))) { + return null;//if found "no data" just return null + } + + b1 = base64Alphabet[d1]; + b2 = base64Alphabet[d2]; + + d3 = base64Data[dataIndex++]; + d4 = base64Data[dataIndex++]; + if (!isData((d3)) || !isData((d4))) {//Check if they are PAD characters + if (isPad(d3) && isPad(d4)) { + if ((b2 & 0xf) != 0)//last 4 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 1]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex] = (byte) (b1 << 2 | b2 >> 4); + return tmp; + } else if (!isPad(d3) && isPad(d4)) { + b3 = base64Alphabet[d3]; + if ((b3 & 0x3) != 0)//last 2 bits should be zero + { + return null; + } + byte[] tmp = new byte[i * 3 + 2]; + System.arraycopy(decodedData, 0, tmp, 0, i * 3); + tmp[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + tmp[encodedIndex] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + return tmp; + } else { + return null; + } + } else { //No PAD e.g 3cQl + b3 = base64Alphabet[d3]; + b4 = base64Alphabet[d4]; + decodedData[encodedIndex++] = (byte) (b1 << 2 | b2 >> 4); + decodedData[encodedIndex++] = (byte) (((b2 & 0xf) << 4) | ((b3 >> 2) & 0xf)); + decodedData[encodedIndex++] = (byte) (b3 << 6 | b4); + + } + + return decodedData; + } + + private static boolean isWhiteSpace(char octect) { + return (octect == 0x20 || octect == 0xd || octect == 0xa || octect == 0x9); + } + + private static boolean isData(char octect) { + return (octect < BASELENGTH && base64Alphabet[octect] != -1); + } + + private static boolean isPad(char octect) { + return (octect == PAD); + } + + /** + * remove WhiteSpace from MIME containing encoded Base64 data. + * + * @param data the byte array of base64 data (with WS) + * @return the new length + */ + private static int removeWhiteSpace(char[] data) { + if (data == null) { + return 0; + } + + // count characters that's not whitespace + int newSize = 0; + int len = data.length; + for (int i = 0; i < len; i++) { + if (!isWhiteSpace(data[i])) { + data[newSize++] = data[i]; + } + } + return newSize; + } +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/Digest.java b/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/Digest.java new file mode 100644 index 0000000..b163d7e --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/Digest.java @@ -0,0 +1,177 @@ +package com.abbidot.baselibrary.util.rsa; + +import com.abbidot.baselibrary.util.LogUtil; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + + +public class Digest { + private static final String ENCODE = "UTF-8"; + + private String signMD5(String aValue, String encoding) { + try { + byte[] input = aValue.getBytes(encoding); + MessageDigest md = MessageDigest.getInstance("MD5"); + return toHex(md.digest(input)); + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { + LogUtil.Companion.e("signMD5 Exception" + e.getMessage()); + return null; + } + } + + private String hmacSign(String aValue) { + try { + byte[] input = aValue.getBytes(); + MessageDigest md = MessageDigest.getInstance("MD5"); + return toHex(md.digest(input)); + } catch (NoSuchAlgorithmException e) { + LogUtil.Companion.e(e.getMessage()); + return null; + } + } + + private String hmacSign(String aValue, String aKey) { + return hmacSign(aValue, aKey, ENCODE); + } + + public static String hmacSign(String aValue, String aKey, String encoding) { + byte[] k_ipad = new byte[64]; + byte[] k_opad = new byte[64]; + byte[] keyb; + byte[] value; + try { + keyb = aKey.getBytes(encoding); + value = aValue.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + keyb = aKey.getBytes(); + value = aValue.getBytes(); + } + Arrays.fill(k_ipad, keyb.length, 64, (byte) 54); + Arrays.fill(k_opad, keyb.length, 64, (byte) 92); + for (int i = 0; i < keyb.length; i++) { + k_ipad[i] = (byte) (keyb[i] ^ 0x36); + k_opad[i] = (byte) (keyb[i] ^ 0x5c); + } + + MessageDigest md; + try { + md = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + LogUtil.Companion.e("hmacSign Exception:" + e.getMessage()); + return null; + } + md.update(k_ipad); + md.update(value); + byte[] dg = md.digest(); + md.reset(); + md.update(k_opad); + md.update(dg, 0, 16); + dg = md.digest(); + return toHex(dg); + } + + private String hmacSHASign(String aValue, String aKey, String encoding) { + byte[] k_ipad = new byte[64]; + byte[] k_opad = new byte[64]; + byte[] keyb; + byte[] value; + try { + keyb = aKey.getBytes(encoding); + value = aValue.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + keyb = aKey.getBytes(); + value = aValue.getBytes(); + } + Arrays.fill(k_ipad, keyb.length, 64, (byte) 54); + Arrays.fill(k_opad, keyb.length, 64, (byte) 92); + for (int i = 0; i < keyb.length; i++) { + k_ipad[i] = (byte) (keyb[i] ^ 0x36); + k_opad[i] = (byte) (keyb[i] ^ 0x5c); + } + + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException e) { + LogUtil.Companion.e("hmacSHASign Exception:" + e.getMessage()); + return null; + } + md.update(k_ipad); + md.update(value); + byte[] dg = md.digest(); + md.reset(); + md.update(k_opad); + md.update(dg, 0, 20); + dg = md.digest(); + return toHex(dg); + } + + public static String digest(String aValue) { + return digest(aValue, ENCODE); + } + + public static String digest(String aValue, String encoding) { + aValue = aValue.trim(); + byte[] value; + try { + value = aValue.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + value = aValue.getBytes(); + } + MessageDigest md; + try { + md = MessageDigest.getInstance("SHA"); + } catch (NoSuchAlgorithmException e) { + LogUtil.Companion.e("digest Exception:" + e.getMessage()); + return null; + } + return toHex(md.digest(value)); + } + + + private String digest(String aValue, String alg, String encoding) { + aValue = aValue.trim(); + byte[] value; + try { + value = aValue.getBytes(encoding); + } catch (UnsupportedEncodingException e) { + value = aValue.getBytes(); + } + MessageDigest md; + try { + md = MessageDigest.getInstance(alg); + } catch (NoSuchAlgorithmException e) { + LogUtil.Companion.e("digest Exception:" + e.getMessage()); + return null; + } + return toHex(md.digest(value)); + } + + private String udpSign(String aValue) { + try { + byte[] input = aValue.getBytes(StandardCharsets.UTF_8); + MessageDigest md = MessageDigest.getInstance("SHA1"); + return new String(Base64.encode(md.digest(input)), ENCODE); + } catch (Exception e) { + return null; + } + } + + private static String toHex(byte[] input) { + if (input == null) + return null; + StringBuilder output = new StringBuilder(input.length * 2); + for (byte b : input) { + int current = b & 0xff; + if (current < 16) + output.append("0"); + output.append(Integer.toString(current, 16)); + } + return output.toString(); + } + +} diff --git a/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/RSA.java b/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/RSA.java new file mode 100644 index 0000000..f0a5cc0 --- /dev/null +++ b/baselibrary/src/main/java/com/abbidot/baselibrary/util/rsa/RSA.java @@ -0,0 +1,187 @@ +package com.abbidot.baselibrary.util.rsa; + +/* + --------------------------------------------**********-------------------------------------------- + + 该算法于1977年由美国麻省理工学院MIT(Massachusetts Institute of Technology)的Ronal Rivest,Adi Shamir和Len + Adleman三位年轻教授提出,并以三人的姓氏Rivest,Shamir和Adlernan命名为RSA算法,是一个支持变长密钥的公共密钥算法,需要加密的文件快的长度也是可变的! + + 所谓RSA加密算法,是世界上第一个非对称加密算法,也是数论的第一个实际应用。它的算法如下: + + 1.找两个非常大的质数p和q(通常p和q都有155十进制位或都有512十进制位)并计算n=pq,k=(p-1)(q-1)。 + + 2.将明文编码成整数M,保证M不小于0但是小于n。 + + 3.任取一个整数e,保证e和k互质,而且e不小于0但是小于k。加密钥匙(称作公钥)是(e, n)。 + + 4.找到一个整数d,使得ed除以k的余数是1(只要e和n满足上面条件,d肯定存在)。解密钥匙(称作密钥)是(d, n)。 + + 加密过程: 加密后的编码C等于M的e次方除以n所得的余数。 + + 解密过程: 解密后的编码N等于C的d次方除以n所得的余数。 + + 只要e、d和n满足上面给定的条件。M等于N。 + + --------------------------------------------**********-------------------------------------------- + */ + +import com.abbidot.baselibrary.util.LogUtil; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.Signature; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.HashMap; +import java.util.Map; + +import javax.crypto.Cipher; + + +/** + * 源码链接 + */ +public class RSA { + + private static final String CHAR_ENCODING = "UTF-8"; + private static final String AES_ALGORITHM = "AES/CBC/PKCS5Padding"; + private static final String RSA_ALGORITHM = "RSA/ECB/PKCS1Padding"; + + /** + * 生成密钥对 + */ + private Map generateKeyPair() throws Exception { + // 指定key的大小 + int keySize = 2048; + /** RSA算法要求有一个可信任的随机数源 */ + SecureRandom sr = new SecureRandom(); + /** 为RSA算法创建一个KeyPairGenerator对象 */ + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */ + kpg.initialize(keySize, sr); + /** 生成密匙对 */ + KeyPair kp = kpg.generateKeyPair(); + /** 得到公钥 */ + Key publicKey = kp.getPublic(); + byte[] publicKeyBytes = publicKey.getEncoded(); + String pub = new String(Base64.encodeBase64(publicKeyBytes), CHAR_ENCODING); + /** 得到私钥 */ + Key privateKey = kp.getPrivate(); + byte[] privateKeyBytes = privateKey.getEncoded(); + String pri = new String(Base64.encodeBase64(privateKeyBytes), CHAR_ENCODING); + + Map map = new HashMap<>(); + map.put("publicKey", pub); + map.put("privateKey", pri); + RSAPublicKey rsp = (RSAPublicKey) kp.getPublic(); + BigInteger bint = rsp.getModulus(); + byte[] b = bint.toByteArray(); + byte[] deBase64Value = Base64.encodeBase64(b); + String retValue = new String(deBase64Value); + map.put("modulus", retValue); + return map; + } + + /** + * 加密方法 source: 源数据 + */ + public static String encrypt(String source, String publicKey) throws Exception { + Key key = getPublicKey(publicKey); + /** 得到Cipher对象来实现对源数据的RSA加密 */ + Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); + cipher.init(Cipher.ENCRYPT_MODE, key); + byte[] b = source.getBytes(); + /** 执行加密操作 */ + byte[] b1 = cipher.doFinal(b); + return new String(Base64.encodeBase64(b1), CHAR_ENCODING); + } + + /** + * 解密算法 cryptograph:密文 + */ + private String decrypt(String cryptograph, String privateKey) throws Exception { + Key key = getPrivateKey(privateKey); + /** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */ + Cipher cipher = Cipher.getInstance(RSA_ALGORITHM); + cipher.init(Cipher.DECRYPT_MODE, key); + byte[] b1 = Base64.decodeBase64(cryptograph.getBytes()); + /** 执行解密操作 */ + byte[] b = cipher.doFinal(b1); + return new String(b); + } + + /** + * 得到公钥 + * + * @param key 密钥字符串(经过base64编码) + * @throws Exception + */ + private static PublicKey getPublicKey(String key) throws Exception { + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(Base64.decodeBase64(key.getBytes())); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePublic(keySpec); + } + + /** + * 得到私钥 + * + * @param key 密钥字符串(经过base64编码) + * @throws Exception + */ + private PrivateKey getPrivateKey(String key) throws Exception { + PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.decodeBase64(key.getBytes())); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + return keyFactory.generatePrivate(keySpec); + } + + private String sign(String content, String privateKey) { + try { + PKCS8EncodedKeySpec priPKCS8 = + new PKCS8EncodedKeySpec(Base64.decodeBase64(privateKey.getBytes())); + KeyFactory keyf = KeyFactory.getInstance("RSA"); + PrivateKey priKey = keyf.generatePrivate(priPKCS8); + + Signature signature = Signature.getInstance("SHA256WithRSA"); + + signature.initSign(priKey); + signature.update(content.getBytes(CHAR_ENCODING)); + + byte[] signed = signature.sign(); + + return new String(Base64.encodeBase64(signed)); + } catch (Exception e) { + LogUtil.Companion.e("sign Exception:" + e.getMessage()); + } + + return null; + } + + private boolean checkSign(String content, String sign, String publicKey) { + try { + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + byte[] encodedKey = Base64.decode2(publicKey); + PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey)); + + + Signature signature = Signature.getInstance("SHA256WithRSA"); + + signature.initVerify(pubKey); + signature.update(content.getBytes(StandardCharsets.UTF_8)); + + return signature.verify(Base64.decode2(sign)); + + } catch (Exception e) { + LogUtil.Companion.e("checkSign Exception:" + e.getMessage()); + } + return false; + } + +} \ No newline at end of file diff --git a/baselibrary/src/test/java/com/abbidot/baselibrary/ExampleUnitTest.kt b/baselibrary/src/test/java/com/abbidot/baselibrary/ExampleUnitTest.kt new file mode 100644 index 0000000..5d03f8d --- /dev/null +++ b/baselibrary/src/test/java/com/abbidot/baselibrary/ExampleUnitTest.kt @@ -0,0 +1,17 @@ +package com.abbidot.baselibrary + +import org.junit.Test + +import org.junit.Assert.* + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..5c0edc5 --- /dev/null +++ b/build.gradle @@ -0,0 +1,59 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = '2.2.0' + repositories { + google() + mavenCentral() + + // hms +// maven { url 'http://developer.huawei.com/repo/'} +// fcm +// maven { url "https://maven.google.com" } + } + dependencies { + classpath 'com.android.tools.build:gradle:8.11.1' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath 'com.google.gms:google-services:4.4.2' +// classpath 'com.google.dagger:hilt-android-gradle-plugin:2.46.1' + classpath 'com.google.dagger:hilt-android-gradle-plugin:2.57' + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + + // fcm +// classpath 'com.google.gms:google-services:4.3.4' + // hms +// classpath 'com.huawei.agconnect:agcp:1.4.1.300' + } +} + +allprojects { + repositories { + google() + mavenCentral() + maven { url 'https://jitpack.io' } +// jcenter() // Warning: this repository is going to shut down soon + + //mapbox + maven { + url 'https://api.mapbox.com/downloads/v2/releases/maven' + authentication { + basic(BasicAuthentication) + } + credentials { + // Do not change the username below. + // This should always be `mapbox` (not your username). + username = "mapbox" + // Use the secret token you stored in gradle.properties as the password + password = project.properties['MAPBOX_DOWNLOADS_TOKEN'] ?: "" + } + } + //hms +// maven {url 'http://developer.huawei.com/repo/'} + //fcm +// maven { url "https://maven.google.com" } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/easyPhotos/.gitignore b/easyPhotos/.gitignore new file mode 100644 index 0000000..8a43cc8 --- /dev/null +++ b/easyPhotos/.gitignore @@ -0,0 +1,73 @@ +.DS_Store + +# Built application files +*.apk +*.ap_ + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea +.idea/workspace.xml +.idea/modules.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +.idea/caches + +# Keystore files +# Uncomment the following line if you do not want to check your keystore files in. +#*.jks + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild + +# Google Services (e.g. APIs or Firebase) +google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# test +/src/test +/src/androidTest \ No newline at end of file diff --git a/easyPhotos/build.gradle b/easyPhotos/build.gradle new file mode 100644 index 0000000..f967bf4 --- /dev/null +++ b/easyPhotos/build.gradle @@ -0,0 +1,57 @@ +//apply plugin: 'com.android.library' +//apply plugin: 'com.github.dcendents.android-maven' +//group = 'com.github.HuanTanSheng' +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 33 + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 33 + vectorDrawables.useSupportLibrary = true + } + namespace 'com.huantansheng.easyphotos' +} + +dependencies { + implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.appcompat:appcompat:1.7.0' + implementation 'com.google.android.material:material:1.10.0' + implementation 'androidx.recyclerview:recyclerview:1.3.1' + implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' + implementation 'com.github.chrisbanes:PhotoView:2.3.0' +} +// +//// 指定编码 +//tasks.withType(JavaCompile) { +// options.encoding = "UTF-8" +//} + +// 打包源码 +//task sourcesJar(type: Jar) { +// from android.sourceSets.main.java.srcDirs +// classifier = 'sources' +//} + +//制作文档 +//task javadoc(type: Javadoc) { +// failOnError false +// source = android.sourceSets.main.java.sourceFiles +// classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +// classpath += configurations.compile +//} + +// 打包文档 +//task javadocJar(type: Jar, dependsOn: javadoc) { +// classifier = 'javadoc' +// from javadoc.destinationDir +//} + +//artifacts { +// archives sourcesJar +//// archives javadocJar +//} \ No newline at end of file diff --git a/easyPhotos/proguard-rules.pro b/easyPhotos/proguard-rules.pro new file mode 100644 index 0000000..068a256 --- /dev/null +++ b/easyPhotos/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\androidSpace\android\sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/easyPhotos/src/main/AndroidManifest.xml b/easyPhotos/src/main/AndroidManifest.xml new file mode 100644 index 0000000..21a6226 --- /dev/null +++ b/easyPhotos/src/main/AndroidManifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/Builder/AlbumBuilder.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/Builder/AlbumBuilder.java new file mode 100644 index 0000000..f1abbe9 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/Builder/AlbumBuilder.java @@ -0,0 +1,678 @@ +package com.huantansheng.easyphotos.Builder; + +import android.app.Activity; +import android.net.Uri; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; + +import com.huantansheng.easyphotos.callback.SelectCallback; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.ad.AdListener; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.EasyPhotosActivity; +import com.huantansheng.easyphotos.utils.result.EasyResult; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Arrays; + +/** + * EasyPhotos的启动管理器 + * Created by huan on 2017/10/18. + */ +public class AlbumBuilder { + + /** + * 启动模式 + * CAMERA-相机 + * ALBUM-相册专辑 + * ALBUM_CAMERA-带有相机按钮的相册专辑 + */ + private enum StartupType { + CAMERA, + ALBUM, + ALBUM_CAMERA + } + + private static final String TAG = "com.huantansheng.easyphotos"; + private static AlbumBuilder instance; + private WeakReference mActivity; + private WeakReference mFragmentV; + private WeakReference mFragment; + private StartupType startupType; + private WeakReference adListener; + + //私有构造函数,不允许外部调用,真正实例化通过静态方法实现 + private AlbumBuilder(Activity activity, StartupType startupType) { + mActivity = new WeakReference(activity); + this.startupType = startupType; + } + + private AlbumBuilder(android.app.Fragment fragment, StartupType startupType) { + mFragment = new WeakReference(fragment); + this.startupType = startupType; + } + + private AlbumBuilder(FragmentActivity activity, StartupType startupType) { + mActivity = new WeakReference(activity); + this.startupType = startupType; + } + + private AlbumBuilder(Fragment fragment, StartupType startupType) { + mFragmentV = new WeakReference(fragment); + this.startupType = startupType; + } + + /** + * 内部处理相机和相册的实例 + * + * @param activity Activity的实例 + * @return AlbumBuilder EasyPhotos的实例 + */ + + private static AlbumBuilder with(Activity activity, StartupType startupType) { + clear(); + instance = new AlbumBuilder(activity, startupType); + return instance; + } + + + private static AlbumBuilder with(android.app.Fragment fragment, StartupType startupType) { + clear(); + instance = new AlbumBuilder(fragment, startupType); + return instance; + } + + private static AlbumBuilder with(FragmentActivity activity, StartupType startupType) { + clear(); + instance = new AlbumBuilder(activity, startupType); + return instance; + } + + private static AlbumBuilder with(Fragment fragmentV, StartupType startupType) { + clear(); + instance = new AlbumBuilder(fragmentV, startupType); + return instance; + } + + + /** + * 创建相机 + * + * @param activity 上下文 + * @return AlbumBuilder + */ + + public static AlbumBuilder createCamera(Activity activity) { + return AlbumBuilder.with(activity, StartupType.CAMERA); + } + + + public static AlbumBuilder createCamera(android.app.Fragment fragment) { + return AlbumBuilder.with(fragment, StartupType.CAMERA); + } + + public static AlbumBuilder createCamera(FragmentActivity activity) { + return AlbumBuilder.with(activity, StartupType.CAMERA); + } + + public static AlbumBuilder createCamera(Fragment fragmentV) { + return AlbumBuilder.with(fragmentV, StartupType.CAMERA); + } + + /** + * 创建相册 + * + * @param activity 上下文 + * @param isShowCamera 是否显示相机按钮 + * @param imageEngine 图片加载引擎的具体实现 + * @return + */ + public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(activity, StartupType.ALBUM); + } + } + + public static AlbumBuilder createAlbum(android.app.Fragment fragment, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(fragment, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(fragment, StartupType.ALBUM); + } + } + + public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(activity, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(activity, StartupType.ALBUM); + } + } + + public static AlbumBuilder createAlbum(Fragment fragmentV, boolean isShowCamera, + @NonNull ImageEngine imageEngine) { + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + if (isShowCamera) { + return AlbumBuilder.with(fragmentV, StartupType.ALBUM_CAMERA); + } else { + return AlbumBuilder.with(fragmentV, StartupType.ALBUM); + } + } + + /** + * 设置fileProvider字段 + * + * @param fileProviderAuthority fileProvider字段 + * @return AlbumBuilder + */ + public AlbumBuilder setFileProviderAuthority(String fileProviderAuthority) { + Setting.fileProviderAuthority = fileProviderAuthority; + return AlbumBuilder.this; + } + + /** + * 设置选择数 + * + * @param selectorMaxCount 最大选择数 + * @return AlbumBuilder + */ + public AlbumBuilder setCount(int selectorMaxCount) { + if (Setting.complexSelector) return AlbumBuilder.this; + Setting.count = selectorMaxCount; + return AlbumBuilder.this; + } + + /** + * 设置是否使用宽高数据 + * + * @param useWidth 是否使用宽高数据,需要使用写true,不用写false。 + * true:会保证宽高数据的正确性,返回速度慢,耗时。 + * false:宽高数据为0。 + * @return AlbumBuilder + */ + public AlbumBuilder setUseWidth(boolean useWidth) { + Setting.useWidth = useWidth; + return AlbumBuilder.this; + } + + /** + * 支持复杂选择情况 + * + * @param singleType 是否只能选择一种文件类型,如用户选择视频后不可以选择图片,若false则可以同时选择 + * @param videoCount 可选择视频类型文件的最大数 + * @param pictureCount 可选择图片类型文件的最大数 + * @return + */ + public AlbumBuilder complexSelector(boolean singleType, int videoCount, int pictureCount) { + Setting.complexSelector = true; + Setting.complexSingleType = singleType; + Setting.complexVideoCount = videoCount; + Setting.complexPictureCount = pictureCount; + Setting.count = videoCount + pictureCount; + Setting.showVideo = true; + return AlbumBuilder.this; + } + + /** + * 设置相机按钮位置 + * + * @param cLocation 使用Material Design风格相机按钮 默认 BOTTOM_RIGHT + * @return AlbumBuilder + */ + public AlbumBuilder setCameraLocation(@Setting.Location int cLocation) { + Setting.cameraLocation = cLocation; + return AlbumBuilder.this; + } + + /** + * 设置显示照片的最小文件大小 + * + * @param minFileSize 最小文件大小,单位Bytes + * @return AlbumBuilder + */ + public AlbumBuilder setMinFileSize(long minFileSize) { + Setting.minSize = minFileSize; + return AlbumBuilder.this; + } + + /** + * 设置显示照片的最小宽度 + * + * @param minWidth 照片的最小宽度,单位Px + * @return AlbumBuilder + */ + public AlbumBuilder setMinWidth(int minWidth) { + Setting.minWidth = minWidth; + return AlbumBuilder.this; + } + + /** + * 设置显示照片的最小高度 + * + * @param minHeight 显示照片的最小高度,单位Px + * @return AlbumBuilder + */ + public AlbumBuilder setMinHeight(int minHeight) { + Setting.minHeight = minHeight; + return AlbumBuilder.this; + } + + /** + * 设置默认选择图片集合 + * + * @param selectedPhotos 默认选择图片集合 + * @return AlbumBuilder + */ + public AlbumBuilder setSelectedPhotos(ArrayList selectedPhotos) { + Setting.selectedPhotos.clear(); + if (selectedPhotos.isEmpty()) { + return AlbumBuilder.this; + } + Setting.selectedPhotos.addAll(selectedPhotos); + Setting.selectedOriginal = selectedPhotos.get(0).selectedOriginal; + return AlbumBuilder.this; + } + + /** + * 设置默认选择图片集合 + * + * @param selectedPhotos 默认选择图片集合 + * @param isSequentialSelectedPhotos 当传入已选中图片时,是否按照之前选中的顺序排序 + * @return AlbumBuilder + */ + public AlbumBuilder setSelectedPhotos(ArrayList selectedPhotos,boolean isSequentialSelectedPhotos) { + Setting.selectedPhotos.clear(); + Setting.isSequentialSelectedPhotos = isSequentialSelectedPhotos; + if (selectedPhotos.isEmpty()) { + return AlbumBuilder.this; + } + Setting.selectedPhotos.addAll(selectedPhotos); + Setting.selectedOriginal = selectedPhotos.get(0).selectedOriginal; + return AlbumBuilder.this; + } + + /** + * 设置默认选择图片地址集合 + * + * @param selectedPhotoPaths 默认选择图片地址集合 + * @return AlbumBuilder + * @Deprecated android 10 不推荐使用直接使用Path方式,推荐使用Photo类 + */ + @Deprecated + public AlbumBuilder setSelectedPhotoPaths(ArrayList selectedPhotoPaths) { + Setting.selectedPhotos.clear(); + ArrayList selectedPhotos = new ArrayList<>(); + for (String path : selectedPhotoPaths) { + File file = new File(path); + Uri uri = null; + if (null != mActivity && null != mActivity.get()) { + uri = UriUtils.getUri(mActivity.get(), file); + } + if (null != mFragment && null != mFragment.get()) { + uri = UriUtils.getUri(mFragment.get().getActivity(), file); + } + if (null != mFragmentV && null != mFragmentV.get()) { + uri = UriUtils.getUri(mFragmentV.get().getActivity(), file); + } + if (uri == null) { + uri = Uri.fromFile(file); + } + Photo photo = new Photo(null, uri, path, 0, 0, 0, 0, 0, 0, null); + selectedPhotos.add(photo); + } + Setting.selectedPhotos.addAll(selectedPhotos); + return AlbumBuilder.this; + } + + /** + * 设置默认选择图片地址集合 + * + * @param selectedPhotoPaths 默认选择图片地址集合 + * @param isSequentialSelectedPhotos 当传入已选中图片时,是否按照之前选中的顺序排序 + * @return AlbumBuilder + * @Deprecated android 10 不推荐使用直接使用Path方式,推荐使用Photo类 + */ + @Deprecated + public AlbumBuilder setSelectedPhotoPaths(ArrayList selectedPhotoPaths,boolean isSequentialSelectedPhotos) { + Setting.selectedPhotos.clear(); + Setting.isSequentialSelectedPhotos = isSequentialSelectedPhotos; + ArrayList selectedPhotos = new ArrayList<>(); + for (String path : selectedPhotoPaths) { + File file = new File(path); + Uri uri = null; + if (null != mActivity && null != mActivity.get()) { + uri = UriUtils.getUri(mActivity.get(), file); + } + if (null != mFragment && null != mFragment.get()) { + uri = UriUtils.getUri(mFragment.get().getActivity(), file); + } + if (null != mFragmentV && null != mFragmentV.get()) { + uri = UriUtils.getUri(mFragmentV.get().getActivity(), file); + } + if (uri == null) { + uri = Uri.fromFile(file); + } + Photo photo = new Photo(null, uri, path, 0, 0, 0, 0, 0, 0, null); + selectedPhotos.add(photo); + } + Setting.selectedPhotos.addAll(selectedPhotos); + return AlbumBuilder.this; + } + + + /** + * 原图按钮设置,不调用该方法不显示原图按钮 + * + * @param isChecked 原图选项默认状态是否为选中状态 + * @param usable 原图按钮是否可使用 + * @param unusableHint 原图按钮不可使用时给用户的文字提示 + * @return AlbumBuilder + */ + public AlbumBuilder setOriginalMenu(boolean isChecked, boolean usable, String unusableHint) { + Setting.showOriginalMenu = true; + Setting.selectedOriginal = isChecked; + Setting.originalMenuUsable = usable; + Setting.originalMenuUnusableHint = unusableHint; + return AlbumBuilder.this; + } + + + /** + * 是否显示拼图按钮 + * + * @param shouldShow 是否显示 + * @return AlbumBuilder + */ + public AlbumBuilder setPuzzleMenu(boolean shouldShow) { + Setting.showPuzzleMenu = shouldShow; + return AlbumBuilder.this; + } + + /** + * 只显示Video + * + * @return @return AlbumBuilder + */ + + public AlbumBuilder onlyVideo() { + return filter(Type.VIDEO); + } + + /** + * 过滤 + * + * @param types {@link Type} + * @return @return AlbumBuilder + */ + public AlbumBuilder filter(String... types) { + Setting.filterTypes = Arrays.asList(types); + return AlbumBuilder.this; + } + + /** + * 是否显示gif图 + * + * @param shouldShow 是否显示 + * @return @return AlbumBuilder + */ + public AlbumBuilder setGif(boolean shouldShow) { + Setting.showGif = shouldShow; + return AlbumBuilder.this; + } + + /** + * 是否显示video + * + * @param shouldShow 是否显示 + * @return @return AlbumBuilder + */ + public AlbumBuilder setVideo(boolean shouldShow) { + Setting.showVideo = shouldShow; + return AlbumBuilder.this; + } + + /** + * 显示最少多少秒的视频 + * + * @param second 秒 + * @return @return AlbumBuilder + */ + public AlbumBuilder setVideoMinSecond(int second) { + Setting.videoMinSecond = second * 1000; + return AlbumBuilder.this; + } + + /** + * 显示最多多少秒的视频 + * + * @param second 秒 + * @return @return AlbumBuilder + */ + public AlbumBuilder setVideoMaxSecond(int second) { + Setting.videoMaxSecond = second * 1000; + return AlbumBuilder.this; + } + + /** + * 相册选择页是否显示清空按钮 + * + * @param shouldShow + * @return + */ + public AlbumBuilder setCleanMenu(boolean shouldShow) { + Setting.showCleanMenu = shouldShow; + return AlbumBuilder.this; + } + + private void setSettingParams() { + switch (startupType) { + case CAMERA: + Setting.onlyStartCamera = true; + Setting.isShowCamera = true; + break; + case ALBUM: + Setting.isShowCamera = false; + break; + case ALBUM_CAMERA: + Setting.isShowCamera = true; + break; + } + if (!Setting.filterTypes.isEmpty()) { + if (Setting.isFilter(Type.GIF)) { + Setting.showGif = true; + } + if (Setting.isFilter(Type.VIDEO)) { + Setting.showVideo = true; + } + } + if (Setting.isOnlyVideo()) { + //只选择视频 暂不支持拍照/拼图等 + Setting.isShowCamera = false; + Setting.showPuzzleMenu = false; + Setting.showGif = false; + Setting.showVideo = true; + } + } + + /** + * 启动,onActivityResult方式 + * + * @param requestCode startActivityForResult的请求码 + */ + + public void start(int requestCode) { + setSettingParams(); + launchEasyPhotosActivity(requestCode); + } + + /** + * 启动,链式调用 + */ + public void start(SelectCallback callback) { + setSettingParams(); + if (null != mActivity && null != mActivity.get() && mActivity.get() instanceof FragmentActivity) { + EasyResult.get((FragmentActivity) mActivity.get()).startEasyPhoto(callback); + return; + } + if (null != mFragmentV && null != mFragmentV.get()) { + EasyResult.get(mFragmentV.get()).startEasyPhoto(callback); + return; + } + throw new RuntimeException("mActivity or mFragmentV maybe null, you can not use this " + + "method... "); + } + + /** + * 正式启动 + * + * @param requestCode startActivityForResult的请求码 + */ + private void launchEasyPhotosActivity(int requestCode) { + if (null != mActivity && null != mActivity.get()) { + EasyPhotosActivity.start(mActivity.get(), requestCode); + return; + } + if (null != mFragment && null != mFragment.get()) { + EasyPhotosActivity.start(mFragment.get(), requestCode); + return; + } + if (null != mFragmentV && null != mFragmentV.get()) { + EasyPhotosActivity.start(mFragmentV.get(), requestCode); + } + } + + /** + * 清除所有数据 + */ + private static void clear() { + Result.clear(); + Setting.clear(); + instance = null; + } + +//*********************AD************************************ + + /** + * 设置广告(不设置该选项则表示不使用广告) + * + * @param photosAdView 使用图片列表的广告View + * @param photosAdIsLoaded 图片列表广告是否加载完毕 + * @param albumItemsAdView 使用专辑项目列表的广告View + * @param albumItemsAdIsLoaded 专辑项目列表广告是否加载完毕 + * @return AlbumBuilder + */ + public AlbumBuilder setAdView(View photosAdView, boolean photosAdIsLoaded, + View albumItemsAdView, boolean albumItemsAdIsLoaded) { + Setting.photosAdView = new WeakReference(photosAdView); + Setting.albumItemsAdView = new WeakReference(albumItemsAdView); + Setting.photoAdIsOk = photosAdIsLoaded; + Setting.albumItemsAdIsOk = albumItemsAdIsLoaded; + return AlbumBuilder.this; + } + + /** + * 设置广告监听 + * 内部使用,无需关心 + * + * @param adListener 广告监听 + */ + public static void setAdListener(AdListener adListener) { + if (null == instance) return; + if (instance.startupType == StartupType.CAMERA) return; + instance.adListener = new WeakReference(adListener); + } + + /** + * 刷新图片列表广告数据 + */ + public static void notifyPhotosAdLoaded() { + if (Setting.photoAdIsOk) { + return; + } + if (null == instance) { + return; + } + if (instance.startupType == StartupType.CAMERA) { + return; + } + if (null == instance.adListener) { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (null != instance && null != instance.adListener) { + Setting.photoAdIsOk = true; + instance.adListener.get().onPhotosAdLoaded(); + } + } + }).start(); + return; + } + Setting.photoAdIsOk = true; + instance.adListener.get().onPhotosAdLoaded(); + } + + /** + * 刷新专辑项目列表广告 + */ + public static void notifyAlbumItemsAdLoaded() { + if (Setting.albumItemsAdIsOk) { + return; + } + if (null == instance) { + return; + } + if (instance.startupType == StartupType.CAMERA) { + return; + } + if (null == instance.adListener) { + new Thread(new Runnable() { + @Override + public void run() { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + if (null != instance && null != instance.adListener) { + Setting.albumItemsAdIsOk = true; + instance.adListener.get().onAlbumItemsAdLoaded(); + } + } + }).start(); + return; + } + Setting.albumItemsAdIsOk = true; + instance.adListener.get().onAlbumItemsAdLoaded(); + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java new file mode 100644 index 0000000..ef9fd8c --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/EasyPhotos.java @@ -0,0 +1,345 @@ +package com.huantansheng.easyphotos; + +import android.app.Activity; +import android.app.Fragment; +import android.content.Context; +import android.graphics.Bitmap; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; + +import com.huantansheng.easyphotos.Builder.AlbumBuilder; +import com.huantansheng.easyphotos.callback.PuzzleCallback; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.ad.AdListener; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.models.sticker.StickerModel; +import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData; +import com.huantansheng.easyphotos.ui.PuzzleActivity; +import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils; +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack; +import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils; +import com.huantansheng.easyphotos.utils.result.EasyResult; + +import java.io.File; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * EasyPhotos的启动管理器 + * Created by huan on 2017/10/18. + */ +public class EasyPhotos { + + //easyPhotos的返回数据Key + public static final String RESULT_PHOTOS = "keyOfEasyPhotosResult"; + public static final String RESULT_SELECTED_ORIGINAL = "keyOfEasyPhotosResultSelectedOriginal"; + + /** + * 预加载 + * 调不调用该方法都可以,不调用不影响EasyPhotos正常使用 + * 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快 + * 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页 + * 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用 + * + * @param cxt 上下文 + */ + public static void preLoad(Context cxt) { + AlbumModel.getInstance().query(cxt, null); + } + + /** + * 预加载 + * 调不调用该方法都可以,不调用不影响EasyPhotos正常使用 + * 第一次扫描媒体库可能会慢,调用预加载会使真正打开相册的速度加快 + * 若调用该方法,建议自行判断代码书写位置,建议在用户打开相册的3秒前调用,比如app主页面或调用相册的上一页 + * 该方法如果没有授权读取权限的话,是无效的,所以外部加不加权限控制都可以,加的话保证执行,不加也不影响程序正常使用 + * + * @param cxt 上下文 + * @param callBack 预加载完成的回调,若进行UI操作,需自行切回主线程。 + */ + public static void preLoad(Context cxt, AlbumModel.CallBack callBack) { + AlbumModel.getInstance().query(cxt, callBack); + } + + + /** + * 创建相机 + * + * @param activity 上下文 + * @param useWidth 是否使用宽高数据 + * @return AlbumBuilder + */ + public static AlbumBuilder createCamera(Activity activity, + boolean useWidth) { + return AlbumBuilder.createCamera(activity).setUseWidth(useWidth); + } + + public static AlbumBuilder createCamera(Fragment fragment, + boolean useWidth) { + return AlbumBuilder.createCamera(fragment).setUseWidth(useWidth); + } + + public static AlbumBuilder createCamera(FragmentActivity activity, + boolean useWidth) { + return AlbumBuilder.createCamera(activity).setUseWidth(useWidth); + } + + public static AlbumBuilder createCamera(androidx.fragment.app.Fragment fragmentV, + boolean useWidth) { + return AlbumBuilder.createCamera(fragmentV).setUseWidth(useWidth); + } + + /** + * 创建相册 + * + * @param activity 上下文 + * @param isShowCamera 是否显示相机按钮 + * @param useWidth 是否使用宽高数据。 + * true:会保证宽高数据的正确性,返回速度慢,耗时,尤其在华为mate30上,可能点击完成后会加载三四秒才能返回。 + * false:有宽高数据但不保证正确性,点击完成后秒回,但可能有因旋转问题导致的宽高相反的情况,以及极少数的宽高为0情况。 + * @param imageEngine 图片加载引擎的具体实现 + * @return AlbumBuilder 建造者模式配置其他选项 + */ + public static AlbumBuilder createAlbum(Activity activity, boolean isShowCamera, + boolean useWidth, @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + public static AlbumBuilder createAlbum(Fragment fragment, boolean isShowCamera, + boolean useWidth, @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(fragment, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + public static AlbumBuilder createAlbum(FragmentActivity activity, boolean isShowCamera, + boolean useWidth, @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(activity, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + public static AlbumBuilder createAlbum(androidx.fragment.app.Fragment fragmentV, + boolean isShowCamera, boolean useWidth, + @NonNull ImageEngine imageEngine) { + return AlbumBuilder.createAlbum(fragmentV, isShowCamera, imageEngine).setUseWidth(useWidth); + } + + +//*********************AD************************************ + + + /** + * 设置广告监听 + * 内部使用,无需关心 + * + * @param adListener 广告监听 + */ + public static void setAdListener(AdListener adListener) { + AlbumBuilder.setAdListener(adListener); + } + + /** + * 刷新图片列表广告数据 + */ + public static void notifyPhotosAdLoaded() { + AlbumBuilder.notifyPhotosAdLoaded(); + } + + /** + * 刷新专辑项目列表广告 + */ + public static void notifyAlbumItemsAdLoaded() { + AlbumBuilder.notifyAlbumItemsAdLoaded(); + } + + +//*************************bitmap功能***********************************/ + + /** + * 回收bitmap + * + * @param bitmap 要回收的bitmap + */ + public static void recycle(Bitmap bitmap) { + BitmapUtils.recycle(bitmap); + } + + /** + * 回收bitmap数组中的所有图片 + * + * @param bitmaps 要回收的bitmap数组 + */ + public static void recycle(Bitmap... bitmaps) { + BitmapUtils.recycle(bitmaps); + } + + /** + * 回收bitmap集合中的所有图片 + * + * @param bitmaps 要回收的bitmap集合 + */ + public static void recycle(List bitmaps) { + BitmapUtils.recycle(bitmaps); + } + + /** + * 给图片添加水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印 + * @param image 添加水印的图片 + * @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcImageWidth, + int offsetX, int offsetY, boolean addInLeft, + int orientation) { + return BitmapUtils.addWatermark(watermark, image, srcImageWidth, offsetX, offsetY, + addInLeft, orientation); + } + + /** + * 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印图片 + * @param image 要加水印的图片 + * @param srcImageWidth 水印对应的原图片宽度,即ui制作水印时参考的要添加水印的图片的宽度 + * @param text 要添加的文字 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermarkWithText(Bitmap watermark, Bitmap image, int srcImageWidth, + @NonNull String text, int offsetX, int offsetY, + boolean addInLeft, int orientation) { + return BitmapUtils.addWatermarkWithText(watermark, image, srcImageWidth, text, offsetX, + offsetY, + addInLeft, orientation); + } + + /** + * 保存Bitmap到指定文件夹 + * + * @param act 上下文 + * @param dirPath 文件夹全路径 + * @param bitmap bitmap + * @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png + * @param notifyMedia 是否更新到媒体库 + * @param callBack 保存图片后的回调,回调已经处于UI线程 + */ + public static void saveBitmapToDir(Activity act, String dirPath, String namePrefix, + Bitmap bitmap, boolean notifyMedia, + SaveBitmapCallBack callBack) { + BitmapUtils.saveBitmapToDir(act, dirPath, namePrefix, bitmap, notifyMedia, callBack); + } + + + /** + * 把View画成Bitmap + * + * @param view 要处理的View + * @return Bitmap + */ + public static Bitmap createBitmapFromView(View view) { + return BitmapUtils.createBitmapFromView(view); + } + + /** + * 启动拼图(最多对9张图片进行拼图) + * + * @param act 上下文 + * @param photos 图片集合(最多对9张图片进行拼图) + * @param puzzleSaveDirPath 拼图完成保存的文件夹全路径 + * @param puzzleSaveNamePrefix 拼图完成保存的文件名前缀,最终格式:前缀+默认生成唯一数字标识+.png + * @param requestCode 请求code + * @param replaceCustom 单击替换拼图中的某张图片时,是否以startForResult的方式启动你的自定义界面,该界面与传进来的act + * 为同一界面。false则在EasyPhotos内部完成,正常需求直接写false即可。 + * true的情况适用于:用于拼图的图片集合中包含网络图片,是在你的act界面中获取并下载的(也可以直接用网络地址,不用下载后的本地地址,也就是可以不下载下来),而非单纯本地相册。举例:你的act中有两个按钮,一个指向本地相册,一个指向网络相册,用户在该界面任意选择,选择好图片后跳转到拼图界面,用户在拼图界面点击替换按钮,将会启动一个新的act界面,这时,act只让用户在网络相册和本地相册选择一张图片,选择好执行 + * Intent intent = new Intent(); + * intent.putParcelableArrayListExtra(AlbumBuilder.RESULT_PHOTOS + * , photos); + * act.setResult(RESULT_OK,intent); 并关闭act,回到拼图界面,完成替换。 + * @param imageEngine 图片加载引擎的具体实现 + */ + + public static void startPuzzleWithPhotos(Activity act, ArrayList photos, + String puzzleSaveDirPath, + String puzzleSaveNamePrefix, int requestCode, + boolean replaceCustom, + @NonNull ImageEngine imageEngine) { + act.setResult(Activity.RESULT_OK); + PuzzleActivity.startWithPhotos(act, photos, puzzleSaveDirPath, puzzleSaveNamePrefix, + requestCode, replaceCustom, imageEngine); + } + + public static void startPuzzleWithPhotos(FragmentActivity act, ArrayList photos, + String puzzleSaveDirPath, + String puzzleSaveNamePrefix, boolean replaceCustom, + @NonNull ImageEngine imageEngine, + PuzzleCallback callback) { + act.setResult(Activity.RESULT_OK); + EasyResult.get(act).startPuzzleWithPhotos(photos, puzzleSaveDirPath, puzzleSaveNamePrefix + , replaceCustom, imageEngine, callback); + } + + + //**************更新媒体库*********************** + + /** + * 更新媒体文件到媒体库 + * + * @param cxt 上下文 + * @param filePaths 更新的文件地址 + */ + public static void notifyMedia(Context cxt, String... filePaths) { + MediaScannerConnectionUtils.refresh(cxt, filePaths); + } + + /** + * 更新媒体文件到媒体库 + * + * @param cxt 上下文 + * @param files 更新的文件 + */ + public static void notifyMedia(Context cxt, File... files) { + MediaScannerConnectionUtils.refresh(cxt, files); + } + + /** + * 更新媒体文件到媒体库 + * + * @param cxt 上下文 + * @param fileList 更新的文件地址集合 + */ + public static void notifyMedia(Context cxt, List fileList) { + MediaScannerConnectionUtils.refresh(cxt, fileList); + } + + + //*********************************贴纸*************************** + + + /** + * 添加文字贴纸的文字数据 + * + * @param textStickerData 文字贴纸的文字数据 + */ + public static void addTextStickerData(TextStickerData... textStickerData) { + StickerModel.textDataList.addAll(Arrays.asList(textStickerData)); + } + + /** + * 清空文字贴纸的数据 + */ + public static void clearTextStickerDataList() { + StickerModel.textDataList.clear(); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java new file mode 100644 index 0000000..eeae759 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/PuzzleCallback.java @@ -0,0 +1,22 @@ +package com.huantansheng.easyphotos.callback; + +import com.huantansheng.easyphotos.models.album.entity.Photo; + +/** + * PuzzleCallback + * + * @author joker + * @date 2019/4/9. + */ +public abstract class PuzzleCallback { + /** + * 选择结果回调 + * + * @param photo 返回对象:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + */ + public abstract void onResult(Photo photo); + /** + * 什么都没选,取消选择回调 + */ + public abstract void onCancel(); +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java new file mode 100644 index 0000000..c91b3a7 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/callback/SelectCallback.java @@ -0,0 +1,26 @@ +package com.huantansheng.easyphotos.callback; + +import com.huantansheng.easyphotos.models.album.entity.Photo; + +import java.util.ArrayList; + +/** + * SelectCallback + * + * @author joker + * @date 2019/4/9. + */ +public abstract class SelectCallback { + /** + * 选择结果回调 + * + * @param photos 返回对象集合:如果你需要了解图片的宽、高、大小、用户是否选中原图选项等信息,可以用这个 + * @param isOriginal 返回图片地址集合时如果你需要知道用户选择图片时是否选择了原图选项,用如下方法获取 + */ + public abstract void onResult(ArrayList photos, boolean isOriginal); + + /** + * 什么都没选,取消选择回调 + */ + public abstract void onCancel(); +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java new file mode 100644 index 0000000..92629f1 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Code.java @@ -0,0 +1,21 @@ +package com.huantansheng.easyphotos.constant; + +/** + * Code常量 + * Created by huan on 2017/10/19. + */ + +public class Code { + //相机请求码 + public static final int REQUEST_CAMERA = 11; + //权限请求码 + public static final int REQUEST_PERMISSION = 12; + //预览activity请求码 + public static final int REQUEST_PREVIEW_ACTIVITY = 13; + //请求应用详情 + public static final int REQUEST_SETTING_APP_DETAILS = 14; + //拼图activity请求吗 + public static final int REQUEST_PUZZLE = 15; + //拼图选择activity请求吗 + public static final int REQUEST_PUZZLE_SELECTOR = 16; +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java new file mode 100644 index 0000000..4502f6a --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Key.java @@ -0,0 +1,23 @@ +package com.huantansheng.easyphotos.constant; + +/** + * key的常量 + * Created by huan on 2017/10/19. + */ + +public class Key { + //预览图片的当前角标 + public static final String PREVIEW_PHOTO_INDEX = "keyOfPreviewPhotoIndex"; + //当前预览界面的专辑index + public static final String PREVIEW_ALBUM_ITEM_INDEX = "keyOfPreviewAlbumItemIndex"; + //预览界面是否点击完成 + public static final String PREVIEW_CLICK_DONE = "keyOfPreviewClickDone"; + //拼图界面图片类型,true-Photo,false-String + public static final String PUZZLE_FILE_IS_PHOTO = "keyOfPuzzleFilesTypeIsPhoto"; + //拼图界面图片结合 + public static final String PUZZLE_FILES = "keyOfPuzzleFiles"; + //拼图界面图片保存文件夹地址 + public static final String PUZZLE_SAVE_DIR = "keyOfPuzzleSaveDir"; + //拼图界面图片保存文件名前缀 + public static final String PUZZLE_SAVE_NAME_PREFIX = "keyOfPuzzleSaveNamePrefix"; +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java new file mode 100644 index 0000000..e506d0a --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/constant/Type.java @@ -0,0 +1,10 @@ +package com.huantansheng.easyphotos.constant; + +/** + * Created by huan on 2018/1/9. + */ + +public class Type { + public static final String GIF = "gif"; + public static final String VIDEO = "video"; +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java new file mode 100644 index 0000000..49226d4 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/engine/ImageEngine.java @@ -0,0 +1,63 @@ +package com.huantansheng.easyphotos.engine; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.Uri; +import android.widget.ImageView; + +import androidx.annotation.NonNull; + +/** + * 自定义图片加载方式 + * Created by huan on 2018/1/15. + */ +public interface ImageEngine { + /** + * 加载图片到ImageView + * + * @param context 上下文 + * @param uri 图片Uri + * @param imageView 加载到的ImageView + */ + //安卓10推荐uri,并且path的方式不再可用 + void loadPhoto(@NonNull Context context, @NonNull Uri uri,@NonNull ImageView imageView); + + /** + * 加载gif动图图片到ImageView,gif动图不动 + * + * @param context 上下文 + * @param gifUri gif动图路径Uri + * @param imageView 加载到的ImageView + *

+ * 备注:不支持动图显示的情况下可以不写 + */ + //安卓10推荐uri,并且path的方式不再可用 + void loadGifAsBitmap(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView); + + /** + * 加载gif动图到ImageView,gif动图动 + * + * @param context 上下文 + * @param gifUri gif动图路径Uri + * @param imageView 加载动图的ImageView + *

+ * 备注:不支持动图显示的情况下可以不写 + */ + //安卓10推荐uri,并且path的方式不再可用 + void loadGif(@NonNull Context context,@NonNull Uri gifUri,@NonNull ImageView imageView); + + /** + * 获取图片加载框架中的缓存Bitmap,不用拼图功能可以直接返回null + * + * @param context 上下文 + * @param uri 图片路径 + * @param width 图片宽度 + * @param height 图片高度 + * @return Bitmap + * @throws Exception 异常直接抛出,EasyPhotos内部处理 + */ + //安卓10推荐uri,并且path的方式不再可用 + Bitmap getCacheBitmap(@NonNull Context context,@NonNull Uri uri, int width, int height) throws Exception; + + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java new file mode 100644 index 0000000..0d624c5 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdEntity.java @@ -0,0 +1,18 @@ +package com.huantansheng.easyphotos.models.ad; + +import android.view.View; + +/** + * 广告实体 + * Created by huan on 2017/10/24. + */ + +public class AdEntity { + public View adView; + public int lineIndex; + + public AdEntity(View adView, int lineIndex) { + this.adView = adView; + this.lineIndex = lineIndex; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java new file mode 100644 index 0000000..ab5ed76 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdListener.java @@ -0,0 +1,11 @@ +package com.huantansheng.easyphotos.models.ad; + +/** + * 广告监听 + * Created by huan on 2017/10/24. + */ + +public interface AdListener { + void onPhotosAdLoaded(); + void onAlbumItemsAdLoaded(); +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java new file mode 100644 index 0000000..9b96e3d --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/ad/AdViewHolder.java @@ -0,0 +1,20 @@ +package com.huantansheng.easyphotos.models.ad; + +import androidx.recyclerview.widget.RecyclerView; +import android.view.View; +import android.widget.FrameLayout; + +import com.huantansheng.easyphotos.R; + +/** + * 广告viewolder + * Created by huan on 2017/10/28. + */ + +public class AdViewHolder extends RecyclerView.ViewHolder { + public FrameLayout adFrame; + public AdViewHolder(View itemView) { + super(itemView); + adFrame = (FrameLayout) itemView.findViewById(R.id.ad_frame_easy_photos); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java new file mode 100644 index 0000000..157d8e5 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/AlbumModel.java @@ -0,0 +1,397 @@ +package com.huantansheng.easyphotos.models.album; + +import android.Manifest; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.util.Log; + +import androidx.core.content.PermissionChecker; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Album; +import com.huantansheng.easyphotos.models.album.entity.AlbumItem; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.utils.String.StringUtils; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +/** + * 专辑模型 + * Created by huan on 2017/10/20. + *

+ * Modified by Eagle on 2018/08/31. + * 修改内容:将AlbumModel的实例化与数据查询分开 + */ +public class AlbumModel { + private static final String TAG = "AlbumModel"; + public static AlbumModel instance; + public Album album; + private String[] projections; + + private AlbumModel() { + album = new Album(); + } + + public static AlbumModel getInstance() { + if (null == instance) { + synchronized (AlbumModel.class) { + if (null == instance) { + instance = new AlbumModel(); + } + } + } + return instance; + } + + /** + * 专辑查询 + * + * @param context 调用查询方法的context + * @param callBack 查询完成后的回调 + */ + public volatile boolean canRun = true; + + public void query(Context context, final CallBack callBack) { + final Context appCxt = context.getApplicationContext(); + + /*******************************官方库的源码start*******************************/ + // if (PermissionChecker.checkSelfPermission(context, + // Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker + // .PERMISSION_GRANTED) { + // if (null != callBack) callBack.onAlbumWorkedCallBack(); + // return; + // } + /*******************************官方库的源码end*******************************/ + + + /*********************************自定义添加适配Android13相册权限start*************************/ + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (PermissionChecker.checkSelfPermission(context, + Manifest.permission.READ_MEDIA_IMAGES) != PermissionChecker.PERMISSION_GRANTED) { + if (null != callBack) + callBack.onAlbumWorkedCallBack(); + return; + } + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + if (PermissionChecker.checkSelfPermission(context, + Manifest.permission.READ_EXTERNAL_STORAGE) != PermissionChecker.PERMISSION_GRANTED) { + if (null != callBack) + callBack.onAlbumWorkedCallBack(); + return; + } + } + /*********************************自定义添加适配Android13相册权限end*************************/ + + canRun = true; + new Thread(() -> { + initAlbum(appCxt); + if (null != callBack) + callBack.onAlbumWorkedCallBack(); + }).start(); + } + + public void stopQuery() { + canRun = false; + } + + private synchronized void initAlbum(Context context) { + if (!album.isEmpty()) { + Log.e(TAG, "已经预加载过相册"); + return; + } + album.clear(); + // long now = System.currentTimeMillis(); + if (Setting.selectedPhotos.size() > Setting.count) { + throw new RuntimeException("AlbumBuilder: 默认勾选的图片张数不能大于设置的选择数!" + "|默认勾选图片张数:" + Setting.selectedPhotos.size() + "|设置的选择数:" + Setting.count); + } + boolean canReadWidth = + android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN; + // boolean isQ = android.os.Build.VERSION.SDK_INT == Build.VERSION_CODES.Q; + final String sortOrder = MediaStore.Files.FileColumns.DATE_MODIFIED + " DESC"; + + Uri contentUri; + String selection = null; + String[] selectionAllArgs = null; + + if (Setting.isOnlyVideo()) { + contentUri = MediaStore.Video.Media.getContentUri("external"); + + } else if (!Setting.showVideo) { + contentUri = MediaStore.Images.Media.getContentUri("external"); + + } else { + contentUri = MediaStore.Files.getContentUri("external"); + selection = + "(" + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?" + " OR " + MediaStore.Files.FileColumns.MEDIA_TYPE + "=?)"; + selectionAllArgs = + new String[]{String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE), + String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)}; + } + + ContentResolver contentResolver = context.getContentResolver(); + + + List projectionList = new ArrayList<>(); + projectionList.add(MediaStore.Files.FileColumns._ID); + // if (isQ) { + // projectionList.add(MediaStore.MediaColumns.RELATIVE_PATH); + // }else { + projectionList.add(MediaStore.MediaColumns.DATA); + // } + projectionList.add(MediaStore.MediaColumns.DISPLAY_NAME); + projectionList.add(MediaStore.MediaColumns.DATE_MODIFIED); + projectionList.add(MediaStore.MediaColumns.MIME_TYPE); + projectionList.add(MediaStore.MediaColumns.SIZE); + projectionList.add(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + + if (!Setting.useWidth) { + if (Setting.minWidth != 1 && Setting.minHeight != 1) + Setting.useWidth = true; + } + if (canReadWidth) { + if (Setting.useWidth) { + projectionList.add(MediaStore.MediaColumns.WIDTH); + projectionList.add(MediaStore.MediaColumns.HEIGHT); + if (!Setting.isOnlyVideo()) + projectionList.add(MediaStore.MediaColumns.ORIENTATION); + } + } + + if (Setting.showVideo) { + projectionList.add(MediaStore.MediaColumns.DURATION); + } + + projections = projectionList.toArray(new String[0]); + + Cursor cursor = contentResolver.query(contentUri, projections, selection, + selectionAllArgs, sortOrder); + if (cursor == null) { + // Log.d(TAG, "call: " + "Empty photos"); + } else if (cursor.moveToFirst()) { + String albumItem_all_name = getAllAlbumName(context); + String albumItem_video_name = + context.getString(R.string.selector_folder_video_easy_photos); + + int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + int durationCol = cursor.getColumnIndex(MediaStore.MediaColumns.DURATION); + int WidthCol = 0; + int HeightCol = 0; + int orientationCol = -1; + if (canReadWidth && Setting.useWidth) { + WidthCol = cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH); + HeightCol = cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT); + orientationCol = cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION); + } + boolean hasTime = durationCol > 0; + + do { + long id = cursor.getLong(0); + String path = cursor.getString(1); + String name = cursor.getString(2); + long dateTime = cursor.getLong(3); + String type = cursor.getString(4); + long size = cursor.getLong(5); + long duration = 0; + + + if (TextUtils.isEmpty(path) || TextUtils.isEmpty(type)) { + continue; + } + + if (size < Setting.minSize) { + continue; + } + + boolean isVideo = type.contains(Type.VIDEO);// 是否是视频 + + int width = 0; + int height = 0; + int orientation = 0; + if (isVideo) { + if (hasTime) + duration = cursor.getLong(durationCol); + if (duration <= Setting.videoMinSecond || duration >= Setting.videoMaxSecond) { + continue; + } + } else { + if (orientationCol != -1) { + orientation = cursor.getInt(orientationCol); + } + if (!Setting.showGif) { + if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) { + continue; + } + } + if (Setting.useWidth) { + if (canReadWidth) { + width = cursor.getInt(WidthCol); + height = cursor.getInt(HeightCol); + } + if (width == 0 || height == 0) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(path, options); + width = options.outWidth; + height = options.outHeight; + } + + if (orientation == 90 || orientation == 270) { + int temp = width; + width = height; + height = temp; + } + + if (width < Setting.minWidth || height < Setting.minHeight) { + continue; + } + + } + } + + Uri uri = ContentUris.withAppendedId(isVideo ? + MediaStore.Video.Media.getContentUri("external") : + MediaStore.Images.Media.getContentUri("external"), id); + + //某些机型,特定情况下三方应用或用户操作删除媒体文件时,没有通知媒体库,导致媒体库表中还有其数据,但真实文件已经不存在 + File file = new File(path); + if (!file.isFile()) { + continue; + } + + Photo imageItem = new Photo(name, uri, path, dateTime, width, height, orientation + , size, duration, type); + if (!Setting.selectedPhotos.isEmpty()) { + int selectSize = Setting.selectedPhotos.size(); + for (int i = 0; i < selectSize; i++) { + Photo selectedPhoto = Setting.selectedPhotos.get(i); + if (path.equals(selectedPhoto.path)) { + imageItem.selectedOriginal = Setting.selectedOriginal; + Result.addPhoto(imageItem); + } + } + } + + // 初始化“全部”专辑 + if (album.isEmpty()) { + // 用第一个图片作为专辑的封面 + album.addAlbumItem(albumItem_all_name, "", path, uri); + } + // 把图片全部放进“全部”专辑 + album.getAlbumItem(albumItem_all_name).addImageItem(imageItem); + + if (Setting.showVideo && isVideo && !albumItem_video_name.equals(albumItem_all_name)) { + album.addAlbumItem(albumItem_video_name, "", path, uri); + album.getAlbumItem(albumItem_video_name).addImageItem(imageItem); + } + + // 添加当前图片的专辑到专辑模型实体中 + String albumName; + String folderPath; + if (albumNameCol > 0) { + albumName = cursor.getString(albumNameCol); + folderPath = albumName; + } else { + File parentFile = new File(path).getParentFile(); + if (null == parentFile) { + continue; + } + folderPath = parentFile.getAbsolutePath(); + albumName = StringUtils.getLastPathSegment(folderPath); + } + + album.addAlbumItem(albumName, folderPath, path, uri); + album.getAlbumItem(albumName).addImageItem(imageItem); + } while (cursor.moveToNext() && canRun); + cursor.close(); + if (!Setting.selectedPhotos.isEmpty() && Setting.isSequentialSelectedPhotos) { + int selectSize = Setting.selectedPhotos.size(); + int photoSize = Result.photos.size(); + ArrayList tempList = new ArrayList<>(photoSize); + for (int i = 0; i < selectSize; i++) { + for (int j = 0; j < photoSize; j++) { + if (Result.photos.get(j).path.equals(Setting.selectedPhotos.get(i).path)) { + tempList.add(i, Result.photos.get(j)); + } + } + } + Result.photos = tempList; + } + } + // Log.d(TAG, "initAlbum: " + (System.currentTimeMillis() - now)); + } + + /** + * 获取全部专辑名 + * + * @return 专辑名 + */ + public String getAllAlbumName(Context context) { + String albumItem_all_name = + context.getString(R.string.selector_folder_all_video_photo_easy_photos); + if (Setting.isOnlyVideo()) { + albumItem_all_name = context.getString(R.string.selector_folder_video_easy_photos); + } else if (!Setting.showVideo) { + //不显示视频 + albumItem_all_name = context.getString(R.string.selector_folder_all_easy_photos); + } + return albumItem_all_name; + } + + /** + * 获取当前专辑项目的图片集 + * + * @return 当前专辑项目的图片集 + */ + public ArrayList getCurrAlbumItemPhotos(int currAlbumItemIndex) { + return album.getAlbumItem(currAlbumItemIndex).photos; + } + + /** + * 获取专辑项目集 + * + * @return 专辑项目集 + */ + public ArrayList getAlbumItems() { + return album.albumItems; + } + + public interface CallBack { + void onAlbumWorkedCallBack(); + } + + + /** + * 获取projections + */ + public String[] getProjections() { + if (null == projections || projections.length == 0) { + if (Setting.useWidth) { + projections = new String[]{MediaStore.Files.FileColumns._ID, + MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.MIME_TYPE, + MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME + , MediaStore.MediaColumns.WIDTH, MediaStore.MediaColumns.HEIGHT, + MediaStore.MediaColumns.ORIENTATION}; + } else { + projections = new String[]{MediaStore.Files.FileColumns._ID, + MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.DISPLAY_NAME, + MediaStore.MediaColumns.DATE_MODIFIED, MediaStore.MediaColumns.MIME_TYPE, + MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.BUCKET_DISPLAY_NAME}; + } + } + return projections; + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java new file mode 100644 index 0000000..d3aa004 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Album.java @@ -0,0 +1,49 @@ +package com.huantansheng.easyphotos.models.album.entity; + +import android.net.Uri; + +import java.util.ArrayList; +import java.util.LinkedHashMap; + +/** + * 专辑模型实体类 + * Created by huan on 2017/10/20. + */ + +public class Album { + public ArrayList albumItems; + private LinkedHashMap hasAlbumItems;//用于记录专辑项目 + + public Album() { + albumItems = new ArrayList<>(); + hasAlbumItems = new LinkedHashMap<>(); + } + + private void addAlbumItem(AlbumItem albumItem) { + this.hasAlbumItems.put(albumItem.name, albumItem); + this.albumItems.add(albumItem); + } + + public void addAlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) { + if (null == hasAlbumItems.get(name)) { + addAlbumItem(new AlbumItem(name, folderPath, coverImagePath,coverImageUri)); + } + } + + public AlbumItem getAlbumItem(String name) { + return hasAlbumItems.get(name); + } + + public AlbumItem getAlbumItem(int currIndex) { + return albumItems.get(currIndex); + } + + public boolean isEmpty() { + return albumItems.isEmpty(); + } + + public void clear() { + albumItems.clear(); + hasAlbumItems.clear(); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java new file mode 100644 index 0000000..2208275 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/AlbumItem.java @@ -0,0 +1,34 @@ +package com.huantansheng.easyphotos.models.album.entity; + +import android.net.Uri; + +import java.util.ArrayList; + +/** + * 专辑项目实体类 + * Created by huan on 2017/10/20. + */ + +public class AlbumItem { + public String name; + public String folderPath; + public String coverImagePath; + public Uri coverImageUri; + public ArrayList photos; + + AlbumItem(String name, String folderPath, String coverImagePath, Uri coverImageUri) { + this.name = name; + this.folderPath = folderPath; + this.coverImagePath = coverImagePath; + this.coverImageUri = coverImageUri; + this.photos = new ArrayList<>(); + } + + public void addImageItem(Photo imageItem) { + this.photos.add(imageItem); + } + + public void addImageItem(int index, Photo imageItem) { + this.photos.add(index, imageItem); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java new file mode 100644 index 0000000..626e5f3 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/album/entity/Photo.java @@ -0,0 +1,114 @@ +package com.huantansheng.easyphotos.models.album.entity; + +import android.net.Uri; +import android.os.Parcel; +import android.os.Parcelable; +import android.util.Log; + +/** + * 图片item实体类 + * Created by huan on 2017/10/20. + */ + +public class Photo implements Parcelable { + private static final String TAG = "Photo"; + public Uri uri;//图片Uri + public String name;//图片名称 + public String path;//图片全路径 + public String type;//图片类型 + public int width;//图片宽度 + public int height;//图片高度 + public int orientation;//图片旋转角度 + public long size;//图片文件大小,单位:Bytes + public long duration;//视频时长,单位:毫秒 + public long time;//图片拍摄的时间戳,单位:毫秒 + public boolean selected;//是否被选中,内部使用,无需关心 + public boolean selectedOriginal;//用户选择时是否选择了原图选项 + + public Photo(String name, Uri uri, String path, long time, int width, int height,int orientation, long size, long duration, String type) { + this.name = name; + this.uri = uri; + this.path = path; + this.time = time; + this.width = width; + this.height = height; + this.orientation = orientation; + this.type = type; + this.size = size; + this.duration = duration; + this.selected = false; + this.selectedOriginal = false; + } + + @Override + public boolean equals(Object o) { + try { + Photo other = (Photo) o; + return this.path.equalsIgnoreCase(other.path); + } catch (ClassCastException e) { + Log.e(TAG, "equals: " + Log.getStackTraceString(e)); + } + return super.equals(o); + } + + @Override + public String toString() { + return "Photo{" + + "name='" + name + '\'' + + ", uri='" + uri.toString() + '\'' + + ", path='" + path + '\'' + + ", time=" + time + '\'' + + ", minWidth=" + width + '\'' + + ", minHeight=" + height + + ", orientation=" + orientation + + '}'; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeParcelable(this.uri, flags); + dest.writeString(this.name); + dest.writeString(this.path); + dest.writeString(this.type); + dest.writeInt(this.width); + dest.writeInt(this.height); + dest.writeInt(this.orientation); + dest.writeLong(this.size); + dest.writeLong(this.duration); + dest.writeLong(this.time); + dest.writeByte(this.selected ? (byte) 1 : (byte) 0); + dest.writeByte(this.selectedOriginal ? (byte) 1 : (byte) 0); + } + + protected Photo(Parcel in) { + this.uri = in.readParcelable(Uri.class.getClassLoader()); + this.name = in.readString(); + this.path = in.readString(); + this.type = in.readString(); + this.width = in.readInt(); + this.height = in.readInt(); + this.orientation = in.readInt(); + this.size = in.readLong(); + this.duration = in.readLong(); + this.time = in.readLong(); + this.selected = in.readByte() != 0; + this.selectedOriginal = in.readByte() != 0; + } + + public static final Creator CREATOR = new Creator() { + @Override + public Photo createFromParcel(Parcel source) { + return new Photo(source); + } + + @Override + public Photo[] newArray(int size) { + return new Photo[size]; + } + }; +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java new file mode 100644 index 0000000..2e41bae --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Area.java @@ -0,0 +1,65 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; + +import java.util.List; + +/** + * @author wupanjie + */ +public interface Area { + float left(); + + float top(); + + float right(); + + float bottom(); + + float centerX(); + + float centerY(); + + float width(); + + float height(); + + PointF getCenterPoint(); + + boolean contains(PointF point); + + boolean contains(float x, float y); + + boolean contains(Line line); + + Path getAreaPath(); + + RectF getAreaRect(); + + List getLines(); + + PointF[] getHandleBarPoints(Line line); + + float radian(); + + void setRadian(float radian); + + float getPaddingLeft(); + + float getPaddingTop(); + + float getPaddingRight(); + + float getPaddingBottom(); + + void setPadding(float padding); + + void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom); +} + + + + + diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java new file mode 100644 index 0000000..2e462bb --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/DegreeSeekBar.java @@ -0,0 +1,353 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.annotation.TargetApi; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Rect; +import android.os.Build; +import androidx.core.content.ContextCompat; +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.huantansheng.easyphotos.R; + +/** + * @author wupanjie + */ +public class DegreeSeekBar extends View { + private static final String TAG = "DegreeSeekBar"; + private Paint mTextPaint; + private Paint mCirclePaint; + private Paint.FontMetricsInt mFontMetrics; + private int mBaseLine; + private float[] mTextWidths; + + private final Rect mCanvasClipBounds = new Rect(); + + private ScrollingListener mScrollingListener; + private float mLastTouchedPosition; + + private Paint mPointPaint; + private float mPointMargin; + + private boolean mScrollStarted; + private int mTotalScrollDistance; + + private Path mIndicatorPath = new Path(); + + private int mCurrentDegrees = 0; + private int mPointCount = 51; + + private int mPointColor; + private int mTextColor; + private int mCenterTextColor; + + //阻尼系数的倒数 + private float mDragFactor = 2.1f; + + private int mMinReachableDegrees = -45; + private int mMaxReachableDegrees = 45; + + private String suffix = ""; + + public DegreeSeekBar(Context context) { + this(context, null); + } + + public DegreeSeekBar(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public DegreeSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + private void init() { + mPointColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary); + mTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary); + mCenterTextColor = ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent); + + mPointPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mPointPaint.setStyle(Paint.Style.STROKE); + mPointPaint.setColor(mPointColor); + mPointPaint.setStrokeWidth(2); + + mTextPaint = new Paint(); + mTextPaint.setColor(mTextColor); + mTextPaint.setStyle(Paint.Style.STROKE); + mTextPaint.setAntiAlias(true); + mTextPaint.setTextSize(24f); + mTextPaint.setTextAlign(Paint.Align.LEFT); + mTextPaint.setAlpha(100); + + mFontMetrics = mTextPaint.getFontMetricsInt(); + + mTextWidths = new float[1]; + mTextPaint.getTextWidths("0", mTextWidths); + + mCirclePaint = new Paint(); + mCirclePaint.setStyle(Paint.Style.FILL); + mCirclePaint.setAlpha(255); + mCirclePaint.setAntiAlias(true); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + mPointMargin = (float) w / mPointCount; + + mBaseLine = (h - mFontMetrics.bottom + mFontMetrics.top) / 2 - mFontMetrics.top; + + mIndicatorPath.moveTo(w / 2, h / 2 + mFontMetrics.top / 2 - 18); + mIndicatorPath.rLineTo(-8, -8); + mIndicatorPath.rLineTo(16, 0); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + mLastTouchedPosition = event.getX(); + if (!mScrollStarted) { + mScrollStarted = true; + if (mScrollingListener != null) { + mScrollingListener.onScrollStart(); + } + } + break; + case MotionEvent.ACTION_UP: + mScrollStarted = false; + if (mScrollingListener != null) { + mScrollingListener.onScrollEnd(); + } + invalidate(); + break; + case MotionEvent.ACTION_MOVE: + float distance = event.getX() - mLastTouchedPosition; + if (mCurrentDegrees >= mMaxReachableDegrees && distance < 0) { + mCurrentDegrees = mMaxReachableDegrees; + invalidate(); + break; + } + if (mCurrentDegrees <= mMinReachableDegrees && distance > 0) { + mCurrentDegrees = mMinReachableDegrees; + invalidate(); + break; + } + if (distance != 0) { + onScrollEvent(event, distance); + } + break; + } + return true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.getClipBounds(mCanvasClipBounds); + + int zeroIndex = mPointCount / 2 + (0 - mCurrentDegrees) / 2; + mPointPaint.setColor(mPointColor); + for (int i = 0; i < mPointCount; i++) { + + if (i > zeroIndex - Math.abs(mMinReachableDegrees) / 2 + && i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2 + && mScrollStarted) { + mPointPaint.setAlpha(255); + } else { + mPointPaint.setAlpha(100); + } + + if (i > mPointCount / 2 - 8 + && i < mPointCount / 2 + 8 + && i > zeroIndex - Math.abs(mMinReachableDegrees) / 2 + && i < zeroIndex + Math.abs(mMaxReachableDegrees) / 2) { + if (mScrollStarted) { + mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 255 / 8); + } else { + mPointPaint.setAlpha(Math.abs(mPointCount / 2 - i) * 100 / 8); + } + } + + canvas.drawPoint(mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin, + mCanvasClipBounds.centerY(), mPointPaint); + + if (mCurrentDegrees != 0 && i == zeroIndex) { + if (mScrollStarted) { + mTextPaint.setAlpha(255); + } else { + mTextPaint.setAlpha(192); + } + mPointPaint.setStrokeWidth(4); + canvas.drawPoint((mCanvasClipBounds.centerX() + (i - mPointCount / 2) * mPointMargin), + mCanvasClipBounds.centerY(), mPointPaint); + mPointPaint.setStrokeWidth(2); + mTextPaint.setAlpha(100); + } + } + + for (int i = -180; i <= 180; i += 15) { + if (i >= mMinReachableDegrees && i <= mMaxReachableDegrees) { + drawDegreeText(i, canvas, true); + } else { + drawDegreeText(i, canvas, false); + } + } + + mTextPaint.setTextSize(28f); + mTextPaint.setAlpha(255); + mTextPaint.setColor(mCenterTextColor); + + if (mCurrentDegrees >= 10) { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine, + mTextPaint); + } else if (mCurrentDegrees <= -10) { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2 * 3, mBaseLine, + mTextPaint); + } else if (mCurrentDegrees < 0) { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0], mBaseLine, + mTextPaint); + } else { + canvas.drawText(mCurrentDegrees + suffix, getWidth() / 2 - mTextWidths[0] / 2, mBaseLine, + mTextPaint); + } + + mTextPaint.setAlpha(100); + mTextPaint.setTextSize(24f); + mTextPaint.setColor(mTextColor); + //画中心三角 + mCirclePaint.setColor(mCenterTextColor); + canvas.drawPath(mIndicatorPath, mCirclePaint); + mCirclePaint.setColor(mCenterTextColor); + } + + private void drawDegreeText(int degrees, Canvas canvas, boolean canReach) { + if (canReach) { + if (mScrollStarted) { + mTextPaint.setAlpha(Math.min(255, Math.abs(degrees - mCurrentDegrees) * 255 / 15)); + if (Math.abs(degrees - mCurrentDegrees) <= 7) { + mTextPaint.setAlpha(0); + } + } else { + mTextPaint.setAlpha(100); + if (Math.abs(degrees - mCurrentDegrees) <= 7) { + mTextPaint.setAlpha(0); + } + } + } else { + mTextPaint.setAlpha(100); + } + if (degrees == 0) { + if (Math.abs(mCurrentDegrees) >= 15 && !mScrollStarted) { + mTextPaint.setAlpha(180); + } + canvas.drawText("0°", + getWidth() / 2 - mTextWidths[0] / 2 - mCurrentDegrees / 2 * mPointMargin, + getHeight() / 2 - 10, mTextPaint); + } else { + canvas.drawText(degrees + suffix, getWidth() / 2 + mPointMargin * degrees / 2 + - mTextWidths[0] / 2 * 3 + - mCurrentDegrees / 2 * mPointMargin, getHeight() / 2 - 10, mTextPaint); + } + } + + private void onScrollEvent(MotionEvent event, float distance) { + mTotalScrollDistance -= distance; + postInvalidate(); + mLastTouchedPosition = event.getX(); + mCurrentDegrees = (int) ((mTotalScrollDistance * mDragFactor) / mPointMargin); + if (mScrollingListener != null) { + mScrollingListener.onScroll(mCurrentDegrees); + } + } + + public void setDegreeRange(int min, int max) { + if (min > max) { + Log.e(TAG, "setDegreeRange: error, max must greater than min"); + } else { + mMinReachableDegrees = min; + mMaxReachableDegrees = max; + + if (mCurrentDegrees > mMaxReachableDegrees || mCurrentDegrees < mMinReachableDegrees) { + mCurrentDegrees = (mMinReachableDegrees + mMaxReachableDegrees) / 2; + } + mTotalScrollDistance = (int) (mCurrentDegrees * mPointMargin / mDragFactor); + invalidate(); + } + } + + public void setCurrentDegrees(int degrees) { + if (degrees <= mMaxReachableDegrees && degrees >= mMinReachableDegrees) { + mCurrentDegrees = degrees; + mTotalScrollDistance = (int) (degrees * mPointMargin / mDragFactor); + invalidate(); + } + } + + public void setScrollingListener(ScrollingListener scrollingListener) { + mScrollingListener = scrollingListener; + } + + public int getPointColor() { + return mPointColor; + } + + public void setPointColor(int pointColor) { + mPointColor = pointColor; + mPointPaint.setColor(mPointColor); + postInvalidate(); + } + + public int getTextColor() { + return mTextColor; + } + + public void setTextColor(int textColor) { + mTextColor = textColor; + mTextPaint.setColor(textColor); + postInvalidate(); + } + + public int getCenterTextColor() { + return mCenterTextColor; + } + + public void setCenterTextColor(int centerTextColor) { + mCenterTextColor = centerTextColor; + postInvalidate(); + } + + public float getDragFactor() { + return mDragFactor; + } + + public void setDragFactor(float dragFactor) { + mDragFactor = dragFactor; + } + + public void setSuffix(String suffix) { + this.suffix = suffix; + } + + public interface ScrollingListener { + + void onScrollStart(); + + void onScroll(int currentDegrees); + + void onScrollEnd(); + } +} + diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java new file mode 100644 index 0000000..1c95d41 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/Line.java @@ -0,0 +1,52 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.PointF; + +/** + * @author wupanjie + */ +public interface Line { + enum Direction { + HORIZONTAL, VERTICAL + } + + float length(); + + PointF startPoint(); + + PointF endPoint(); + + Line lowerLine(); + + Line upperLine(); + + Line attachStartLine(); + + Line attachEndLine(); + + void setLowerLine(Line lowerLine); + + void setUpperLine(Line upperLine); + + Direction direction(); + + float slope(); + + boolean contains(float x, float y, float extra); + + void prepareMove(); + + boolean move(float offset, float extra); + + void update(float layoutWidth, float layoutHeight); + + float minX(); + + float maxX(); + + float minY(); + + float maxY(); + + void offset(float x, float y); +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java new file mode 100644 index 0000000..cdd8d0f --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/MatrixUtils.java @@ -0,0 +1,167 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.Matrix; +import android.graphics.RectF; +import android.graphics.drawable.Drawable; + +import java.util.Arrays; + +import static java.lang.Math.round; + +/** + * some useful matrix operation methods + * + * @author wupanjie + */ +public class MatrixUtils { + private MatrixUtils() { + //no instance + } + + private static final float[] sMatrixValues = new float[9]; + private static final Matrix sTempMatrix = new Matrix(); + + /** + * This method calculates scale value for given Matrix object. + */ + public static float getMatrixScale(Matrix matrix) { + return (float) Math.sqrt(Math.pow(getMatrixValue(matrix, Matrix.MSCALE_X), 2) + Math.pow( + getMatrixValue(matrix, Matrix.MSKEW_Y), 2)); + } + + /** + * This method calculates rotation angle for given Matrix object. + */ + public static float getMatrixAngle(Matrix matrix) { + return (float) -(Math.atan2(getMatrixValue(matrix, Matrix.MSKEW_X), + getMatrixValue(matrix, Matrix.MSCALE_X)) * (180 / Math.PI)); + } + + public static float getMatrixValue(Matrix matrix, int valueIndex) { + matrix.getValues(sMatrixValues); + return sMatrixValues[valueIndex]; + } + + public static float getMinMatrixScale(PuzzlePiece piece) { + if (piece != null) { + + sTempMatrix.reset(); + sTempMatrix.setRotate(-piece.getMatrixAngle()); + + float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect()); + + sTempMatrix.mapPoints(unrotatedCropBoundsCorners); + + RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners); + + return Math.max(unrotatedCropRect.width() / piece.getWidth(), + unrotatedCropRect.height() / piece.getHeight()); + } + + return 1f; + } + + //判断剪裁框是否在图片内 + static boolean judgeIsImageContainsBorder(PuzzlePiece piece, float rotateDegrees) { + sTempMatrix.reset(); + sTempMatrix.setRotate(-rotateDegrees); + float[] unrotatedWrapperCorner = new float[8]; + float[] unrotateBorderCorner = new float[8]; + sTempMatrix.mapPoints(unrotatedWrapperCorner, piece.getCurrentDrawablePoints()); + sTempMatrix.mapPoints(unrotateBorderCorner, getCornersFromRect(piece.getArea().getAreaRect())); + + return trapToRect(unrotatedWrapperCorner).contains(trapToRect(unrotateBorderCorner)); + } + + static float[] calculateImageIndents(PuzzlePiece piece) { + if (piece == null) return new float[] { 0, 0, 0, 0, 0, 0, 0, 0 }; + + sTempMatrix.reset(); + sTempMatrix.setRotate(-piece.getMatrixAngle()); + + final float[] currentImageCorners = piece.getCurrentDrawablePoints(); + final float[] unrotatedImageCorners = + Arrays.copyOf(currentImageCorners, currentImageCorners.length); + final float[] unrotatedCropBoundsCorners = getCornersFromRect(piece.getArea().getAreaRect()); + + sTempMatrix.mapPoints(unrotatedImageCorners); + sTempMatrix.mapPoints(unrotatedCropBoundsCorners); + + RectF unrotatedImageRect = trapToRect(unrotatedImageCorners); + RectF unrotatedCropRect = trapToRect(unrotatedCropBoundsCorners); + + float deltaLeft = unrotatedImageRect.left - unrotatedCropRect.left; + float deltaTop = unrotatedImageRect.top - unrotatedCropRect.top; + float deltaRight = unrotatedImageRect.right - unrotatedCropRect.right; + float deltaBottom = unrotatedImageRect.bottom - unrotatedCropRect.bottom; + + float indents[] = new float[4]; + + indents[0] = (deltaLeft > 0) ? deltaLeft : 0; + indents[1] = (deltaTop > 0) ? deltaTop : 0; + indents[2] = (deltaRight < 0) ? deltaRight : 0; + indents[3] = (deltaBottom < 0) ? deltaBottom : 0; + + sTempMatrix.reset(); + sTempMatrix.setRotate(piece.getMatrixAngle()); + sTempMatrix.mapPoints(indents); + + return indents; + } + + //计算包含给出点的最小矩形 + public static RectF trapToRect(float[] array) { + RectF r = new RectF(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY); + int length = array.length; + for (int i = 1; i < length; i += 2) { + float x = round(array[i - 1] * 10) / 10.f; + float y = round(array[i] * 10) / 10.f; + r.left = (x < r.left) ? x : r.left; + r.top = (y < r.top) ? y : r.top; + r.right = (x > r.right) ? x : r.right; + r.bottom = (y > r.bottom) ? y : r.bottom; + } + r.sort(); + return r; + } + + public static float[] getCornersFromRect(RectF r) { + return new float[] { + r.left, r.top, r.right, r.top, r.right, r.bottom, r.left, r.bottom + }; + } + + public static Matrix generateMatrix(PuzzlePiece piece, float extra) { + return generateMatrix(piece.getArea(), piece.getDrawable(), extra); + } + + public static Matrix generateMatrix(Area area, Drawable drawable, float extraSize) { + return generateCenterCropMatrix(area, drawable.getIntrinsicWidth(), + drawable.getIntrinsicHeight(), extraSize); + } + + private static Matrix generateCenterCropMatrix(Area area, int width, int height, + float extraSize) { + final RectF rectF = area.getAreaRect(); + + Matrix matrix = new Matrix(); + + float offsetX = rectF.centerX() - width / 2; + float offsetY = rectF.centerY() - height / 2; + + matrix.postTranslate(offsetX, offsetY); + + float scale; + + if (width * rectF.height() > rectF.width() * height) { + scale = (rectF.height() + extraSize) / height; + } else { + scale = (rectF.width() + extraSize) / width; + } + + matrix.postScale(scale, scale, rectF.centerX(), rectF.centerY()); + + return matrix; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java new file mode 100644 index 0000000..555153b --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleLayout.java @@ -0,0 +1,88 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.graphics.RectF; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public interface PuzzleLayout { + void setOuterBounds(RectF bounds); + + void layout(); + + int getAreaCount(); + + List getOuterLines(); + + List getLines(); + + Area getOuterArea(); + + void update(); + + void reset(); + + Area getArea(int position); + + float width(); + + float height(); + + void setPadding(float padding); + + float getPadding(); + + float getRadian(); + + void setRadian(float radian); + + Info generateInfo(); + + void setColor(int color); + + int getColor(); + + class Info { + public static final int TYPE_STRAIGHT = 0; + public static final int TYPE_SLANT = 1; + + public int type; + public ArrayList steps; + public ArrayList lineInfos; + public float padding; + public float radian; + public int color; + } + + class Step { + public static final int ADD_LINE = 0; + public static final int ADD_CROSS = 1; + public static final int CUT_EQUAL_PART_ONE = 2; + public static final int CUT_EQUAL_PART_TWO = 3; + public static final int CUT_SPIRAL = 4; + + public int type; + public int direction; + public int position; + public int part; + public int hSize; + public int vSize; + } + + class LineInfo { + public float startX; + public float startY; + public float endX; + public float endY; + + public LineInfo(Line line){ + startX = line.startPoint().x; + startY = line.startPoint().y; + endX = line.endPoint().x; + endY = line.endPoint().y; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java new file mode 100644 index 0000000..aaa6024 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzlePiece.java @@ -0,0 +1,432 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.animation.ValueAnimator; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Xfermode; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.DecelerateInterpolator; + +import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.calculateImageIndents; +import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.getMinMatrixScale; +import static com.huantansheng.easyphotos.models.puzzle.MatrixUtils.judgeIsImageContainsBorder; + + +/** + * @author wupanjie + */ +@SuppressWarnings("WeakerAccess") public class PuzzlePiece { + private static Xfermode SRC_IN = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN); + + private Drawable drawable; + private Matrix matrix; + private Matrix previousMatrix; + private Area area; + private Rect drawableBounds; + private float[] drawablePoints; + private float[] mappedDrawablePoints; + + private float previousMoveX; + private float previousMoveY; + + private final RectF mappedBounds; + private final PointF centerPoint; + private final PointF mappedCenterPoint; + + private ValueAnimator animator; + private int duration = 300; + private Matrix tempMatrix; + + PuzzlePiece(Drawable drawable, Area area, Matrix matrix) { + this.drawable = drawable; + this.area = area; + this.matrix = matrix; + this.previousMatrix = new Matrix(); + this.drawableBounds = new Rect(0, 0, getWidth(), getHeight()); + this.drawablePoints = new float[] { + 0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight() + }; + this.mappedDrawablePoints = new float[8]; + + this.mappedBounds = new RectF(); + this.centerPoint = new PointF(area.centerX(), area.centerY()); + this.mappedCenterPoint = new PointF(); + + this.animator = ValueAnimator.ofFloat(0f, 1f); + this.animator.setInterpolator(new DecelerateInterpolator()); + + this.tempMatrix = new Matrix(); + } + + void draw(Canvas canvas) { + draw(canvas, 255, true); + } + + void draw(Canvas canvas, int alpha) { + draw(canvas, alpha, false); + } + + private void draw(Canvas canvas, int alpha, boolean needClip) { + if (drawable instanceof BitmapDrawable){ + int saved = canvas.saveLayer(null, null, Canvas.ALL_SAVE_FLAG); + + Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); + Paint paint= ((BitmapDrawable) drawable).getPaint(); + + paint.setColor(Color.WHITE); + paint.setAlpha(alpha); + if (needClip) { + canvas.drawPath(area.getAreaPath(), paint); + paint.setXfermode(SRC_IN); + } + canvas.drawBitmap(bitmap,matrix,paint); + paint.setXfermode(null); + + canvas.restoreToCount(saved); + }else { + canvas.save(); + if (needClip) { + canvas.clipPath(area.getAreaPath()); + } + canvas.concat(matrix); + drawable.setBounds(drawableBounds); + drawable.setAlpha(alpha); + drawable.draw(canvas); + + canvas.restore(); + } + } + + public Area getArea() { + return area; + } + + public void setDrawable(Drawable drawable) { + this.drawable = drawable; + this.drawableBounds = new Rect(0, 0, getWidth(), getHeight()); + this.drawablePoints = new float[] { + 0f, 0f, getWidth(), 0f, getWidth(), getHeight(), 0f, getHeight() + }; + } + + public Drawable getDrawable() { + return drawable; + } + + public int getWidth() { + return drawable.getIntrinsicWidth(); + } + + public int getHeight() { + return drawable.getIntrinsicHeight(); + } + + public boolean contains(float x, float y) { + return area.contains(x, y); + } + + public boolean contains(Line line) { + return area.contains(line); + } + + public Rect getDrawableBounds() { + return drawableBounds; + } + + void setPreviousMoveX(float previousMoveX) { + this.previousMoveX = previousMoveX; + } + + void setPreviousMoveY(float previousMoveY) { + this.previousMoveY = previousMoveY; + } + + private RectF getCurrentDrawableBounds() { + matrix.mapRect(mappedBounds, new RectF(drawableBounds)); + return mappedBounds; + } + + private PointF getCurrentDrawableCenterPoint() { + getCurrentDrawableBounds(); + mappedCenterPoint.x = mappedBounds.centerX(); + mappedCenterPoint.y = mappedBounds.centerY(); + return mappedCenterPoint; + } + + public PointF getAreaCenterPoint() { + centerPoint.x = area.centerX(); + centerPoint.y = area.centerY(); + return centerPoint; + } + + private float getMatrixScale() { + return MatrixUtils.getMatrixScale(matrix); + } + + float getMatrixAngle() { + return MatrixUtils.getMatrixAngle(matrix); + } + + float[] getCurrentDrawablePoints() { + matrix.mapPoints(mappedDrawablePoints, drawablePoints); + return mappedDrawablePoints; + } + + boolean isFilledArea() { + RectF bounds = getCurrentDrawableBounds(); + return !(bounds.left > area.left() + || bounds.top > area.top() + || bounds.right < area.right() + || bounds.bottom < area.bottom()); + } + + boolean canFilledArea() { + float scale = MatrixUtils.getMatrixScale(matrix); + float minScale = getMinMatrixScale(this); + return scale >= minScale; + } + + void record() { + previousMatrix.set(matrix); + } + + void translate(float offsetX, float offsetY) { + matrix.set(previousMatrix); + postTranslate(offsetX, offsetY); + } + + private void zoom(float scaleX, float scaleY, PointF midPoint) { + matrix.set(previousMatrix); + postScale(scaleX, scaleY, midPoint); + } + + void zoomAndTranslate(float scaleX, float scaleY, PointF midPoint, float offsetX, float offsetY) { + matrix.set(previousMatrix); + postTranslate(offsetX, offsetY); + postScale(scaleX, scaleY, midPoint); + } + + void set(Matrix matrix) { + this.matrix.set(matrix); + moveToFillArea(null); + } + + void postTranslate(float x, float y) { + this.matrix.postTranslate(x, y); + } + + void postScale(float scaleX, float scaleY, PointF midPoint) { + this.matrix.postScale(scaleX, scaleY, midPoint.x, midPoint.y); + } + + void postFlipVertically() { + this.matrix.postScale(1, -1, area.centerX(), area.centerY()); + } + + void postFlipHorizontally() { + this.matrix.postScale(-1, 1, area.centerX(), area.centerY()); + } + + void postRotate(float degree) { + this.matrix.postRotate(degree, area.centerX(), area.centerY()); + + float minScale = getMinMatrixScale(this); + if (getMatrixScale() < minScale) { + final PointF midPoint = new PointF(); + midPoint.set(getCurrentDrawableCenterPoint()); + + postScale(minScale / getMatrixScale(), minScale / getMatrixScale(), midPoint); + } + + if (!judgeIsImageContainsBorder(this, getMatrixAngle())) { + final float[] imageIndents = calculateImageIndents(this); + float deltaX = -(imageIndents[0] + imageIndents[2]); + float deltaY = -(imageIndents[1] + imageIndents[3]); + + postTranslate(deltaX, deltaY); + } + } + + private void animateTranslate(final View view, final float translateX, final float translateY) { + animator.end(); + animator.removeAllUpdateListeners(); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float x = translateX * (float) animation.getAnimatedValue(); + float y = translateY * (float) animation.getAnimatedValue(); + + translate(x, y); + view.invalidate(); + } + }); + animator.setDuration(duration); + animator.start(); + } + + void moveToFillArea(final View view) { + if (isFilledArea()) return; + record(); + + RectF rectF = getCurrentDrawableBounds(); + float offsetX = 0f; + float offsetY = 0f; + + if (rectF.left > area.left()) { + offsetX = area.left() - rectF.left; + } + + if (rectF.top > area.top()) { + offsetY = area.top() - rectF.top; + } + + if (rectF.right < area.right()) { + offsetX = area.right() - rectF.right; + } + + if (rectF.bottom < area.bottom()) { + offsetY = area.bottom() - rectF.bottom; + } + + if (view == null) { + postTranslate(offsetX, offsetY); + } else { + animateTranslate(view, offsetX, offsetY); + } + } + + void fillArea(final View view, boolean quick) { + if (isFilledArea()) return; + record(); + + final float startScale = getMatrixScale(); + final float endScale = getMinMatrixScale(this); + + final PointF midPoint = new PointF(); + midPoint.set(getCurrentDrawableCenterPoint()); + + tempMatrix.set(matrix); + tempMatrix.postScale(endScale / startScale, endScale / startScale, midPoint.x, midPoint.y); + + RectF rectF = new RectF(drawableBounds); + tempMatrix.mapRect(rectF); + + float offsetX = 0f; + float offsetY = 0f; + + if (rectF.left > area.left()) { + offsetX = area.left() - rectF.left; + } + + if (rectF.top > area.top()) { + offsetY = area.top() - rectF.top; + } + + if (rectF.right < area.right()) { + offsetX = area.right() - rectF.right; + } + + if (rectF.bottom < area.bottom()) { + offsetY = area.bottom() - rectF.bottom; + } + + final float translateX = offsetX; + final float translateY = offsetY; + + animator.end(); + animator.removeAllUpdateListeners(); + animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + float value = (float) animation.getAnimatedValue(); + float scale = (startScale + (endScale - startScale) * value) / startScale; + float x = translateX * value; + float y = translateY * value; + + zoom(scale, scale, midPoint); + postTranslate(x, y); + view.invalidate(); + } + }); + + if (quick) { + animator.setDuration(0); + } else { + animator.setDuration(duration); + } + animator.start(); + } + + void updateWith(final MotionEvent event, final Line line) { + float offsetX = (event.getX() - previousMoveX) / 2; + float offsetY = (event.getY() - previousMoveY) / 2; + + if (!canFilledArea()) { + final Area area = getArea(); + float deltaScale = getMinMatrixScale(this) / getMatrixScale(); + postScale(deltaScale, deltaScale, area.getCenterPoint()); + record(); + + previousMoveX = event.getX(); + previousMoveY = event.getY(); + } + + if (line.direction() == Line.Direction.HORIZONTAL) { + translate(0, offsetY); + } else if (line.direction() == Line.Direction.VERTICAL) { + translate(offsetX, 0); + } + + final RectF rectF = getCurrentDrawableBounds(); + final Area area = getArea(); + float moveY = 0f; + + if (rectF.top > area.top()) { + moveY = area.top() - rectF.top; + } + + if (rectF.bottom < area.bottom()) { + moveY = area.bottom() - rectF.bottom; + } + + float moveX = 0f; + + if (rectF.left > area.left()) { + moveX = area.left() - rectF.left; + } + + if (rectF.right < area.right()) { + moveX = area.right() - rectF.right; + } + + if (moveX != 0 || moveY != 0) { + previousMoveX = event.getX(); + previousMoveY = event.getY(); + postTranslate(moveX, moveY); + record(); + } + } + + public void setArea(Area area) { + this.area = area; + } + + boolean isAnimateRunning() { + return animator.isRunning(); + } + + void setAnimateDuration(int duration) { + this.duration = duration; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java new file mode 100644 index 0000000..fc436dd --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleUtils.java @@ -0,0 +1,94 @@ +package com.huantansheng.easyphotos.models.puzzle; + + +import com.huantansheng.easyphotos.models.puzzle.template.slant.OneSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.slant.SlantLayoutHelper; +import com.huantansheng.easyphotos.models.puzzle.template.slant.ThreeSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.slant.TwoSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.EightStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.FiveStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.FourStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.NineStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.OneStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.SevenStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.SixStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.StraightLayoutHelper; +import com.huantansheng.easyphotos.models.puzzle.template.straight.ThreeStraightLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.TwoStraightLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class PuzzleUtils { + private static final String TAG = "PuzzleUtils"; + + private PuzzleUtils() { + //no instance + } + + public static PuzzleLayout getPuzzleLayout(int type, int borderSize, int themeId) { + if (type == 0) { + switch (borderSize) { + case 1: + return new OneSlantLayout(themeId); + case 2: + return new TwoSlantLayout(themeId); + case 3: + return new ThreeSlantLayout(themeId); + default: + return new OneSlantLayout(themeId); + } + } else { + switch (borderSize) { + case 1: + return new OneStraightLayout(themeId); + case 2: + return new TwoStraightLayout(themeId); + case 3: + return new ThreeStraightLayout(themeId); + case 4: + return new FourStraightLayout(themeId); + case 5: + return new FiveStraightLayout(themeId); + case 6: + return new SixStraightLayout(themeId); + case 7: + return new SevenStraightLayout(themeId); + case 8: + return new EightStraightLayout(themeId); + case 9: + return new NineStraightLayout(themeId); + default: + return new OneStraightLayout(themeId); + } + } + } + + public static List getAllPuzzleLayouts() { + List puzzleLayouts = new ArrayList<>(); + //slant layout + puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(2)); + puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(3)); + + // straight layout + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(2)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(3)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(4)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(5)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(6)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(7)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(8)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(9)); + return puzzleLayouts; + } + + public static List getPuzzleLayouts(int pieceCount) { + List puzzleLayouts = new ArrayList<>(); + puzzleLayouts.addAll(SlantLayoutHelper.getAllThemeLayout(pieceCount)); + puzzleLayouts.addAll(StraightLayoutHelper.getAllThemeLayout(pieceCount)); + return puzzleLayouts; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java new file mode 100644 index 0000000..638fbfa --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/PuzzleView.java @@ -0,0 +1,791 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.PointF; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; + +import androidx.core.content.ContextCompat; + +import android.util.AttributeSet; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; + +import com.huantansheng.easyphotos.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class PuzzleView extends View { + private static final String TAG = "SlantPuzzleView"; + + private enum ActionMode { + NONE, + DRAG, + ZOOM, + MOVE, + SWAP + } + + private ActionMode currentMode = ActionMode.NONE; + + private List puzzlePieces = new ArrayList<>(); + + private List needChangePieces = new ArrayList<>(); + private PuzzleLayout puzzleLayout; + + private RectF bounds; + private int lineSize; + + private int duration; + private Line handlingLine; + + private PuzzlePiece handlingPiece; + private PuzzlePiece replacePiece; + private PuzzlePiece previousHandlingPiece; + + private Paint linePaint; + private Paint selectedAreaPaint; + private Paint handleBarPaint; + + private float downX; + private float downY; + private float previousDistance; + private PointF midPoint; + private boolean needDrawLine; + + private boolean needDrawOuterLine; + private boolean touchEnable = true; + private int lineColor; + + private int selectedLineColor; + private int handleBarColor; + private float piecePadding; + private float pieceRadian; + + private boolean needResetPieceMatrix = true; + + private OnPieceSelectedListener onPieceSelectedListener; + + private Runnable switchToSwapAction = new Runnable() { + @Override + public void run() { + currentMode = ActionMode.SWAP; + invalidate(); + } + }; + + public PuzzleView(Context context) { + this(context, null); + } + + public PuzzleView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public PuzzleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(context, attrs); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PuzzleView); + lineSize = ta.getInt(R.styleable.PuzzleView_line_size, 4); + lineColor = ta.getColor(R.styleable.PuzzleView_line_color, + ContextCompat.getColor(getContext(), R.color.easy_photos_fg_primary)); + selectedLineColor = + ta.getColor(R.styleable.PuzzleView_selected_line_color, + ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent)); + handleBarColor = + ta.getColor(R.styleable.PuzzleView_handle_bar_color, + ContextCompat.getColor(getContext(), R.color.easy_photos_fg_accent)); + piecePadding = ta.getDimensionPixelSize(R.styleable.PuzzleView_piece_padding, 0); + needDrawLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_line, false); + needDrawOuterLine = ta.getBoolean(R.styleable.PuzzleView_need_draw_outer_line, false); + duration = ta.getInt(R.styleable.PuzzleView_animation_duration, 300); + pieceRadian = ta.getFloat(R.styleable.PuzzleView_radian, 0f); + ta.recycle(); + + bounds = new RectF(); + + // init some paint + linePaint = new Paint(); + linePaint.setAntiAlias(true); + linePaint.setColor(lineColor); + linePaint.setStrokeWidth(lineSize); + linePaint.setStyle(Paint.Style.STROKE); + linePaint.setStrokeJoin(Paint.Join.ROUND); + linePaint.setStrokeCap(Paint.Cap.SQUARE); + + selectedAreaPaint = new Paint(); + selectedAreaPaint.setAntiAlias(true); + selectedAreaPaint.setStyle(Paint.Style.STROKE); + selectedAreaPaint.setStrokeJoin(Paint.Join.ROUND); + selectedAreaPaint.setStrokeCap(Paint.Cap.ROUND); + selectedAreaPaint.setColor(selectedLineColor); + selectedAreaPaint.setStrokeWidth(lineSize); + + handleBarPaint = new Paint(); + handleBarPaint.setAntiAlias(true); + handleBarPaint.setStyle(Paint.Style.FILL); + handleBarPaint.setColor(handleBarColor); + handleBarPaint.setStrokeWidth(lineSize * 3); + + midPoint = new PointF(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + resetPuzzleBounds(); + + if (puzzlePieces.size() != 0) { + int size = puzzlePieces.size(); + for (int i = 0; i < size; i++) { + PuzzlePiece piece = puzzlePieces.get(i); + piece.setArea(puzzleLayout.getArea(i)); + if (needResetPieceMatrix) { + piece.set(MatrixUtils.generateMatrix(piece, 0f)); + } else { + piece.fillArea(this, true); + } + } + } + invalidate(); + } + + private void resetPuzzleBounds() { + bounds.left = getPaddingLeft(); + bounds.top = getPaddingTop(); + bounds.right = getWidth() - getPaddingRight(); + bounds.bottom = getHeight() - getPaddingBottom(); + + if (puzzleLayout != null) { + puzzleLayout.reset(); + puzzleLayout.setOuterBounds(bounds); + puzzleLayout.layout(); + puzzleLayout.setPadding(piecePadding); + puzzleLayout.setRadian(pieceRadian); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (puzzleLayout == null) { + return; + } + + linePaint.setStrokeWidth(lineSize); + selectedAreaPaint.setStrokeWidth(lineSize); + handleBarPaint.setStrokeWidth(lineSize * 3); + + // draw pieces + int count = puzzleLayout.getAreaCount(); + for (int i = 0; i < count; i++) { + if (i >= puzzlePieces.size()) { + break; + } + + PuzzlePiece piece = puzzlePieces.get(i); + + if (piece == handlingPiece && currentMode == ActionMode.SWAP) { + continue; + } + + if (puzzlePieces.size() > i) { + piece.draw(canvas); + } + } + + // draw outer bounds + if (needDrawOuterLine) { + for (Line outerLine : puzzleLayout.getOuterLines()) { + drawLine(canvas, outerLine); + } + } + + // draw slant lines + if (needDrawLine) { + for (Line line : puzzleLayout.getLines()) { + drawLine(canvas, line); + } + } + + // draw selected area + if (handlingPiece != null && currentMode != ActionMode.SWAP) { + drawSelectedArea(canvas, handlingPiece); + } + + // draw swap piece + if (handlingPiece != null && currentMode == ActionMode.SWAP) { + handlingPiece.draw(canvas, 128); + if (replacePiece != null) { + drawSelectedArea(canvas, replacePiece); + } + } + } + + private void drawSelectedArea(Canvas canvas, PuzzlePiece piece) { + final Area area = piece.getArea(); + // draw select area + canvas.drawPath(area.getAreaPath(), selectedAreaPaint); + + // draw handle bar + for (Line line : area.getLines()) { + if (puzzleLayout.getLines().contains(line)) { + PointF[] handleBarPoints = area.getHandleBarPoints(line); + canvas.drawLine(handleBarPoints[0].x, handleBarPoints[0].y, handleBarPoints[1].x, + handleBarPoints[1].y, handleBarPaint); + canvas.drawCircle(handleBarPoints[0].x, handleBarPoints[0].y, lineSize * 3 / 2, + handleBarPaint); + canvas.drawCircle(handleBarPoints[1].x, handleBarPoints[1].y, lineSize * 3 / 2, + handleBarPaint); + } + } + } + + private void drawLine(Canvas canvas, Line line) { + canvas.drawLine(line.startPoint().x, line.startPoint().y, line.endPoint().x, + line.endPoint().y, + linePaint); + } + + public void setPuzzleLayout(PuzzleLayout puzzleLayout) { + clearPieces(); + + this.puzzleLayout = puzzleLayout; + + this.puzzleLayout.setOuterBounds(bounds); + this.puzzleLayout.layout(); + + invalidate(); + } + + public PuzzleLayout getPuzzleLayout() { + return this.puzzleLayout; + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + if (!touchEnable) { + return super.onTouchEvent(event); + } + switch (event.getAction() & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + downX = event.getX(); + downY = event.getY(); + + decideActionMode(event); + prepareAction(event); + break; + + case MotionEvent.ACTION_POINTER_DOWN: + previousDistance = calculateDistance(event); + calculateMidPoint(event, midPoint); + + decideActionMode(event); + break; + + case MotionEvent.ACTION_MOVE: + performAction(event); + + if ((Math.abs(event.getX() - downX) > 10 || Math.abs(event.getY() - downY) > 10) + && currentMode != ActionMode.SWAP) { + removeCallbacks(switchToSwapAction); + } + + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + finishAction(event); + currentMode = ActionMode.NONE; + removeCallbacks(switchToSwapAction); + break; + } + + invalidate(); + return true; + } + + // 决定应该执行什么Action + private void decideActionMode(MotionEvent event) { + for (PuzzlePiece piece : puzzlePieces) { + if (piece.isAnimateRunning()) { + currentMode = ActionMode.NONE; + return; + } + } + + if (event.getPointerCount() == 1) { + handlingLine = findHandlingLine(); + if (handlingLine != null) { + currentMode = ActionMode.MOVE; + } else { + handlingPiece = findHandlingPiece(); + + if (handlingPiece != null) { + currentMode = ActionMode.DRAG; + + postDelayed(switchToSwapAction, 500); + } + } + } else if (event.getPointerCount() > 1) { + if (handlingPiece != null + && handlingPiece.contains(event.getX(1), event.getY(1)) + && currentMode == ActionMode.DRAG) { + currentMode = ActionMode.ZOOM; + } + } + } + + // 执行Action前的准备工作 + @SuppressWarnings("unused") + private void prepareAction(MotionEvent event) { + switch (currentMode) { + case NONE: + break; + case DRAG: + handlingPiece.record(); + break; + case ZOOM: + handlingPiece.record(); + break; + case MOVE: + handlingLine.prepareMove(); + needChangePieces.clear(); + needChangePieces.addAll(findNeedChangedPieces()); + for (PuzzlePiece piece : needChangePieces) { + piece.record(); + piece.setPreviousMoveX(downX); + piece.setPreviousMoveY(downY); + } + break; + } + } + + // 执行Action + private void performAction(MotionEvent event) { + switch (currentMode) { + case NONE: + break; + case DRAG: + dragPiece(handlingPiece, event); + break; + case ZOOM: + zoomPiece(handlingPiece, event); + break; + case SWAP: + dragPiece(handlingPiece, event); + replacePiece = findReplacePiece(event); + break; + case MOVE: + moveLine(handlingLine, event); + break; + } + } + + // 结束Action + private void finishAction(MotionEvent event) { + switch (currentMode) { + case NONE: + break; + case DRAG: + if (handlingPiece != null && !handlingPiece.isFilledArea()) { + handlingPiece.moveToFillArea(this); + } + + if (previousHandlingPiece == handlingPiece + && Math.abs(downX - event.getX()) < 3 + && Math.abs(downY - event.getY()) < 3) { + + handlingPiece = null; + } + + // trigger listener + if (onPieceSelectedListener != null) { + onPieceSelectedListener.onPieceSelected(handlingPiece, + puzzlePieces.indexOf(handlingPiece)); + } + + previousHandlingPiece = handlingPiece; + break; + case ZOOM: + if (handlingPiece != null && !handlingPiece.isFilledArea()) { + if (handlingPiece.canFilledArea()) { + handlingPiece.moveToFillArea(this); + } else { + handlingPiece.fillArea(this, false); + } + } + previousHandlingPiece = handlingPiece; + break; + case MOVE: + break; + case SWAP: + if (handlingPiece != null && replacePiece != null) { + Drawable temp = handlingPiece.getDrawable(); + + handlingPiece.setDrawable(replacePiece.getDrawable()); + replacePiece.setDrawable(temp); + + handlingPiece.fillArea(this, true); + replacePiece.fillArea(this, true); + + handlingPiece = null; + replacePiece = null; + previousHandlingPiece = null; + // trigger listener + if (onPieceSelectedListener != null) { + onPieceSelectedListener.onPieceSelected(null, + 0); + } + } + break; + } + + handlingLine = null; + needChangePieces.clear(); + } + + private void moveLine(Line line, MotionEvent event) { + if (line == null || event == null) return; + + boolean needUpdate; + if (line.direction() == Line.Direction.HORIZONTAL) { + needUpdate = line.move(event.getY() - downY, 80); + } else { + needUpdate = line.move(event.getX() - downX, 80); + } + + if (needUpdate) { + puzzleLayout.update(); + updatePiecesInArea(line, event); + } + } + + private void updatePiecesInArea(Line line, MotionEvent event) { + int size = needChangePieces.size(); + for (int i = 0; i < size; i++) { + needChangePieces.get(i).updateWith(event, line); + } + } + + private void zoomPiece(PuzzlePiece piece, MotionEvent event) { + if (piece == null || event == null || event.getPointerCount() < 2) return; + float scale = calculateDistance(event) / previousDistance; + piece.zoomAndTranslate(scale, scale, midPoint, event.getX() - downX, event.getY() - downY); + } + + private void dragPiece(PuzzlePiece piece, MotionEvent event) { + if (piece == null || event == null) return; + piece.translate(event.getX() - downX, event.getY() - downY); + } + + public void replace(Bitmap bitmap) { + replace(new BitmapDrawable(getResources(), bitmap)); + } + + public void replace(final Drawable bitmapDrawable) { + post(new Runnable() { + @Override + public void run() { + if (handlingPiece == null) { + return; + } + + handlingPiece.setDrawable(bitmapDrawable); + handlingPiece.set(MatrixUtils.generateMatrix(handlingPiece, 0f)); + + postInvalidate(); + } + }); + } + + public void flipVertically() { + if (handlingPiece == null) { + return; + } + + handlingPiece.postFlipVertically(); + handlingPiece.record(); + + invalidate(); + } + + public void flipHorizontally() { + if (handlingPiece == null) { + return; + } + + handlingPiece.postFlipHorizontally(); + handlingPiece.record(); + + invalidate(); + } + + public void rotate(float degree) { + if (handlingPiece == null) { + return; + } + + handlingPiece.postRotate(degree); + handlingPiece.record(); + + invalidate(); + } + + private PuzzlePiece findHandlingPiece() { + for (PuzzlePiece piece : puzzlePieces) { + if (piece.contains(downX, downY)) { + return piece; + } + } + return null; + } + + private Line findHandlingLine() { + for (Line line : puzzleLayout.getLines()) { + if (line.contains(downX, downY, 40)) { + return line; + } + } + return null; + } + + private PuzzlePiece findReplacePiece(MotionEvent event) { + for (PuzzlePiece piece : puzzlePieces) { + if (piece.contains(event.getX(), event.getY())) { + return piece; + } + } + return null; + } + + private List findNeedChangedPieces() { + if (handlingLine == null) return new ArrayList<>(); + + List needChanged = new ArrayList<>(); + + for (PuzzlePiece piece : puzzlePieces) { + if (piece.contains(handlingLine)) { + needChanged.add(piece); + } + } + + return needChanged; + } + + private float calculateDistance(MotionEvent event) { + float x = event.getX(0) - event.getX(1); + float y = event.getY(0) - event.getY(1); + + return (float) Math.sqrt(x * x + y * y); + } + + private void calculateMidPoint(MotionEvent event, PointF point) { + point.x = (event.getX(0) + event.getX(1)) / 2; + point.y = (event.getY(0) + event.getY(1)) / 2; + } + + public void reset() { + clearPieces(); + if (puzzleLayout != null) { + puzzleLayout.reset(); + } + } + + public void clearPieces() { + handlingLine = null; + handlingPiece = null; + replacePiece = null; + needChangePieces.clear(); + puzzlePieces.clear(); + } + + public void addPieces(List bitmaps) { + for (Bitmap bitmap : bitmaps) { + addPiece(bitmap); + } + + postInvalidate(); + } + + public void addPiece(Bitmap bitmap) { + BitmapDrawable bitmapDrawable = new BitmapDrawable(getResources(), bitmap); + bitmapDrawable.setAntiAlias(true); + bitmapDrawable.setFilterBitmap(true); + + addPiece(bitmapDrawable); + } + + public void addPiece(Drawable drawable) { + int position = puzzlePieces.size(); + + if (position >= puzzleLayout.getAreaCount()) { + Log.e(TAG, "addPiece: can not add more. the current puzzle layout can contains " + + puzzleLayout.getAreaCount() + + " puzzle piece."); + return; + } + + final Area area = puzzleLayout.getArea(position); + area.setPadding(piecePadding); + + PuzzlePiece piece = new PuzzlePiece(drawable, area, new Matrix()); + + final Matrix matrix = MatrixUtils.generateMatrix(area, drawable, 0f); + piece.set(matrix); + + piece.setAnimateDuration(duration); + + puzzlePieces.add(piece); + + setPiecePadding(piecePadding); + setPieceRadian(pieceRadian); + + invalidate(); + } + + public void setAnimateDuration(int duration) { + this.duration = duration; + for (PuzzlePiece piece : puzzlePieces) { + piece.setAnimateDuration(duration); + } + } + + public boolean isNeedDrawLine() { + return needDrawLine; + } + + public void setNeedDrawLine(boolean needDrawLine) { + this.needDrawLine = needDrawLine; + handlingPiece = null; + previousHandlingPiece = null; + invalidate(); + } + + public boolean isNeedDrawOuterLine() { + return needDrawOuterLine; + } + + public void setNeedDrawOuterLine(boolean needDrawOuterLine) { + this.needDrawOuterLine = needDrawOuterLine; + invalidate(); + } + + public int getLineColor() { + return lineColor; + } + + public void setLineColor(int lineColor) { + this.lineColor = lineColor; + this.linePaint.setColor(lineColor); + invalidate(); + } + + public int getLineSize() { + return lineSize; + } + + public void setLineSize(int lineSize) { + this.lineSize = lineSize; + invalidate(); + } + + public int getSelectedLineColor() { + return selectedLineColor; + } + + public void setSelectedLineColor(int selectedLineColor) { + this.selectedLineColor = selectedLineColor; + this.selectedAreaPaint.setColor(selectedLineColor); + invalidate(); + } + + public int getHandleBarColor() { + return handleBarColor; + } + + public void setHandleBarColor(int handleBarColor) { + this.handleBarColor = handleBarColor; + this.handleBarPaint.setColor(handleBarColor); + invalidate(); + } + + public boolean isTouchEnable() { + return touchEnable; + } + + public void setTouchEnable(boolean touchEnable) { + this.touchEnable = touchEnable; + } + + public void clearHandling() { + handlingPiece = null; + handlingLine = null; + replacePiece = null; + previousHandlingPiece = null; + needChangePieces.clear(); + } + + public void setPiecePadding(float padding) { + this.piecePadding = padding; + if (puzzleLayout != null) { + puzzleLayout.setPadding(padding); + } + + invalidate(); + } + + public void setPieceRadian(float radian) { + this.pieceRadian = radian; + if (puzzleLayout != null) { + puzzleLayout.setRadian(radian); + } + + invalidate(); + } + + @Override + public void setBackgroundColor(int color) { + super.setBackgroundColor(color); + if (puzzleLayout != null) { + puzzleLayout.setColor(color); + } + } + + public void setNeedResetPieceMatrix(boolean needResetPieceMatrix) { + this.needResetPieceMatrix = needResetPieceMatrix; + } + + public float getPiecePadding() { + return piecePadding; + } + + public float getPieceRadian() { + return pieceRadian; + } + + public void setOnPieceSelectedListener(OnPieceSelectedListener onPieceSelectedListener) { + this.onPieceSelectedListener = onPieceSelectedListener; + } + + public interface OnPieceSelectedListener { + void onPieceSelected(PuzzlePiece piece, int position); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java new file mode 100644 index 0000000..f50ea9e --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/SquarePuzzleView.java @@ -0,0 +1,32 @@ +package com.huantansheng.easyphotos.models.puzzle; + +import android.content.Context; +import android.util.AttributeSet; + +/** + * @author wupanjie + */ +public class SquarePuzzleView extends PuzzleView { + public SquarePuzzleView(Context context) { + super(context); + } + + public SquarePuzzleView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SquarePuzzleView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + int width = getMeasuredWidth(); + int height = getMeasuredHeight(); + int length = width > height ? height : width; + + setMeasuredDimension(length, length); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java new file mode 100644 index 0000000..3cf1253 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/CrossoverPointF.java @@ -0,0 +1,34 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.PointF; + +/** + * 两条线的交点 + * + * @author wupanjie + */ +class CrossoverPointF extends PointF { + SlantLine horizontal; + SlantLine vertical; + + CrossoverPointF() { + + } + + CrossoverPointF(float x, float y) { + this.x = x; + this.y = y; + } + + CrossoverPointF(SlantLine horizontal, SlantLine vertical) { + this.horizontal = horizontal; + this.vertical = vertical; + } + + void update() { + if (horizontal == null || vertical == null){ + return; + } + SlantUtils.intersectionOfLines(this, horizontal, vertical); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java new file mode 100644 index 0000000..0766ff7 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantArea.java @@ -0,0 +1,294 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; + + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.distance; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.getPoint; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines; + + +/** + * @author wupanjie + */ +class SlantArea implements Area { + SlantLine lineLeft; + SlantLine lineTop; + SlantLine lineRight; + SlantLine lineBottom; + + CrossoverPointF leftTop; + CrossoverPointF leftBottom; + CrossoverPointF rightTop; + CrossoverPointF rightBottom; + + private PointF tempPoint; + + private float paddingLeft; + private float paddingTop; + private float paddingRight; + private float paddingBottom; + private float radian; + + private Path areaPath = new Path(); + private RectF areaRect = new RectF(); + private PointF[] handleBarPoints = new PointF[2]; + + SlantArea() { + handleBarPoints[0] = new PointF(); + handleBarPoints[1] = new PointF(); + + leftTop = new CrossoverPointF(); + leftBottom = new CrossoverPointF(); + rightTop = new CrossoverPointF(); + rightBottom = new CrossoverPointF(); + + tempPoint = new PointF(); + } + + SlantArea(SlantArea src) { + this(); + this.lineLeft = src.lineLeft; + this.lineTop = src.lineTop; + this.lineRight = src.lineRight; + this.lineBottom = src.lineBottom; + + this.leftTop = src.leftTop; + this.leftBottom = src.leftBottom; + this.rightTop = src.rightTop; + this.rightBottom = src.rightBottom; + + updateCornerPoints(); + } + + @Override + public float left() { + return Math.min(leftTop.x, leftBottom.x) + paddingLeft; + } + + @Override + public float top() { + return Math.min(leftTop.y, rightTop.y) + paddingTop; + } + + @Override + public float right() { + return Math.max(rightTop.x, rightBottom.x) - paddingRight; + } + + @Override + public float bottom() { + return Math.max(leftBottom.y, rightBottom.y) - paddingBottom; + } + + @Override + public float centerX() { + return (left() + right()) / 2; + } + + @Override + public float centerY() { + return (top() + bottom()) / 2; + } + + @Override + public float width() { + return right() - left(); + } + + @Override + public float height() { + return bottom() - top(); + } + + @Override + public PointF getCenterPoint() { + return new PointF(centerX(), centerY()); + } + + public Path getAreaPath() { + areaPath.reset(); + + if (radian > 0) { + float tempRatio = radian / distance(leftTop, leftBottom); + getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(paddingLeft, paddingTop); + areaPath.moveTo(tempPoint.x, tempPoint.y); + + tempRatio = radian / distance(leftTop, rightTop); + getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(paddingLeft, paddingTop); + areaPath.quadTo(leftTop.x + paddingLeft, leftTop.y + paddingTop, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, leftTop, rightTop, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(-paddingRight, paddingTop); + areaPath.lineTo(tempPoint.x, tempPoint.y); + + tempRatio = radian / distance(rightTop, rightBottom); + getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(-paddingRight, paddingTop); + areaPath.quadTo(rightTop.x - paddingLeft, rightTop.y + paddingTop, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, rightTop, rightBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(-paddingRight, -paddingBottom); + areaPath.lineTo(tempPoint.x, tempPoint.y); + + tempRatio = 1 - radian / distance(leftBottom, rightBottom); + getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(-paddingRight, -paddingBottom); + areaPath.quadTo(rightBottom.x - paddingRight, rightBottom.y - paddingTop, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, leftBottom, rightBottom, Line.Direction.HORIZONTAL, tempRatio); + tempPoint.offset(paddingLeft, -paddingBottom); + areaPath.lineTo(tempPoint.x, tempPoint.y); + + tempRatio = 1 - radian / distance(leftTop, leftBottom); + getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(paddingLeft, -paddingBottom); + areaPath.quadTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom, tempPoint.x, tempPoint.y); + + tempRatio = 1 - tempRatio; + getPoint(tempPoint, leftTop, leftBottom, Line.Direction.VERTICAL, tempRatio); + tempPoint.offset(paddingLeft, paddingTop); + areaPath.lineTo(tempPoint.x, tempPoint.y); + } else { + areaPath.moveTo(leftTop.x + paddingLeft, leftTop.y + paddingTop); + areaPath.lineTo(rightTop.x - paddingRight, rightTop.y + paddingTop); + areaPath.lineTo(rightBottom.x - paddingRight, rightBottom.y - paddingBottom); + areaPath.lineTo(leftBottom.x + paddingLeft, leftBottom.y - paddingBottom); + areaPath.lineTo(leftTop.x + paddingLeft, leftTop.y + paddingTop); + } + return areaPath; + } + + @Override + public RectF getAreaRect() { + areaRect.set(left(), top(), right(), bottom()); + return areaRect; + } + + public boolean contains(float x, float y) { + return SlantUtils.contains(this, x, y); + } + + @Override + public boolean contains(Line line) { + return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line; + } + + @Override + public boolean contains(PointF point) { + return contains(point.x, point.y); + } + + @Override + public List getLines() { + return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom); + } + + @Override + public PointF[] getHandleBarPoints(Line line) { + if (line == lineLeft) { + getPoint(handleBarPoints[0], leftTop, leftBottom, line.direction(), 0.25f); + getPoint(handleBarPoints[1], leftTop, leftBottom, line.direction(), 0.75f); + handleBarPoints[0].offset(paddingLeft, 0); + handleBarPoints[1].offset(paddingLeft, 0); + } else if (line == lineTop) { + getPoint(handleBarPoints[0], leftTop, rightTop, line.direction(), 0.25f); + getPoint(handleBarPoints[1], leftTop, rightTop, line.direction(), 0.75f); + handleBarPoints[0].offset(0, paddingTop); + handleBarPoints[1].offset(0, paddingTop); + } else if (line == lineRight) { + getPoint(handleBarPoints[0], rightTop, rightBottom, line.direction(), 0.25f); + getPoint(handleBarPoints[1], rightTop, rightBottom, line.direction(), 0.75f); + handleBarPoints[0].offset(-paddingRight, 0); + handleBarPoints[1].offset(-paddingRight, 0); + } else if (line == lineBottom) { + getPoint(handleBarPoints[0], leftBottom, rightBottom, line.direction(), 0.25f); + getPoint(handleBarPoints[1], leftBottom, rightBottom, line.direction(), 0.75f); + handleBarPoints[0].offset(0, -paddingBottom); + handleBarPoints[1].offset(0, -paddingBottom); + } + return handleBarPoints; + } + + @Override + public float radian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + } + + @Override + public float getPaddingLeft() { + return paddingLeft; + } + + @Override + public float getPaddingTop() { + return paddingTop; + } + + @Override + public float getPaddingRight() { + return paddingRight; + } + + @Override + public float getPaddingBottom() { + return paddingBottom; + } + + @Override + public void setPadding(float padding) { + setPadding(padding, padding, padding, padding); + } + + @Override + public void setPadding(float paddingLeft, float paddingTop, float paddingRight, float paddingBottom) { + this.paddingLeft = paddingLeft; + this.paddingTop = paddingTop; + this.paddingRight = paddingRight; + this.paddingBottom = paddingBottom; + } + + void updateCornerPoints() { + intersectionOfLines(leftTop, lineLeft, lineTop); + intersectionOfLines(leftBottom, lineLeft, lineBottom); + intersectionOfLines(rightTop, lineRight, lineTop); + intersectionOfLines(rightBottom, lineRight, lineBottom); + } + + static class AreaComparator implements Comparator { + + @Override + public int compare(SlantArea one, SlantArea two) { + if (one.leftTop.y < two.leftTop.y) { + return -1; + } else if (one.leftTop.y == two.leftTop.y) { + if (one.leftTop.x < two.leftTop.x) { + return -1; + } else { + return 1; + } + } else { + return 1; + } + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java new file mode 100644 index 0000000..de54dde --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantLine.java @@ -0,0 +1,173 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.PointF; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.intersectionOfLines; +import static java.lang.Math.max; +import static java.lang.Math.min; +import static java.lang.Math.pow; +import static java.lang.Math.sqrt; + +/** + * 分为两种斜线,横谢线和竖线线 + * 横斜线-->start为左边的点,end为右边的点 + * 竖斜线-->start为上面的点,end为下面的点 + * + * @author wupanjie + */ +class SlantLine implements Line { + CrossoverPointF start; + CrossoverPointF end; + + // 移动前的点 + private PointF previousStart = new PointF(); + private PointF previousEnd = new PointF(); + + public final Line.Direction direction; + + SlantLine attachLineStart; + SlantLine attachLineEnd; + + Line upperLine; + Line lowerLine; + + SlantLine(Line.Direction direction) { + this.direction = direction; + } + + SlantLine(CrossoverPointF start, CrossoverPointF end, Line.Direction direction) { + this.start = start; + this.end = end; + this.direction = direction; + } + + public float length() { + return (float) sqrt(pow(end.x - start.x, 2) + pow(end.y - start.y, 2)); + } + + @Override + public PointF startPoint() { + return start; + } + + @Override + public PointF endPoint() { + return end; + } + + @Override + public Line lowerLine() { + return lowerLine; + } + + @Override + public Line upperLine() { + return upperLine; + } + + @Override + public Line attachStartLine() { + return attachLineStart; + } + + @Override + public Line attachEndLine() { + return attachLineEnd; + } + + @Override + public void setLowerLine(Line lowerLine) { + this.lowerLine = lowerLine; + } + + @Override + public void setUpperLine(Line upperLine) { + this.upperLine = upperLine; + } + + @Override + public Direction direction() { + return direction; + } + + @Override + public float slope() { + return SlantUtils.calculateSlope(this); + } + + public boolean contains(float x, float y, float extra) { + return SlantUtils.contains(this, x, y, extra); + } + + @Override + public boolean move(float offset, float extra) { + if (direction == Line.Direction.HORIZONTAL) { + if (previousStart.y + offset < lowerLine.maxY() + extra + || previousStart.y + offset > upperLine.minY() - extra + || previousEnd.y + offset < lowerLine.maxY() + extra + || previousEnd.y + offset > upperLine.minY() - extra) { + return false; + } + + start.y = previousStart.y + offset; + end.y = previousEnd.y + offset; + } else { + if (previousStart.x + offset < lowerLine.maxX() + extra + || previousStart.x + offset > upperLine.minX() - extra + || previousEnd.x + offset < lowerLine.maxX() + extra + || previousEnd.x + offset > upperLine.minX() - extra) { + return false; + } + + start.x = previousStart.x + offset; + end.x = previousEnd.x + offset; + } + + return true; + } + + @Override + public void prepareMove() { + previousStart.set(start); + previousEnd.set(end); + } + + @Override + public void update(float layoutWidth, float layoutHeight) { + intersectionOfLines(start, this, attachLineStart); + intersectionOfLines(end, this, attachLineEnd); + } + + @Override + public float minX() { + return min(start.x, end.x); + } + + @Override + public float maxX() { + return max(start.x, end.x); + } + + @Override + public float minY() { + return min(start.y, end.y); + } + + @Override + public float maxY() { + return max(start.y, end.y); + } + + @Override + public void offset(float x, float y) { + start.offset(x, y); + end.offset(x, y); + } + + @Override + public String toString() { + return "start --> " + start.toString() + ",end --> " + end.toString(); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java new file mode 100644 index 0000000..231d2ac --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantPuzzleLayout.java @@ -0,0 +1,334 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.RectF; +import android.util.Pair; + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.createLine; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaCross; +import static com.huantansheng.easyphotos.models.puzzle.slant.SlantUtils.cutAreaWith; + + +/** + * 斜线布局,外围区域为一矩形 + * + * @author wupanjie + */ +public abstract class SlantPuzzleLayout implements PuzzleLayout { + private RectF bounds; + private SlantArea outerArea; + + private List outerLines = new ArrayList<>(4); + private List areas = new ArrayList<>(); + private List lines = new ArrayList<>(); + + private float padding; + private float radian; + private int color; + + private Comparator areaComparator = new SlantArea.AreaComparator(); + + private ArrayList steps = new ArrayList<>(); + + protected SlantPuzzleLayout() { + + } + + @Override + public void setOuterBounds(RectF bounds) { + reset(); + + this.bounds = bounds; + + CrossoverPointF leftTop = new CrossoverPointF(bounds.left, bounds.top); + CrossoverPointF rightTop = new CrossoverPointF(bounds.right, bounds.top); + CrossoverPointF leftBottom = new CrossoverPointF(bounds.left, bounds.bottom); + CrossoverPointF rightBottom = new CrossoverPointF(bounds.right, bounds.bottom); + + SlantLine lineLeft = new SlantLine(leftTop, leftBottom, Line.Direction.VERTICAL); + SlantLine lineTop = new SlantLine(leftTop, rightTop, Line.Direction.HORIZONTAL); + SlantLine lineRight = new SlantLine(rightTop, rightBottom, Line.Direction.VERTICAL); + SlantLine lineBottom = new SlantLine(leftBottom, rightBottom, Line.Direction.HORIZONTAL); + + outerLines.clear(); + + outerLines.add(lineLeft); + outerLines.add(lineTop); + outerLines.add(lineRight); + outerLines.add(lineBottom); + + outerArea = new SlantArea(); + outerArea.lineLeft = lineLeft; + outerArea.lineTop = lineTop; + outerArea.lineRight = lineRight; + outerArea.lineBottom = lineBottom; + + outerArea.updateCornerPoints(); + + areas.clear(); + areas.add(outerArea); + } + + public abstract void layout(); + + private void updateLineLimit() { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line line = lines.get(i); + updateUpperLine(line); + updateLowerLine(line); + } + } + + private void updateLowerLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l.direction() != line.direction()) { + continue; + } + + if (l.attachStartLine() != line.attachStartLine() + || l.attachEndLine() != line.attachEndLine()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) { + line.setLowerLine(l); + } + } else { + if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) { + line.setLowerLine(l); + } + } + } + } + + private void updateUpperLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l.direction() != line.direction()) { + continue; + } + + if (l.attachStartLine() != line.attachStartLine() + || l.attachEndLine() != line.attachEndLine()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) { + line.setUpperLine(l); + } + } else { + if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) { + line.setUpperLine(l); + } + } + } + } + + @Override + public int getAreaCount() { + return areas.size(); + } + + @Override + public void reset() { + lines.clear(); + areas.clear(); + areas.add(outerArea); + steps.clear(); + } + + @Override + public void update() { + int size = lines.size(); + for (int i = 0; i < size; i++) { + lines.get(i).update(width(), height()); + } + int areasSize = areas.size(); + for (int i = 0; i < areasSize; i++) { + areas.get(i).updateCornerPoints(); + } + } + + @Override + public float width() { + return outerArea == null ? 0 : outerArea.width(); + } + + @Override + public float height() { + return outerArea == null ? 0 : outerArea.height(); + } + + private void sortAreas() { + Collections.sort(areas, areaComparator); + } + + @Override + public List getOuterLines() { + return outerLines; + } + + @Override + public Area getOuterArea() { + return outerArea; + } + + public List getAreas() { + return areas; + } + + @Override + public SlantArea getArea(int position) { + return areas.get(position); + } + + @Override + public List getLines() { + return lines; + } + + @Override + public void setPadding(float padding) { + this.padding = padding; + for (Area area : areas) { + area.setPadding(padding); + } + + outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding); + outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding); + + outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding); + outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding); + + outerArea.updateCornerPoints(); + update(); + } + + @Override + public float getPadding() { + return padding; + } + + @Override + public float getRadian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + for (Area area : areas) { + area.setRadian(radian); + } + } + + @Override + public int getColor() { + return color; + } + + @Override + public void setColor(int color) { + this.color = color; + } + + protected List addLine(int position, Line.Direction direction, float ratio) { + return addLine(position, direction, ratio, ratio); + } + + protected List addLine(int position, Line.Direction direction, float startRatio, + float endRatio) { + SlantArea area = areas.get(position); + areas.remove(area); + SlantLine line = createLine(area, direction, startRatio, endRatio); + lines.add(line); + + List increasedAreas = cutAreaWith(area, line); + + areas.addAll(increasedAreas); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.ADD_LINE; + step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1; + step.position = position; + steps.add(step); + + return increasedAreas; + } + + protected void addCross(int position, float startRatio1, float endRatio1, + float startRatio2, float endRatio2) { + SlantArea area = areas.get(position); + areas.remove(area); + + SlantLine horizontal = createLine(area, Line.Direction.HORIZONTAL, startRatio1, endRatio1); + SlantLine vertical = createLine(area, Line.Direction.VERTICAL, startRatio2, endRatio2); + lines.add(horizontal); + lines.add(vertical); + + List increasedAreas = cutAreaCross(area, horizontal, vertical); + + areas.addAll(increasedAreas); + sortAreas(); + + Step step = new Step(); + step.type = Step.ADD_CROSS; + step.position = position; + steps.add(step); + } + + protected void cutArea(int position, int hSize, int vSize) { + SlantArea area = areas.get(position); + areas.remove(area); + + Pair, List> spilt = + cutAreaWith(area, hSize, vSize); + + lines.addAll(spilt.first); + areas.addAll(spilt.second); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.CUT_EQUAL_PART_ONE; + step.position = position; + step.hSize = hSize; + step.vSize = vSize; + steps.add(step); + } + + @Override + public Info generateInfo() { + Info info = new Info(); + info.type = Info.TYPE_SLANT; + info.padding = padding; + info.radian = radian; + info.color = color; + info.steps = steps; + ArrayList lineInfos = new ArrayList<>(); + for (Line line : lines) { + LineInfo lineInfo = new LineInfo(line); + lineInfos.add(lineInfo); + } + info.lineInfos = lineInfos; + return info; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java new file mode 100644 index 0000000..b6d57bc --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/slant/SlantUtils.java @@ -0,0 +1,482 @@ +package com.huantansheng.easyphotos.models.puzzle.slant; + +import android.graphics.PointF; +import android.util.Pair; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +class SlantUtils { + private static final PointF A = new PointF(); + private static final PointF B = new PointF(); + private static final PointF C = new PointF(); + private static final PointF D = new PointF(); + private static final PointF AB = new PointF(); + private static final PointF AM = new PointF(); + private static final PointF BC = new PointF(); + private static final PointF BM = new PointF(); + private static final PointF CD = new PointF(); + private static final PointF CM = new PointF(); + private static final PointF DA = new PointF(); + private static final PointF DM = new PointF(); + + private SlantUtils() { + //no instance + } + + static float distance(PointF one, PointF two) { + return (float) Math.sqrt(Math.pow(two.x - one.x, 2) + Math.pow(two.y - one.y, 2)); + } + + static List cutAreaWith(SlantArea area, SlantLine line) { + List areas = new ArrayList<>(); + SlantArea area1 = new SlantArea(area); + SlantArea area2 = new SlantArea(area); + + if (line.direction == Line.Direction.HORIZONTAL) { + area1.lineBottom = line; + area1.leftBottom = line.start; + area1.rightBottom = line.end; + + area2.lineTop = line; + area2.leftTop = line.start; + area2.rightTop = line.end; + } else { + area1.lineRight = line; + area1.rightTop = line.start; + area1.rightBottom = line.end; + + area2.lineLeft = line; + area2.leftTop = line.start; + area2.leftBottom = line.end; + } + + areas.add(area1); + areas.add(area2); + + return areas; + } + + static SlantLine createLine(SlantArea area, Line.Direction direction, float startratio, + float endratio) { + SlantLine line = new SlantLine(direction); + + if (direction == Line.Direction.HORIZONTAL) { + line.start = getPoint(area.leftTop, area.leftBottom, Line.Direction.VERTICAL, startratio); + line.end = getPoint(area.rightTop, area.rightBottom, Line.Direction.VERTICAL, endratio); + + line.attachLineStart = area.lineLeft; + line.attachLineEnd = area.lineRight; + + line.upperLine = area.lineBottom; + line.lowerLine = area.lineTop; + } else { + line.start = getPoint(area.leftTop, area.rightTop, Line.Direction.HORIZONTAL, startratio); + line.end = getPoint(area.leftBottom, area.rightBottom, Line.Direction.HORIZONTAL, endratio); + + line.attachLineStart = area.lineTop; + line.attachLineEnd = area.lineBottom; + + line.upperLine = area.lineRight; + line.lowerLine = area.lineLeft; + } + + return line; + } + + static Pair, List> cutAreaWith(final SlantArea area, + final int horizontalSize, final int verticalSize) { + List areaList = new ArrayList<>(); + List horizontalLines = new ArrayList<>(horizontalSize); + + SlantArea restArea = new SlantArea(area); + for (int i = horizontalSize + 1; i > 1; i--) { + SlantLine horizontalLine = + createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i - 0.025f, (float) (i - 1) / i + 0.025f); + horizontalLines.add(horizontalLine); + restArea.lineBottom = horizontalLine; + restArea.leftBottom = horizontalLine.start; + restArea.rightBottom = horizontalLine.end; + } + List verticalLines = new ArrayList<>(); + + restArea = new SlantArea(area); + for (int i = verticalSize + 1; i > 1; i--) { + SlantLine verticalLine = + createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i + 0.025f, (float) (i - 1) / i - 0.025f); + verticalLines.add(verticalLine); + SlantArea spiltArea = new SlantArea(restArea); + spiltArea.lineLeft = verticalLine; + spiltArea.leftTop = verticalLine.start; + spiltArea.leftBottom = verticalLine.end; + + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + SlantArea blockArea = new SlantArea(spiltArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == size) { + blockArea.lineBottom = horizontalLines.get(j - 1); + + CrossoverPointF leftBottom = + new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft); + intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft); + CrossoverPointF rightBottom = + new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight); + intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight); + blockArea.leftBottom = leftBottom; + blockArea.rightBottom = rightBottom; + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft); + intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft); + CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight); + intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight); + blockArea.leftTop = leftTop; + blockArea.rightTop = rightTop; + areaList.add(blockArea); + } + restArea.lineRight = verticalLine; + restArea.rightTop = verticalLine.start; + restArea.rightBottom = verticalLine.end; + } + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + SlantArea blockArea = new SlantArea(restArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == size) { + blockArea.lineBottom = horizontalLines.get(j - 1); + CrossoverPointF leftBottom = new CrossoverPointF(blockArea.lineBottom, blockArea.lineLeft); + intersectionOfLines(leftBottom, blockArea.lineBottom, blockArea.lineLeft); + CrossoverPointF rightBottom = + new CrossoverPointF(blockArea.lineBottom, blockArea.lineRight); + intersectionOfLines(rightBottom, blockArea.lineBottom, blockArea.lineRight); + blockArea.leftBottom = leftBottom; + blockArea.rightBottom = rightBottom; + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + CrossoverPointF leftTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineLeft); + intersectionOfLines(leftTop, blockArea.lineTop, blockArea.lineLeft); + CrossoverPointF rightTop = new CrossoverPointF(blockArea.lineTop, blockArea.lineRight); + intersectionOfLines(rightTop, blockArea.lineTop, blockArea.lineRight); + blockArea.leftTop = leftTop; + blockArea.rightTop = rightTop; + areaList.add(blockArea); + } + + List lines = new ArrayList<>(); + lines.addAll(horizontalLines); + lines.addAll(verticalLines); + return new Pair<>(lines, areaList); + } + + static List cutAreaCross(final SlantArea area, final SlantLine horizontal, + final SlantLine vertical) { + List list = new ArrayList<>(); + + CrossoverPointF crossoverPoint = new CrossoverPointF(horizontal, vertical); + intersectionOfLines(crossoverPoint, horizontal, vertical); + + SlantArea one = new SlantArea(area); + one.lineBottom = horizontal; + one.lineRight = vertical; + + one.rightTop = vertical.start; + one.rightBottom = crossoverPoint; + one.leftBottom = horizontal.start; + list.add(one); + + SlantArea two = new SlantArea(area); + two.lineBottom = horizontal; + two.lineLeft = vertical; + + two.leftTop = vertical.start; + two.rightBottom = horizontal.end; + two.leftBottom = crossoverPoint; + list.add(two); + + SlantArea three = new SlantArea(area); + three.lineTop = horizontal; + three.lineRight = vertical; + + three.leftTop = horizontal.start; + three.rightTop = crossoverPoint; + three.rightBottom = vertical.end; + list.add(three); + + SlantArea four = new SlantArea(area); + four.lineTop = horizontal; + four.lineLeft = vertical; + + four.leftTop = crossoverPoint; + four.rightTop = horizontal.end; + four.leftBottom = vertical.end; + list.add(four); + + return list; + } + + private static CrossoverPointF getPoint(final PointF start, final PointF end, + final Line.Direction direction, float ratio) { + CrossoverPointF point = new CrossoverPointF(); + getPoint(point, start, end, direction, ratio); + return point; + } + + static void getPoint(final PointF dst, final PointF start, final PointF end, + final Line.Direction direction, float ratio) { + float deltaY = Math.abs(start.y - end.y); + float deltaX = Math.abs(start.x - end.x); + float maxY = Math.max(start.y, end.y); + float minY = Math.min(start.y, end.y); + float maxX = Math.max(start.x, end.x); + float minX = Math.min(start.x, end.x); + if (direction == Line.Direction.HORIZONTAL) { + dst.x = minX + deltaX * ratio; + if (start.y < end.y) { + dst.y = minY + ratio * deltaY; + } else { + dst.y = maxY - ratio * deltaY; + } + } else { + dst.y = minY + deltaY * ratio; + if (start.x < end.x) { + dst.x = minX + ratio * deltaX; + } else { + dst.x = maxX - ratio * deltaX; + } + } + } + + // 叉乘 + private static float crossProduct(final PointF a, final PointF b) { + return a.x * b.y - b.x * a.y; + } + + /** + * 判断一个斜线区域是否包含(x,y)点 + * + * @param area 斜线区域 + * @param x x + * @param y y + * @return 是否包含 + */ + static boolean contains(SlantArea area, float x, float y) { + AB.x = area.rightTop.x - area.leftTop.x; + AB.y = area.rightTop.y - area.leftTop.y; + + AM.x = x - area.leftTop.x; + AM.y = y - area.leftTop.y; + + BC.x = area.rightBottom.x - area.rightTop.x; + BC.y = area.rightBottom.y - area.rightTop.y; + + BM.x = x - area.rightTop.x; + BM.y = y - area.rightTop.y; + + CD.x = area.leftBottom.x - area.rightBottom.x; + CD.y = area.leftBottom.y - area.rightBottom.y; + + CM.x = x - area.rightBottom.x; + CM.y = y - area.rightBottom.y; + + DA.x = area.leftTop.x - area.leftBottom.x; + DA.y = area.leftTop.y - area.leftBottom.y; + + DM.x = x - area.leftBottom.x; + DM.y = y - area.leftBottom.y; + + return crossProduct(AB, AM) > 0 + && crossProduct(BC, BM) > 0 + && crossProduct(CD, CM) > 0 + && crossProduct(DA, DM) > 0; + } + + static boolean contains(SlantLine line, float x, float y, float extra) { + PointF start = line.start; + PointF end = line.end; + if (line.direction == Line.Direction.VERTICAL) { + A.x = start.x - extra; + A.y = start.y; + B.x = start.x + extra; + B.y = start.y; + C.x = end.x + extra; + C.y = end.y; + D.x = end.x - extra; + D.y = end.y; + } else { + A.x = start.x; + A.y = start.y - extra; + B.x = end.x; + B.y = end.y - extra; + C.x = end.x; + C.y = end.y + extra; + D.x = start.x; + D.y = start.y + extra; + } + + AB.x = B.x - A.x; + AB.y = B.y - A.y; + + AM.x = x - A.x; + AM.y = y - A.y; + + BC.x = C.x - B.x; + BC.y = C.y - B.y; + + BM.x = x - B.x; + BM.y = y - B.y; + + CD.x = D.x - C.x; + CD.y = D.y - C.y; + + CM.x = x - C.x; + CM.y = y - C.y; + + DA.x = A.x - D.x; + DA.y = A.y - D.y; + + DM.x = x - D.x; + DM.y = y - D.y; + + return crossProduct(AB, AM) > 0 + && crossProduct(BC, BM) > 0 + && crossProduct(CD, CM) > 0 + && crossProduct(DA, DM) > 0; + } + + /** + * 计算两线的交点 + * + * @param dst 计算出的交点 + * @param lineOne 线一 + * @param lineTwo 线二 + */ + static void intersectionOfLines(final CrossoverPointF dst, final SlantLine lineOne, + final SlantLine lineTwo) { + dst.horizontal = lineOne; + dst.vertical = lineTwo; + if (isParallel(lineOne, lineTwo)) { + dst.set(0, 0); + return; + } + + if (isHorizontalLine(lineOne) && isVerticalLine(lineTwo)) { + dst.set(lineTwo.start.x, lineOne.start.y); + return; + } + + if (isVerticalLine(lineOne) && isHorizontalLine(lineTwo)) { + dst.set(lineOne.start.x, lineTwo.start.y); + return; + } + + if (isHorizontalLine(lineOne) && !isVerticalLine(lineTwo)) { + float k = calculateSlope(lineTwo); + float b = calculateVerticalIntercept(lineTwo); + + dst.y = lineOne.start.y; + dst.x = (dst.y - b) / k; + return; + } + + if (isVerticalLine(lineOne) && !isHorizontalLine(lineTwo)) { + float k = calculateSlope(lineTwo); + float b = calculateVerticalIntercept(lineTwo); + + dst.x = lineOne.start.x; + dst.y = k * dst.x + b; + return; + } + + if (isHorizontalLine(lineTwo) && !isVerticalLine(lineOne)) { + float k = calculateSlope(lineOne); + float b = calculateVerticalIntercept(lineOne); + + dst.y = lineTwo.start.y; + dst.x = (dst.y - b) / k; + return; + } + + if (isVerticalLine(lineTwo) && !isHorizontalLine(lineOne)) { + float k = calculateSlope(lineOne); + float b = calculateVerticalIntercept(lineOne); + + dst.x = lineTwo.start.x; + dst.y = k * dst.x + b; + return; + } + + final float k1 = calculateSlope(lineOne); + final float b1 = calculateVerticalIntercept(lineOne); + + final float k2 = calculateSlope(lineTwo); + final float b2 = calculateVerticalIntercept(lineTwo); + + dst.x = (b2 - b1) / (k1 - k2); + dst.y = dst.x * k1 + b1; + } + + private static boolean isHorizontalLine(SlantLine line) { + return line.start.y == line.end.y; + } + + private static boolean isVerticalLine(SlantLine line) { + return line.start.x == line.end.x; + } + + /** + * 判断两条线是否平行 + * + * @param lineOne 第一条 + * @param lineTwo 第二条 + * @return 是否平行 + */ + private static boolean isParallel(final SlantLine lineOne, final SlantLine lineTwo) { + return calculateSlope(lineOne) == calculateSlope(lineTwo); + } + + /** + * 计算线的斜率 + * + * @param line 线 + * @return 线的斜率 + */ + static float calculateSlope(final SlantLine line) { + if (isHorizontalLine(line)) { + return 0f; + } else if (isVerticalLine(line)) { + return Float.POSITIVE_INFINITY; + } else { + return (line.start.y - line.end.y) / (line.start.x - line.end.x); + } + } + + /** + * 计算纵截距 + * + * @param line 线 + * @return 纵截距 + */ + private static float calculateVerticalIntercept(final SlantLine line) { + if (isHorizontalLine(line)) { + return line.start.y; + } else if (isVerticalLine(line)) { + return Float.POSITIVE_INFINITY; + } else { + float k = calculateSlope(line); + return line.start.y - k * line.start.x; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java new file mode 100644 index 0000000..867424b --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightArea.java @@ -0,0 +1,231 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.Path; +import android.graphics.PointF; +import android.graphics.RectF; + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + + +/** + * @author wupanjie + */ +class StraightArea implements Area { + StraightLine lineLeft; + StraightLine lineTop; + StraightLine lineRight; + StraightLine lineBottom; + + private Path areaPath = new Path(); + private RectF areaRect = new RectF(); + private PointF[] handleBarPoints = new PointF[2]; + + private float paddingLeft; + private float paddingTop; + private float paddingRight; + private float paddingBottom; + private float radian; + + StraightArea(){ + handleBarPoints[0] = new PointF(); + handleBarPoints[1] = new PointF(); + } + + StraightArea(RectF baseRect) { + this(); + setBaseRect(baseRect); + } + + private void setBaseRect(RectF baseRect) { + PointF one = new PointF(baseRect.left, baseRect.top); + PointF two = new PointF(baseRect.right, baseRect.top); + PointF three = new PointF(baseRect.left, baseRect.bottom); + PointF four = new PointF(baseRect.right, baseRect.bottom); + + lineLeft = new StraightLine(one, three); + lineTop = new StraightLine(one, two); + lineRight = new StraightLine(two, four); + lineBottom = new StraightLine(three, four); + } + + StraightArea(StraightArea src) { + this.lineLeft = src.lineLeft; + this.lineTop = src.lineTop; + this.lineRight = src.lineRight; + this.lineBottom = src.lineBottom; + + handleBarPoints[0] = new PointF(); + handleBarPoints[1] = new PointF(); + } + + @Override + public float left() { + return lineLeft.minX() + paddingLeft; + } + + @Override + public float top() { + return lineTop.minY() + paddingTop; + } + + @Override + public float right() { + return lineRight.maxX() - paddingRight; + } + + @Override + public float bottom() { + return lineBottom.maxY() - paddingBottom; + } + + @Override + public float centerX() { + return (left() + right()) / 2; + } + + @Override + public float centerY() { + return (top() + bottom()) / 2; + } + + @Override + public float width() { + return right() - left(); + } + + @Override + public float height() { + return bottom() - top(); + } + + @Override + public PointF getCenterPoint() { + return new PointF(centerX(), centerY()); + } + + @Override + public boolean contains(PointF point) { + return contains(point.x, point.y); + } + + @Override + public boolean contains(float x, float y) { + return getAreaRect().contains(x, y); + } + + @Override + public boolean contains(Line line) { + return lineLeft == line || lineTop == line || lineRight == line || lineBottom == line; + } + + @Override + public Path getAreaPath() { + areaPath.reset(); + areaPath.addRoundRect(getAreaRect(),radian,radian, Path.Direction.CCW); + //areaPath.addRect(getAreaRect(), Path.Direction.CCW); + return areaPath; + } + + @Override + public RectF getAreaRect() { + areaRect.set(left(), top(), right(), bottom()); + return areaRect; + } + + @Override + public List getLines() { + return Arrays.asList((Line) lineLeft, lineTop, lineRight, lineBottom); + } + + @Override + public PointF[] getHandleBarPoints(Line line) { + if (line == lineLeft) { + handleBarPoints[0].x = left(); + handleBarPoints[0].y = top() + height() / 4; + handleBarPoints[1].x = left(); + handleBarPoints[1].y = top() + height() / 4 * 3; + } else if (line == lineTop) { + handleBarPoints[0].x = left() + width() / 4; + handleBarPoints[0].y = top(); + handleBarPoints[1].x = left() + width() / 4 * 3; + handleBarPoints[1].y = top(); + } else if (line == lineRight) { + handleBarPoints[0].x = right(); + handleBarPoints[0].y = top() + height() / 4; + handleBarPoints[1].x = right(); + handleBarPoints[1].y = top() + height() / 4 * 3; + } else if (line == lineBottom) { + handleBarPoints[0].x = left() + width() / 4; + handleBarPoints[0].y = bottom(); + handleBarPoints[1].x = left() + width() / 4 * 3; + handleBarPoints[1].y = bottom(); + } + return handleBarPoints; + } + + @Override + public float radian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + } + + @Override + public float getPaddingLeft() { + return paddingLeft; + } + + @Override + public float getPaddingTop() { + return paddingTop; + } + + @Override + public float getPaddingRight() { + return paddingRight; + } + + @Override + public float getPaddingBottom() { + return paddingBottom; + } + + @Override + public void setPadding(float padding) { + setPadding(padding, padding, padding, padding); + } + + @Override + public void setPadding(float paddingLeft, float paddingTop, float paddingRight, + float paddingBottom) { + this.paddingLeft = paddingLeft; + this.paddingTop = paddingTop; + this.paddingRight = paddingRight; + this.paddingBottom = paddingBottom; + } + + static class AreaComparator implements Comparator { + @Override + public int compare(StraightArea lhs, StraightArea rhs) { + if (lhs.top() < rhs.top()) { + return -1; + } else if (lhs.top() == rhs.top()) { + if (lhs.left() < rhs.left()) { + return -1; + } else { + return 1; + } + } else { + return 1; + } + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java new file mode 100644 index 0000000..017446b --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightLine.java @@ -0,0 +1,215 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import static java.lang.Math.max; +import static java.lang.Math.min; + +/** + * @author wupanjie + */ +class StraightLine implements Line { + private PointF start; + private PointF end; + + private PointF previousStart = new PointF(); + private PointF previousEnd = new PointF(); + + public Line.Direction direction = Direction.HORIZONTAL; + + StraightLine attachLineStart; + StraightLine attachLineEnd; + + private Line upperLine; + private Line lowerLine; + + private RectF bounds = new RectF(); + + StraightLine(PointF start, PointF end) { + this.start = start; + this.end = end; + + if (start.x == end.x) { + direction = Line.Direction.VERTICAL; + } else if (start.y == end.y) { + direction = Line.Direction.HORIZONTAL; + } else { + Log.d("StraightLine", "StraightLine: current only support two direction"); + } + } + + @Override + public float length() { + return (float) Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.y - start.y, 2)); + } + + @Override + public PointF startPoint() { + return start; + } + + @Override + public PointF endPoint() { + return end; + } + + @Override + public Line lowerLine() { + return lowerLine; + } + + @Override + public Line upperLine() { + return upperLine; + } + + @Override + public Line attachStartLine() { + return attachLineStart; + } + + @Override + public Line attachEndLine() { + return attachLineEnd; + } + + @Override + public void setLowerLine(Line lowerLine) { + this.lowerLine = lowerLine; + } + + @Override + public void setUpperLine(Line upperLine) { + this.upperLine = upperLine; + } + + void setAttachLineStart(StraightLine attachLineStart) { + this.attachLineStart = attachLineStart; + } + + void setAttachLineEnd(StraightLine attachLineEnd) { + this.attachLineEnd = attachLineEnd; + } + + @Override + public Direction direction() { + return direction; + } + + @Override + public float slope() { + return direction == Direction.HORIZONTAL ? 0 : Float.MAX_VALUE; + } + + @Override + public boolean contains(float x, float y, float extra) { + if (direction == Line.Direction.HORIZONTAL) { + bounds.left = start.x; + bounds.right = end.x; + bounds.top = start.y - extra / 2; + bounds.bottom = start.y + extra / 2; + } else if (direction == Line.Direction.VERTICAL) { + bounds.top = start.y; + bounds.bottom = end.y; + bounds.left = start.x - extra / 2; + bounds.right = start.x + extra / 2; + } + + return bounds.contains(x, y); + } + + @Override + public void prepareMove() { + previousStart.set(start); + previousEnd.set(end); + } + + @Override + public boolean move(float offset, float extra) { + if (direction == Line.Direction.HORIZONTAL) { + if (previousStart.y + offset < lowerLine.maxY() + extra + || previousStart.y + offset > upperLine.minY() - extra + || previousEnd.y + offset < lowerLine.maxY() + extra + || previousEnd.y + offset > upperLine.minY() - extra) { + return false; + } + + start.y = previousStart.y + offset; + end.y = previousEnd.y + offset; + } else { + if (previousStart.x + offset < lowerLine.maxX() + extra + || previousStart.x + offset > upperLine.minX() - extra + || previousEnd.x + offset < lowerLine.maxX() + extra + || previousEnd.x + offset > upperLine.minX() - extra) { + return false; + } + + start.x = previousStart.x + offset; + end.x = previousEnd.x + offset; + } + + return true; + } + + @Override + public void update(float layoutWidth, float layoutHeight) { + if (direction == Line.Direction.HORIZONTAL) { + if (attachLineStart != null) { + start.x = attachLineStart.getPosition(); + } + if (attachLineEnd != null) { + end.x = attachLineEnd.getPosition(); + } + } else if (direction == Line.Direction.VERTICAL) { + if (attachLineStart != null) { + start.y = attachLineStart.getPosition(); + } + if (attachLineEnd != null) { + end.y = attachLineEnd.getPosition(); + } + } + } + + public float getPosition() { + if (direction == Line.Direction.HORIZONTAL) { + return start.y; + } else { + return start.x; + } + } + + @Override + public float minX() { + return min(start.x, end.x); + } + + @Override + public float maxX() { + return max(start.x, end.x); + } + + @Override + public float minY() { + return min(start.y, end.y); + } + + @Override + public float maxY() { + return max(start.y, end.y); + } + + @Override + public void offset(float x, float y) { + start.offset(x, y); + end.offset(x, y); + } + + @Override + public String toString() { + return "start --> " + start.toString() + ",end --> " + end.toString(); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java new file mode 100644 index 0000000..9a24d58 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightPuzzleLayout.java @@ -0,0 +1,359 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Pair; + +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.Line; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; + +import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.createLine; +import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaCross; +import static com.huantansheng.easyphotos.models.puzzle.straight.StraightUtils.cutAreaSpiral; + + +/** + * @author wupanjie + */ +public abstract class StraightPuzzleLayout implements PuzzleLayout { + private RectF bounds; + private StraightArea outerArea; + + private List areas = new ArrayList<>(); + private List lines = new ArrayList<>(); + private List outerLines = new ArrayList<>(4); + + private float padding; + private float radian; + private int color; + + private Comparator areaComparator = new StraightArea.AreaComparator(); + + private ArrayList steps = new ArrayList<>(); + + protected StraightPuzzleLayout() { + + } + + @Override + public void setOuterBounds(RectF bounds) { + reset(); + + this.bounds = bounds; + + PointF one = new PointF(bounds.left, bounds.top); + PointF two = new PointF(bounds.right, bounds.top); + PointF three = new PointF(bounds.left, bounds.bottom); + PointF four = new PointF(bounds.right, bounds.bottom); + + StraightLine lineLeft = new StraightLine(one, three); + StraightLine lineTop = new StraightLine(one, two); + StraightLine lineRight = new StraightLine(two, four); + StraightLine lineBottom = new StraightLine(three, four); + + outerLines.clear(); + + outerLines.add(lineLeft); + outerLines.add(lineTop); + outerLines.add(lineRight); + outerLines.add(lineBottom); + + outerArea = new StraightArea(); + outerArea.lineLeft = lineLeft; + outerArea.lineTop = lineTop; + outerArea.lineRight = lineRight; + outerArea.lineBottom = lineBottom; + + areas.clear(); + areas.add(outerArea); + } + + @Override + public abstract void layout(); + + @Override + public int getAreaCount() { + return areas.size(); + } + + @Override + public List getOuterLines() { + return outerLines; + } + + @Override + public List getLines() { + return lines; + } + + @Override + public void update() { + for (Line line : lines) { + line.update(width(), height()); + } + } + + @Override + public float width() { + return outerArea == null ? 0 : outerArea.width(); + } + + @Override + public float height() { + return outerArea == null ? 0 : outerArea.height(); + } + + @Override + public void reset() { + lines.clear(); + areas.clear(); + areas.add(outerArea); + steps.clear(); + } + + @Override + public Area getArea(int position) { + return areas.get(position); + } + + @Override + + public StraightArea getOuterArea() { + return outerArea; + } + + @Override + public void setPadding(float padding) { + this.padding = padding; + + for (Area area : areas) { + area.setPadding(padding); + } + + outerArea.lineLeft.startPoint().set(bounds.left + padding, bounds.top + padding); + outerArea.lineLeft.endPoint().set(bounds.left + padding, bounds.bottom - padding); + + outerArea.lineRight.startPoint().set(bounds.right - padding, bounds.top + padding); + outerArea.lineRight.endPoint().set(bounds.right - padding, bounds.bottom - padding); + + update(); + } + + @Override + public float getPadding() { + return padding; + } + + protected void addLine(int position, Line.Direction direction, float ratio) { + StraightArea area = areas.get(position); + addLine(area, direction, ratio); + + Step step = new Step(); + step.type = Step.ADD_LINE; + step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1; + step.position = position; + steps.add(step); + } + + private List addLine(StraightArea area, Line.Direction direction, float ratio) { + areas.remove(area); + StraightLine line = createLine(area, direction, ratio); + lines.add(line); + + List increasedArea = StraightUtils.cutArea(area, line); + areas.addAll(increasedArea); + + updateLineLimit(); + sortAreas(); + + return increasedArea; + } + + protected void cutAreaEqualPart(int position, int part, Line.Direction direction) { + StraightArea temp = areas.get(position); + for (int i = part; i > 1; i--) { + temp = addLine(temp, direction, (float) (i - 1) / i).get(0); + } + + Step step = new Step(); + step.type = Step.CUT_EQUAL_PART_TWO; + step.part = part; + step.position = position; + step.direction = direction == Line.Direction.HORIZONTAL ? 0 : 1; + steps.add(step); + } + + protected void addCross(int position, float ratio) { + addCross(position, ratio, ratio); + } + + protected void addCross(int position, float horizontalRatio, float verticalRatio) { + StraightArea area = areas.get(position); + areas.remove(area); + StraightLine horizontal = createLine(area, Line.Direction.HORIZONTAL, horizontalRatio); + StraightLine vertical = createLine(area, Line.Direction.VERTICAL, verticalRatio); + lines.add(horizontal); + lines.add(vertical); + + List newAreas = cutAreaCross(area, horizontal, vertical); + areas.addAll(newAreas); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.ADD_CROSS; + step.position = position; + steps.add(step); + } + + protected void cutAreaEqualPart(int position, int hSize, int vSize) { + StraightArea area = areas.get(position); + areas.remove(area); + Pair, List> increased = + StraightUtils.cutArea(area, hSize, vSize); + List newLines = increased.first; + List newAreas = increased.second; + + lines.addAll(newLines); + areas.addAll(newAreas); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.CUT_EQUAL_PART_ONE; + step.position = position; + step.hSize = hSize; + step.vSize = vSize; + steps.add(step); + } + + protected void cutSpiral(int position) { + StraightArea area = areas.get(position); + areas.remove(area); + Pair, List> spilt = cutAreaSpiral(area); + + lines.addAll(spilt.first); + areas.addAll(spilt.second); + + updateLineLimit(); + sortAreas(); + + Step step = new Step(); + step.type = Step.CUT_SPIRAL; + step.position = position; + steps.add(step); + } + + private void sortAreas() { + Collections.sort(areas, areaComparator); + } + + private void updateLineLimit() { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line line = lines.get(i); + updateUpperLine(line); + updateLowerLine(line); + } + } + + private void updateLowerLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l == line) { + continue; + } + + if (l.direction() != line.direction()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue; + if (l.minY() > line.lowerLine().maxY() && l.maxY() < line.minY()) { + line.setLowerLine(l); + } + } else { + if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue; + if (l.minX() > line.lowerLine().maxX() && l.maxX() < line.minX()) { + line.setLowerLine(l); + } + } + } + } + + private void updateUpperLine(final Line line) { + int size = lines.size(); + for (int i = 0; i < size; i++) { + Line l = lines.get(i); + if (l == line) { + continue; + } + + if (l.direction() != line.direction()) { + continue; + } + + if (l.direction() == Line.Direction.HORIZONTAL) { + if (l.maxX() <= line.minX() || line.maxX() <= l.minX()) continue; + if (l.maxY() < line.upperLine().minY() && l.minY() > line.maxY()) { + line.setUpperLine(l); + } + } else { + if (l.maxY() <= line.minY() || line.maxY() <= l.minY()) continue; + if (l.maxX() < line.upperLine().minX() && l.minX() > line.maxX()) { + line.setUpperLine(l); + } + } + } + } + + @Override + public float getRadian() { + return radian; + } + + @Override + public void setRadian(float radian) { + this.radian = radian; + for (Area area : areas) { + area.setRadian(radian); + } + } + + @Override + public int getColor() { + return color; + } + + @Override + public void setColor(int color) { + this.color = color; + } + + @Override + public Info generateInfo() { + Info info = new Info(); + info.type = Info.TYPE_STRAIGHT; + info.padding = padding; + info.radian = radian; + info.color = color; + info.steps = steps; + ArrayList lineInfos = new ArrayList<>(); + for (Line line : lines) { + LineInfo lineInfo = new LineInfo(line); + lineInfos.add(lineInfo); + } + info.lineInfos = lineInfos; + return info; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java new file mode 100644 index 0000000..47d1411 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/straight/StraightUtils.java @@ -0,0 +1,236 @@ +package com.huantansheng.easyphotos.models.puzzle.straight; + +import android.graphics.PointF; +import android.util.Pair; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +class StraightUtils { + static StraightLine createLine(final StraightArea area, final Line.Direction direction, + final float ratio) { + PointF one = new PointF(); + PointF two = new PointF(); + if (direction == Line.Direction.HORIZONTAL) { + one.x = area.left(); + one.y = area.height() * ratio + area.top(); + two.x = area.right(); + two.y = area.height() * ratio + area.top(); + } else if (direction == Line.Direction.VERTICAL) { + one.x = area.width() * ratio + area.left(); + one.y = area.top(); + two.x = area.width() * ratio + area.left(); + two.y = area.bottom(); + } + + StraightLine line = new StraightLine(one, two); + + if (direction == Line.Direction.HORIZONTAL) { + line.attachLineStart = area.lineLeft; + line.attachLineEnd = area.lineRight; + + line.setUpperLine(area.lineBottom); + line.setLowerLine(area.lineTop); + } else if (direction == Line.Direction.VERTICAL) { + line.attachLineStart = area.lineTop; + line.attachLineEnd = area.lineBottom; + + line.setUpperLine(area.lineRight); + line.setLowerLine(area.lineLeft); + } + + return line; + } + + static List cutArea(final StraightArea area, final StraightLine line) { + List list = new ArrayList<>(); + if (line.direction() == Line.Direction.HORIZONTAL) { + StraightArea one = new StraightArea(area); + one.lineBottom = line; + list.add(one); + + StraightArea two = new StraightArea(area); + two.lineTop = line; + list.add(two); + } else if (line.direction() == Line.Direction.VERTICAL) { + StraightArea one = new StraightArea(area); + one.lineRight = line; + list.add(one); + + StraightArea two = new StraightArea(area); + two.lineLeft = line; + list.add(two); + } + + return list; + } + + static Pair, List> cutArea(final StraightArea area, + final int horizontalSize, + final int verticalSize) { + List areaList = new ArrayList<>(); + List horizontalLines = new ArrayList<>(horizontalSize); + + StraightArea restArea = new StraightArea(area); + for (int i = horizontalSize + 1; i > 1; i--) { + StraightLine horizontalLine = + createLine(restArea, Line.Direction.HORIZONTAL, (float) (i - 1) / i); + horizontalLines.add(horizontalLine); + restArea.lineBottom = horizontalLine; + } + List verticalLines = new ArrayList<>(); + + restArea = new StraightArea(area); + for (int i = verticalSize + 1; i > 1; i--) { + StraightLine verticalLine = + createLine(restArea, Line.Direction.VERTICAL, (float) (i - 1) / i); + verticalLines.add(verticalLine); + StraightArea spiltArea = new StraightArea(restArea); + spiltArea.lineLeft = verticalLine; + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + StraightArea blockArea = new StraightArea(spiltArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == size) { + blockArea.lineBottom = horizontalLines.get(j - 1); + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + areaList.add(blockArea); + } + restArea.lineRight = verticalLine; + } + int size = horizontalLines.size(); + for (int j = 0; j <= size; j++) { + StraightArea blockArea = new StraightArea(restArea); + if (j == 0) { + blockArea.lineTop = horizontalLines.get(j); + } else if (j == horizontalLines.size()) { + blockArea.lineBottom = horizontalLines.get(j - 1); + } else { + blockArea.lineTop = horizontalLines.get(j); + blockArea.lineBottom = horizontalLines.get(j - 1); + } + areaList.add(blockArea); + } + + List lines = new ArrayList<>(); + lines.addAll(horizontalLines); + lines.addAll(verticalLines); + return new Pair<>(lines, areaList); + } + + static List cutAreaCross(final StraightArea area, final StraightLine horizontal, + final StraightLine vertical) { + List list = new ArrayList<>(); + + StraightArea one = new StraightArea(area); + one.lineBottom = horizontal; + one.lineRight = vertical; + list.add(one); + + StraightArea two = new StraightArea(area); + two.lineBottom = horizontal; + two.lineLeft = vertical; + list.add(two); + + StraightArea three = new StraightArea(area); + three.lineTop = horizontal; + three.lineRight = vertical; + list.add(three); + + StraightArea four = new StraightArea(area); + four.lineTop = horizontal; + four.lineLeft = vertical; + list.add(four); + + return list; + } + + static Pair, List> cutAreaSpiral(final StraightArea area) { + List lines = new ArrayList<>(); + List areas = new ArrayList<>(); + + float width = area.width(); + float height = area.height(); + + float left = area.left(); + float top = area.top(); + + PointF one = new PointF(left, top + height / 3); + PointF two = new PointF(left + width / 3 * 2, top); + PointF three = new PointF(left + width, top + height / 3 * 2); + PointF four = new PointF(left + width / 3, top + height); + PointF five = new PointF(left + width / 3, top + height / 3); + PointF six = new PointF(left + width / 3 * 2, top + height / 3); + PointF seven = new PointF(left + width / 3 * 2, top + height / 3 * 2); + PointF eight = new PointF(left + width / 3, top + height / 3 * 2); + + StraightLine l1 = new StraightLine(one, six); + StraightLine l2 = new StraightLine(two, seven); + StraightLine l3 = new StraightLine(eight, three); + StraightLine l4 = new StraightLine(five, four); + + l1.setAttachLineStart(area.lineLeft); + l1.setAttachLineEnd(l2); + l1.setLowerLine(area.lineTop); + l1.setUpperLine(l3); + + l2.setAttachLineStart(area.lineTop); + l2.setAttachLineEnd(l3); + l2.setLowerLine(l4); + l2.setUpperLine(area.lineRight); + + l3.setAttachLineStart(l4); + l3.setAttachLineEnd(area.lineRight); + l3.setLowerLine(l1); + l3.setUpperLine(area.lineBottom); + + l4.setAttachLineStart(l1); + l4.setAttachLineEnd(area.lineBottom); + l4.setLowerLine(area.lineLeft); + l4.setUpperLine(l2); + + lines.add(l1); + lines.add(l2); + lines.add(l3); + lines.add(l4); + + StraightArea b1 = new StraightArea(area); + b1.lineRight = l2; + b1.lineBottom = l1; + areas.add(b1); + + StraightArea b2 = new StraightArea(area); + b2.lineLeft = l2; + b2.lineBottom = l3; + areas.add(b2); + + StraightArea b3 = new StraightArea(area); + b3.lineRight = l4; + b3.lineTop = l1; + areas.add(b3); + + StraightArea b4 = new StraightArea(area); + b4.lineTop = l1; + b4.lineRight = l2; + b4.lineLeft = l4; + b4.lineBottom = l3; + areas.add(b4); + + StraightArea b5 = new StraightArea(area); + b5.lineLeft = l4; + b5.lineTop = l3; + areas.add(b5); + + return new Pair<>(lines, areas); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java new file mode 100644 index 0000000..2779cd0 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/NumberSlantLayout.java @@ -0,0 +1,33 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.slant.SlantPuzzleLayout; + + +/** + * @author wupanjie + */ + +public abstract class NumberSlantLayout extends SlantPuzzleLayout { + + static final String TAG = "NumberSlantLayout"; + protected int theme; + + public NumberSlantLayout(int theme) { + if (theme >= getThemeCount()) { + Log.e(TAG, "NumberSlantLayout: the most theme count is " + + getThemeCount() + + " ,you should let theme from 0 to " + + (getThemeCount() - 1) + + " ."); + } + this.theme = theme; + } + + public abstract int getThemeCount(); + + public int getTheme() { + return theme; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java new file mode 100644 index 0000000..43bdfd4 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/OneSlantLayout.java @@ -0,0 +1,36 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ + +public class OneSlantLayout extends NumberSlantLayout { + public OneSlantLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 4; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 2: + addCross(0, 0.56f, 0.44f, 0.56f, 0.44f); + break; + case 3: + cutArea(0, 1, 2); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java new file mode 100644 index 0000000..6918b46 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/SlantLayoutHelper.java @@ -0,0 +1,70 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + + + +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class SlantLayoutHelper { + private SlantLayoutHelper() { + + } + + public static List getAllThemeLayout(int pieceCount) { + List puzzleLayouts = new ArrayList<>(); + switch (pieceCount) { + case 1: + for (int i = 0; i < 4; i++) { + puzzleLayouts.add(new OneSlantLayout(i)); + } + break; + case 2: + for (int i = 0; i < 2; i++) { + puzzleLayouts.add(new TwoSlantLayout(i)); + } + break; + case 3: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new ThreeSlantLayout(i)); + } + break; + //case 4: + // for (int i = 0; i < 8; i++) { + // puzzleLayouts.add(new FourStraightLayout(i)); + // } + // break; + //case 5: + // for (int i = 0; i < 17; i++) { + // puzzleLayouts.add(new FiveStraightLayout(i)); + // } + // break; + //case 6: + // for (int i = 0; i < 12; i++) { + // puzzleLayouts.add(new SixStraightLayout(i)); + // } + // break; + //case 7: + // for (int i = 0; i < 9; i++) { + // puzzleLayouts.add(new SevenStraightLayout(i)); + // } + // break; + //case 8: + // for (int i = 0; i < 11; i++) { + // puzzleLayouts.add(new EightStraightLayout(i)); + // } + // break; + //case 9: + // for (int i = 0; i < 8; i++) { + // puzzleLayouts.add(new NineStraightLayout(i)); + // } + // break; + } + + return puzzleLayouts; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java new file mode 100644 index 0000000..3fca36a --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/ThreeSlantLayout.java @@ -0,0 +1,48 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ + +public class ThreeSlantLayout extends NumberSlantLayout { + public ThreeSlantLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 0.5f); + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 1: + addLine(0, Line.Direction.HORIZONTAL, 0.5f); + addLine(1, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 2: + addLine(0, Line.Direction.VERTICAL, 0.5f); + addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 3: + addLine(0, Line.Direction.VERTICAL, 0.5f); + addLine(1, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 4: + addLine(0, Line.Direction.HORIZONTAL, 0.44f, 0.56f); + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + addLine(1, Line.Direction.HORIZONTAL, 0.44f, 0.56f); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java new file mode 100644 index 0000000..b56e0b6 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/slant/TwoSlantLayout.java @@ -0,0 +1,30 @@ +package com.huantansheng.easyphotos.models.puzzle.template.slant; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ + +public class TwoSlantLayout extends NumberSlantLayout { + public TwoSlantLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 2; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 0.56f, 0.44f); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 0.56f, 0.44f); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java new file mode 100644 index 0000000..c97a5e8 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/EightStraightLayout.java @@ -0,0 +1,89 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class EightStraightLayout extends NumberStraightLayout { + public EightStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 11; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 3, 1); + break; + case 1: + cutAreaEqualPart(0, 1, 3); + break; + case 2: + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + addLine(3, Line.Direction.HORIZONTAL, 4f / 5); + addLine(2, Line.Direction.HORIZONTAL, 3f / 5); + addLine(1, Line.Direction.HORIZONTAL, 2f / 5); + addLine(0, Line.Direction.HORIZONTAL, 1f / 5); + break; + case 3: + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + addLine(3, Line.Direction.VERTICAL, 4f / 5); + addLine(2, Line.Direction.VERTICAL, 3f / 5); + addLine(1, Line.Direction.VERTICAL, 2f / 5); + addLine(0, Line.Direction.VERTICAL, 1f / 5); + break; + case 4: + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + addLine(3, Line.Direction.HORIZONTAL, 1f / 5); + addLine(2, Line.Direction.HORIZONTAL, 2f / 5); + addLine(1, Line.Direction.HORIZONTAL, 3f / 5); + addLine(0, Line.Direction.HORIZONTAL, 4f / 5); + break; + case 5: + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + addLine(3, Line.Direction.VERTICAL, 1f / 5); + addLine(2, Line.Direction.VERTICAL, 2f / 5); + addLine(1, Line.Direction.VERTICAL, 3f / 5); + addLine(0, Line.Direction.VERTICAL, 4f / 5); + break; + case 6: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(1, 2, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 7: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(1, 2, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 8: + addLine(0, Line.Direction.HORIZONTAL, 4f / 5); + cutAreaEqualPart(1, 5, Line.Direction.VERTICAL); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + break; + case 9: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(2, 2, Line.Direction.VERTICAL); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + break; + case 10: + cutAreaEqualPart(0, 2, 1); + addLine(5, Line.Direction.VERTICAL, 1f / 2); + addLine(4, Line.Direction.VERTICAL, 1f / 2); + break; + default: + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java new file mode 100644 index 0000000..e7ebb47 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FiveStraightLayout.java @@ -0,0 +1,94 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class FiveStraightLayout extends NumberStraightLayout { + + public FiveStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 15; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 2f / 5); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + break; + case 1: + addLine(0, Line.Direction.HORIZONTAL, 3f / 5); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + break; + case 2: + addLine(0, Line.Direction.VERTICAL, 2f / 5); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 3: + addLine(0, Line.Direction.VERTICAL, 2f / 5); + cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 4: + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + cutAreaEqualPart(1, 4, Line.Direction.VERTICAL); + break; + case 5: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + break; + case 6: + addLine(0, Line.Direction.VERTICAL, 3f / 4); + cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL); + break; + case 7: + addLine(0, Line.Direction.VERTICAL, 1f / 4); + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + break; + case 8: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + break; + case 9: + addLine(0, Line.Direction.VERTICAL, 1f / 4); + addLine(1, Line.Direction.VERTICAL, 2f / 3); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 10: + addCross(0, 1f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 11: + addCross(0, 2f / 3); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 12: + addCross(0, 1f / 3, 2f / 3); + addLine(3, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 13: + addCross(0, 2f / 3, 1f / 3); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 14: + cutSpiral(0); + break; + default: + cutAreaEqualPart(0, 5, Line.Direction.HORIZONTAL); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java new file mode 100644 index 0000000..bba0630 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/FourStraightLayout.java @@ -0,0 +1,52 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class FourStraightLayout extends NumberStraightLayout { + private static final String TAG = "FourStraightLayout"; + + public FourStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addCross(0, 1f / 2); + break; + case 1: + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + break; + case 3: + addLine(0, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 2f / 3); + cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + addLine(1, Line.Direction.HORIZONTAL, 1f / 3); + break; + default: + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java new file mode 100644 index 0000000..e29b0b4 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NineStraightLayout.java @@ -0,0 +1,76 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class NineStraightLayout extends NumberStraightLayout { + public NineStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 8; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 2, 2); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(2, 4, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 4, Line.Direction.HORIZONTAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(2, 4, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 4, Line.Direction.VERTICAL); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + addLine(1, Line.Direction.VERTICAL, 3f / 4); + addLine(1, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL); + addLine(1, Line.Direction.HORIZONTAL, 3f / 4); + addLine(1, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 5: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + addLine(2, Line.Direction.HORIZONTAL, 3f / 4); + addLine(2, Line.Direction.HORIZONTAL, 1f / 3); + cutAreaEqualPart(1, 3, Line.Direction.HORIZONTAL); + addLine(0, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + break; + case 6: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + addLine(2, Line.Direction.VERTICAL, 3f / 4); + addLine(2, Line.Direction.VERTICAL, 1f / 3); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + addLine(0, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 3); + break; + case 7: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + cutAreaEqualPart(1, 1, 3); + break; + default: + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java new file mode 100644 index 0000000..14a8618 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/NumberStraightLayout.java @@ -0,0 +1,31 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.straight.StraightPuzzleLayout; + + +/** + * @author wupanjie + */ +public abstract class NumberStraightLayout extends StraightPuzzleLayout { + static final String TAG = "NumberStraightLayout"; + protected int theme; + + public NumberStraightLayout(int theme) { + if (theme >= getThemeCount()) { + Log.e(TAG, "NumberStraightLayout: the most theme count is " + + getThemeCount() + + " ,you should let theme from 0 to " + + (getThemeCount() - 1) + + " ."); + } + this.theme = theme; + } + + public abstract int getThemeCount(); + + public int getTheme() { + return theme; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java new file mode 100644 index 0000000..2367174 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/OneStraightLayout.java @@ -0,0 +1,45 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class OneStraightLayout extends NumberStraightLayout { + + public OneStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 2: + addCross(0, 1f / 2); + break; + case 3: + cutAreaEqualPart(0, 2, 1); + break; + case 4: + cutAreaEqualPart(0, 1, 2); + break; + case 5: + cutAreaEqualPart(0, 2, 2); + break; + default: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java new file mode 100644 index 0000000..307e975 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SevenStraightLayout.java @@ -0,0 +1,77 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class SevenStraightLayout extends NumberStraightLayout { + public SevenStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 9; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + cutAreaEqualPart(1, 4, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + cutAreaEqualPart(1, 4, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + cutAreaEqualPart(1, 1, 2); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + cutAreaEqualPart(1, 3, Line.Direction.VERTICAL); + addCross(0, 1f / 2); + break; + case 4: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(2, 3, Line.Direction.HORIZONTAL); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 5: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + addLine(1, Line.Direction.VERTICAL, 3f / 4); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 2f / 5); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 6: + addLine(0, Line.Direction.VERTICAL, 2f / 3); + addLine(1, Line.Direction.HORIZONTAL, 3f / 4); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 2f / 5); + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 7: + addLine(0, Line.Direction.VERTICAL, 1f / 4); + addLine(1, Line.Direction.VERTICAL, 2f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 3f / 4); + addLine(1, Line.Direction.HORIZONTAL, 1f / 3); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 8: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + cutAreaEqualPart(2, 3, Line.Direction.VERTICAL); + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + default: + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java new file mode 100644 index 0000000..86b93d0 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/SixStraightLayout.java @@ -0,0 +1,93 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class SixStraightLayout extends NumberStraightLayout { + + public SixStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 12; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 2, 1); + break; + + case 1: + cutAreaEqualPart(0, 1, 2); + break; + case 2: + addCross(0, 2f / 3, 1f / 2); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + addLine(2, Line.Direction.VERTICAL, 1f / 2); + break; + case 3: + addCross(0, 1f / 2, 2f / 3); + addLine(3, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 4: + addCross(0, 1f / 2, 1f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 5: + addCross(0, 1f / 3, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 6: + addLine(0, Line.Direction.HORIZONTAL, 4f / 5); + cutAreaEqualPart(1, 5, Line.Direction.VERTICAL); + break; + case 7: + addLine(0, Line.Direction.HORIZONTAL, 1f / 4); + addLine(1, Line.Direction.HORIZONTAL, 2f / 3); + addLine(1, Line.Direction.VERTICAL, 1f / 4); + addLine(2, Line.Direction.VERTICAL, 2f / 3); + addLine(4, Line.Direction.VERTICAL, 1f / 2); + break; + case 8: + addCross(0, 1f / 3); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + addLine(4, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 9: + addCross(0, 2f / 3, 1f / 3); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 10: + addCross(0, 2f / 3); + addLine(2, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 11: + addCross(0, 1f / 3, 2f / 3); + addLine(3, Line.Direction.HORIZONTAL, 1f / 2); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 12: + addCross(0, 1f / 3); + addLine(2, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + break; + default: + addCross(0, 2f / 3, 1f / 2); + addLine(3, Line.Direction.VERTICAL, 1f / 2); + addLine(2, Line.Direction.VERTICAL, 1f / 2); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java new file mode 100644 index 0000000..103be9c --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/StraightLayoutHelper.java @@ -0,0 +1,69 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + + +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class StraightLayoutHelper { + private StraightLayoutHelper() { + + } + + public static List getAllThemeLayout(int pieceCount) { + List puzzleLayouts = new ArrayList<>(); + switch (pieceCount) { + case 1: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new OneStraightLayout(i)); + } + break; + case 2: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new TwoStraightLayout(i)); + } + break; + case 3: + for (int i = 0; i < 6; i++) { + puzzleLayouts.add(new ThreeStraightLayout(i)); + } + break; + case 4: + for (int i = 0; i < 8; i++) { + puzzleLayouts.add(new FourStraightLayout(i)); + } + break; + case 5: + for (int i = 0; i < 17; i++) { + puzzleLayouts.add(new FiveStraightLayout(i)); + } + break; + case 6: + for (int i = 0; i < 12; i++) { + puzzleLayouts.add(new SixStraightLayout(i)); + } + break; + case 7: + for (int i = 0; i < 9; i++) { + puzzleLayouts.add(new SevenStraightLayout(i)); + } + break; + case 8: + for (int i = 0; i < 11; i++) { + puzzleLayouts.add(new EightStraightLayout(i)); + } + break; + case 9: + for (int i = 0; i < 8; i++) { + puzzleLayouts.add(new NineStraightLayout(i)); + } + break; + } + + return puzzleLayouts; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java new file mode 100644 index 0000000..add863d --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/ThreeStraightLayout.java @@ -0,0 +1,50 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + + +import com.huantansheng.easyphotos.models.puzzle.Line; + +/** + * @author wupanjie + */ +public class ThreeStraightLayout extends NumberStraightLayout { + + public ThreeStraightLayout(int theme) { + super(theme); + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + case 1: + cutAreaEqualPart(0, 3, Line.Direction.VERTICAL); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(0, Line.Direction.VERTICAL, 1f / 2); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + addLine(1, Line.Direction.VERTICAL, 1f / 2); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(0, Line.Direction.HORIZONTAL, 1f / 2); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 1f / 2); + addLine(1, Line.Direction.HORIZONTAL, 1f / 2); + break; + default: + cutAreaEqualPart(0, 3, Line.Direction.HORIZONTAL); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java new file mode 100644 index 0000000..3b2b896 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/puzzle/template/straight/TwoStraightLayout.java @@ -0,0 +1,58 @@ +package com.huantansheng.easyphotos.models.puzzle.template.straight; + +import android.util.Log; + +import com.huantansheng.easyphotos.models.puzzle.Line; + + +/** + * @author wupanjie + */ +public class TwoStraightLayout extends NumberStraightLayout { + private float mRadio = 1f / 2; + + public TwoStraightLayout(int theme) { + super(theme); + } + + public TwoStraightLayout(float radio, int theme) { + super(theme); + if (mRadio > 1) { + Log.e(TAG, "CrossLayout: the radio can not greater than 1f"); + mRadio = 1f; + } + mRadio = radio; + } + + @Override + public int getThemeCount() { + return 6; + } + + @Override + public void layout() { + switch (theme) { + case 0: + addLine(0, Line.Direction.HORIZONTAL, mRadio); + break; + case 1: + addLine(0, Line.Direction.VERTICAL, mRadio); + break; + case 2: + addLine(0, Line.Direction.HORIZONTAL, 1f / 3); + break; + case 3: + addLine(0, Line.Direction.HORIZONTAL, 2f / 3); + break; + case 4: + addLine(0, Line.Direction.VERTICAL, 1f / 3); + break; + case 5: + addLine(0, Line.Direction.VERTICAL, 2f / 3); + break; + default: + addLine(0, Line.Direction.HORIZONTAL, mRadio); + break; + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java new file mode 100644 index 0000000..e749c59 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/StickerModel.java @@ -0,0 +1,220 @@ +package com.huantansheng.easyphotos.models.sticker; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import androidx.fragment.app.FragmentManager; +import android.view.View; +import android.view.ViewGroup; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.models.sticker.cache.StickerCache; +import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData; +import com.huantansheng.easyphotos.models.sticker.view.BitmapSticker; +import com.huantansheng.easyphotos.models.sticker.view.TextSticker; +import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener; +import com.huantansheng.easyphotos.models.sticker.view.EditFragment; +import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils; +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack; + +import java.util.ArrayList; +import java.util.List; + +/** + * 贴图view的管理器,用于module与外部解耦 + * Created by huan on 2017/7/24. + */ + +public class StickerModel { + public static final ArrayList textDataList = new ArrayList<>(); + + public List bitmapStickers; + public List textStickers; + public BitmapSticker currBitmapSticker; + public TextSticker currTextSticker; + + + public StickerModel() { + super(); + this.bitmapStickers = new ArrayList<>(); + this.textStickers = new ArrayList<>(); + } + + public void addBitmapSticker(Context cxt, String imagePath, int imageResourceId, ViewGroup rootgroup) { + + if (bitmapStickers.size() > 0 && !bitmapStickers.get(bitmapStickers.size() - 1).isChecked) { + bitmapStickers.get(bitmapStickers.size() - 1).delete(); + } + final BitmapSticker sticker = new BitmapSticker(cxt, imagePath, imageResourceId, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2); + sticker.setOnStickerClickListener(new OnStickerClickListener() { + @Override + public void onDelete() { + bitmapStickers.remove(sticker); + } + + @Override + public void onEditor() { + + } + + @Override + public void onTop() { + bitmapStickers.remove(sticker); + bitmapStickers.add(sticker); + } + + @Override + public void onUsing() { + if (currBitmapSticker != null && currBitmapSticker != sticker) { + currBitmapSticker.setUsing(false); + currBitmapSticker = sticker; + } + } + }); + if (currBitmapSticker != null) { + currBitmapSticker.setUsing(false); + } + rootgroup.addView(sticker); + currBitmapSticker = sticker; + bitmapStickers.add(sticker); + } + + + public void addTextSticker(final Context cxt, final FragmentManager fragmentManager, String text, ViewGroup rootgroup) { + + if (textStickers.size() > 0 && !textStickers.get(textStickers.size() - 1).isChecked) { + textStickers.get(textStickers.size() - 1).delete(); + } + final TextSticker sticker = new TextSticker(cxt, text, rootgroup.getWidth() / 2, rootgroup.getHeight() / 2); + sticker.setOnStickerClickListener(new OnStickerClickListener() { + @Override + public void onDelete() { + textStickers.remove(sticker); + } + + @Override + public void onEditor() { + EditFragment.show(fragmentManager, sticker); + } + + @Override + public void onTop() { + textStickers.remove(sticker); + textStickers.add(sticker); + } + + @Override + public void onUsing() { + if (currTextSticker != null && currTextSticker != sticker) { + currTextSticker.setUsing(false); + currTextSticker = sticker; + } + } + }); + if (currBitmapSticker != null) { + currBitmapSticker.setUsing(false); + } + rootgroup.addView(sticker); + currTextSticker = sticker; + textStickers.add(sticker); + } + + public void save(Activity act, ViewGroup stickerGroup, View imageGroup, int imageWidth, int imageHeight, final String dirPath, final String namePrefix, final boolean notifyMedia, final SaveBitmapCallBack callBack) { + + if (null != this.currBitmapSticker && this.currBitmapSticker.isUsing()) { + this.currBitmapSticker.setUsing(false); + } + if (null != this.currTextSticker && this.currTextSticker.isUsing()) { + this.currTextSticker.setUsing(false); + } + + for (BitmapSticker bs : bitmapStickers) { + if (bs.isUsing()) { + bs.setUsing(false); + } + } + + for (TextSticker ts : textStickers) { + if (ts.isUsing()) { + ts.setUsing(false); + } + } + + Bitmap srcBitmap = Bitmap.createBitmap(stickerGroup.getWidth(), stickerGroup.getHeight(), Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(srcBitmap); + stickerGroup.draw(canvas); + + Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, imageGroup.getLeft(), imageGroup.getTop(), imageGroup.getWidth(), imageGroup.getHeight()); + BitmapUtils.recycle(srcBitmap); + Bitmap saveBitmap = null; + if (imageGroup.getWidth() > imageWidth || imageGroup.getHeight() > imageHeight) { + saveBitmap = Bitmap.createScaledBitmap(cropBitmap, imageWidth, imageHeight, true); + BitmapUtils.recycle(cropBitmap); + } else { + saveBitmap = cropBitmap; + } + + EasyPhotos.saveBitmapToDir(act, dirPath, namePrefix, saveBitmap, notifyMedia, callBack); + + } + + public void setCanvasSize(final Bitmap b, final ViewGroup imageGroup) { + if (imageGroup.getMeasuredWidth() == 0) { + imageGroup.post(new Runnable() { + @Override + public void run() { + setSize(b, imageGroup); + } + }); + } else { + setSize(b, imageGroup); + } + } + + private void setSize(Bitmap b, ViewGroup v) { + int bW = b.getWidth(); + int bH = b.getHeight(); + + int vW = v.getMeasuredWidth(); + int vH = v.getMeasuredHeight(); + + float scalW = (float) vW / (float) bW; + float scalH = (float) vH / (float) bH; + + ViewGroup.LayoutParams params = v.getLayoutParams(); + //如果图片小于viewGroup的宽高则把viewgroup设置为图片宽高 +// if (bW < vW && bH < vH) { +// params.width = bW; +// params.height = bH; +// v.setLayoutParams(params); +// return; +// } + if (bW >= bH) { + params.width = vW; + params.height = (int) (scalW * bH); + } else { + params.width = (int) (scalH * bW); + params.height = vH; + } + if (params.width > vW) { + float tempScaleW = (float) vW / (float) params.width; + params.width = vW; + params.height = (int) (params.height * tempScaleW); + } + if (params.height > vH) { + float tempScaleH = (float) vH / (float) params.height; + params.height = vH; + params.width = (int) (params.width * tempScaleH); + } + v.setLayoutParams(params); + } + + /** + * 释放资源 + */ + public void release() { + StickerCache.get().clear(); + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java new file mode 100644 index 0000000..2cd2f15 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/cache/StickerCache.java @@ -0,0 +1,114 @@ +package com.huantansheng.easyphotos.models.sticker.cache; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import androidx.annotation.IdRes; + +import com.huantansheng.easyphotos.EasyPhotos; + +import java.util.LinkedHashMap; + +/** + * 贴纸的图片缓存器 + * Created by huan on 2017/12/8. + */ + +public class StickerCache { + private static StickerCache instance = null; + + public static StickerCache get() { + if (null == instance) { + synchronized (StickerCache.class) { + if (null == instance) { + instance = new StickerCache(); + } + } + } + return instance; + } + + private LinkedHashMap srcBitmapCache = null; + private LinkedHashMap mirrorBitmapCache = null; + private LinkedHashMap bitmapUsedCount = null; + + private StickerCache() { + srcBitmapCache = new LinkedHashMap<>(); + mirrorBitmapCache = new LinkedHashMap<>(); + bitmapUsedCount = new LinkedHashMap<>(); + } + + public Bitmap getSrcBitmap(String path) { + Bitmap bitmap = srcBitmapCache.get(path); + if (null == bitmap) { + bitmap = BitmapFactory.decodeFile(path); + srcBitmapCache.put(path, bitmap); + bitmapUsedCount.put(path, 0); + convertMirror(path, bitmap); + } + + int count = bitmapUsedCount.get(path); + bitmapUsedCount.put(path, ++count); + return bitmap; + } + + public Bitmap getSrcBitmap(Resources resources, @IdRes int resId) { + String path = String.valueOf(resId); + Bitmap bitmap = srcBitmapCache.get(path); + if (null == bitmap) { + bitmap = BitmapFactory.decodeResource(resources, resId); + srcBitmapCache.put(path, bitmap); + bitmapUsedCount.put(path, 0); + convertMirror(path, bitmap); + } + + int count = bitmapUsedCount.get(path); + bitmapUsedCount.put(path, ++count); + return bitmap; + } + + public Bitmap getMirrorBitmap(String key) { + return mirrorBitmapCache.get(key); + } + + + public void clear() { + for (String key : srcBitmapCache.keySet()) { + recycle(key); + } + } + + public void recycle(String key) { + if (!srcBitmapCache.containsKey(key)) { + return; + } + + int count = bitmapUsedCount.get(key); + if (count > 1) { + count--; + bitmapUsedCount.put(key, count); + return; + } + + EasyPhotos.recycle(srcBitmapCache.get(key), mirrorBitmapCache.get(key)); + removeKey(key); + } + + private void convertMirror(String key, Bitmap a) { + int w = a.getWidth(); + int h = a.getHeight(); + + Matrix m = new Matrix(); + m.postScale(-1, 1); //镜像水平翻转 + Bitmap mirrorBitmap = Bitmap.createBitmap(a, 0, 0, w, h, m, true); + mirrorBitmapCache.put(key, mirrorBitmap); + } + + + private void removeKey(String key) { + srcBitmapCache.remove(key); + mirrorBitmapCache.remove(key); + bitmapUsedCount.remove(key); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java new file mode 100644 index 0000000..13c0b06 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/entity/TextStickerData.java @@ -0,0 +1,48 @@ +package com.huantansheng.easyphotos.models.sticker.entity; + +import android.os.Parcel; +import android.os.Parcelable; + +/** + * 文字贴纸数据类 + * Created by huan on 2017/12/14. + */ + +public class TextStickerData implements Parcelable { + + public String stickerName;//文字贴纸的名字 + public String stickerValue;//文字贴纸的文字内容 + + public TextStickerData(String stickerName, String stickerValue) { + this.stickerName = stickerName; + this.stickerValue = stickerValue; + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(this.stickerName); + dest.writeString(this.stickerValue); + } + + protected TextStickerData(Parcel in) { + this.stickerName = in.readString(); + this.stickerValue = in.readString(); + } + + public static final Parcelable.Creator CREATOR = new Parcelable.Creator() { + @Override + public TextStickerData createFromParcel(Parcel source) { + return new TextStickerData(source); + } + + @Override + public TextStickerData[] newArray(int size) { + return new TextStickerData[size]; + } + }; +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java new file mode 100644 index 0000000..f7be75d --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/listener/OnStickerClickListener.java @@ -0,0 +1,16 @@ +package com.huantansheng.easyphotos.models.sticker.listener; + +/** + * 贴纸的点击监听 + * Created by huan on 2017/12/12. + */ + +public interface OnStickerClickListener { + void onDelete(); + + void onEditor(); + + void onTop(); + + void onUsing(); +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java new file mode 100644 index 0000000..b7a5572 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/BitmapSticker.java @@ -0,0 +1,441 @@ +package com.huantansheng.easyphotos.models.sticker.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.RectF; +import android.graphics.Region; +import androidx.core.content.ContextCompat; +import android.text.TextUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.sticker.cache.StickerCache; +import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener; + + +/** + * 自定义贴图view + * Created by huan on 2017/7/24. + */ + +public class BitmapSticker extends View { + + public boolean isChecked = false; + private Bitmap image; + private Bitmap mirrorImage; + private Bitmap srcImage; + private boolean mirror = true; + private int imageWidth; + private int imageHeight; + private Bitmap btDelete; + private Bitmap btScale; + private Bitmap btMirror; + // private Bitmap btRotate; + private int btSize; + private Matrix mMatrix; + private float[] srcPs, dstPs; + private Paint mPaint; + private Paint framePaint; + private boolean isUsing = true; + private float downX1; + private float downY1; + private float downX2; + private float downY2; + private ClickType clickType; + private boolean isOut = false; + private GestureDetector gestureDetector; + private float lastDegree; + private float lastDoubleDegress; + private OnStickerClickListener listener; + private int startX, startY; + private Path path; + + private String cacheKey; + + public BitmapSticker(Context context, String bitmapPath, int bitmapResourceId, int ViewGroupCenterX, int ViewGroupCenterY) { + super(context); + + if (TextUtils.isEmpty(bitmapPath)) { + image = StickerCache.get().getSrcBitmap(getResources(), bitmapResourceId); + cacheKey = String.valueOf(bitmapResourceId); + } else { + image = StickerCache.get().getSrcBitmap(bitmapPath); + cacheKey = bitmapPath; + } + path = new Path(); + srcImage = image; + mirrorImage = StickerCache.get().getMirrorBitmap(cacheKey); + + this.imageWidth = image.getWidth(); + this.imageHeight = image.getHeight(); + this.startX = ViewGroupCenterX - imageWidth / 2; + if (this.startX < 100) { + this.startX = ViewGroupCenterX / 2; + } + this.startY = ViewGroupCenterY - imageHeight / 2; + if (this.startY < 100) { + this.startY = ViewGroupCenterY / 2; + } + initButtons(); + initPs(); + initPaints(); + initMatrix(); + initCanvasPosition(); + lastDegree = computeDegree(new Point(imageWidth, imageHeight), new Point(imageWidth / 2, imageHeight / 2)); + lastDoubleDegress = 1000; + gestureDetector = new GestureDetector(context, new StickerGestureListener()); + + } + + public void setOnStickerClickListener(OnStickerClickListener listener) { + this.listener = listener; + } + + private void initCanvasPosition() { + mMatrix.postTranslate(startX, startY); + mMatrix.mapPoints(dstPs, srcPs); + } + + private void initMatrix() { + mMatrix = new Matrix(); + } + + private void initPaints() { + mPaint = new Paint(); + mPaint.setAntiAlias(true); + mPaint.setDither(true); + mPaint.setFilterBitmap(true); + + framePaint = new Paint(); + framePaint.setAntiAlias(true); + framePaint.setStrokeWidth(1); + framePaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white)); + } + + private void initPs() { + srcPs = new float[]{0, 0, imageWidth, 0, imageWidth, imageHeight, 0, imageHeight, imageWidth / 2, imageHeight / 2}; + dstPs = srcPs.clone(); + } + + private void initButtons() { + btDelete = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_delete_easy_photos); + btMirror = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_mirror_easy_photos); + btScale = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_controller_easy_photos); +// btRotate = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rotate); + btSize = btDelete.getWidth(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + canvas.drawBitmap(image, mMatrix, mPaint); + if (isUsing) + drawOthers(canvas); + } + + private void drawOthers(Canvas canvas) { + path.reset(); + path.moveTo(dstPs[0], dstPs[1]); + path.lineTo(dstPs[2], dstPs[3]); + path.lineTo(dstPs[4], dstPs[5]); + path.lineTo(dstPs[6], dstPs[7]); + path.lineTo(dstPs[0], dstPs[1]); + for (int i = 0; i < 7; i += 2) { + if (i == 6) { + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[0], dstPs[1], framePaint); + break; + } + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[i + 2], dstPs[i + 3], framePaint); + } + + canvas.drawBitmap(btDelete, dstPs[2] - btSize / 2, dstPs[3] - btSize / 2, mPaint); + canvas.drawBitmap(btMirror, dstPs[0] - btSize / 2, dstPs[1] - btSize / 2, mPaint); + canvas.drawBitmap(btScale, dstPs[4] - btSize / 2, dstPs[5] - btSize / 2, mPaint); +// canvas.drawBitmap(btRotate, dstPs[6] - btSize / 2, dstPs[7] - btSize / 2, mPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + gestureDetector.onTouchEvent(event); + if (MotionEvent.ACTION_UP == event.getAction() || MotionEvent.ACTION_POINTER_UP == event.getAction() || MotionEvent.ACTION_POINTER_1_UP == event.getAction() || MotionEvent.ACTION_POINTER_2_UP == event.getAction()) { + setDoubleDownPoints(0, 0, 0, 0); + lastDoubleDegress = 1000; + lastDegree = computeDegree(new Point((int) dstPs[4], (int) dstPs[5]), new Point((int) dstPs[8], (int) dstPs[9])); + } + + return !isOut; + } + + private void setDoubleDownPoints(float x1, float y1, float x2, float y2) { + downX1 = x1; + downY1 = y1; + downX2 = x2; + downY2 = y2; + } + + private void calculateClickType(int x, int y) { + RectF rectF = new RectF(x - btSize / 2 - 40, y - btSize / 2 - 40, x + btSize / 2 + 40, y + btSize / 2 + 40); + if (rectF.contains(dstPs[2] - 20, dstPs[3])) { + clickType = ClickType.DELETE; + } else if (rectF.contains(dstPs[0], dstPs[1])) { + clickType = ClickType.MIRROR; + } else if (rectF.contains(dstPs[4] + 20, dstPs[5])) { + clickType = ClickType.SCALE; + } else if (rectF.contains(dstPs[6] - 20, dstPs[7])) { + clickType = ClickType.IMAGE; + } else { + + RectF bounds = new RectF(); + path.computeBounds(bounds, true); + Region region = new Region(); + region.setPath(path, new Region((int)bounds.left, (int)bounds.top,(int)bounds.right, (int)bounds.bottom)); + + if (region.contains(x, y)) { + if (isOut) { + isOut = false; + } + if (!isUsing) { + isUsing = true; + listener.onUsing(); + postInvalidate(); + } + clickType = ClickType.IMAGE; + } else { + if (isUsing) { + isUsing = false; + postInvalidate(); + } + if (!isOut) { + isOut = true; + } + clickType = ClickType.OUT; + } + } + } + + public void delete() { + if (null == listener) { + throw new NullPointerException("OnStickerClickListener listener is null"); + } + setVisibility(GONE); + StickerCache.get().recycle(cacheKey); + listener.onDelete(); + } + + private void mirror() { + if (mirror) { + image = mirrorImage; + } else { + image = srcImage; + } + mirror = !mirror; + matrixMap(); + } + + private void top() { + bringToFront();//置顶 + invalidate(); + listener.onTop(); + } + + private void move(float distansX, float distansY) { + mMatrix.postTranslate(distansX, distansY); + matrixMap(); + } + + private void controller(MotionEvent event) { + scale(event); + rotate(event); + } + + private void scale(MotionEvent event) { + float originalX1; + float originalY1; + float originalX2; + float originalY2; + float moveX1; + float moveY1; + float moveX2; + float moveY2; + if (event.getPointerCount() == 2) { + originalX2 = downX2; + originalY2 = downY2; + originalX1 = downX1; + originalY1 = downY1; + + moveX2 = event.getX(1); + moveY2 = event.getY(1); + moveX1 = event.getX(0); + moveY1 = event.getY(0); + + } else { + originalX2 = dstPs[4]; + originalY2 = dstPs[5]; + originalX1 = dstPs[0]; + originalY1 = dstPs[1]; + + moveX2 = event.getX(); + moveY2 = event.getY(); + moveX1 = originalX1; + moveY1 = originalY1; + } + + float temp1 = getDistanceOfTwoPoints(originalX2, originalY2, originalX1, originalY1); + float temp2 = getDistanceOfTwoPoints(moveX2, moveY2, moveX1, moveY1); + + float scalValue = temp2 / temp1; + + if (getScaleValue() < (float) 0.3 && scalValue < (float) 1) { + return; + } + mMatrix.postScale(scalValue, scalValue, dstPs[8], dstPs[9]); + matrixMap(); + if (event.getPointerCount() == 2) { + setDoubleDownPoints(moveX1, moveY1, moveX2, moveY2); + } + } + + private void rotate(MotionEvent event) { + if (event.getPointerCount() == 2) { + float preDegree = computeDegree(new Point((int) event.getX(0), (int) event.getY(0)), new Point((int) event.getX(1), (int) event.getY(1))); + if (lastDoubleDegress == 1000) { + lastDoubleDegress = preDegree; + } + mMatrix.postRotate(preDegree - lastDoubleDegress, dstPs[8], dstPs[9]); + matrixMap(); + lastDoubleDegress = preDegree; + + } else { + float preDegree = computeDegree(new Point((int) event.getX(), (int) event.getY()), new Point((int) dstPs[8], (int) dstPs[9])); + mMatrix.postRotate(preDegree - lastDegree, dstPs[8], dstPs[9]); + matrixMap(); + lastDegree = preDegree; + } + } + + // 获取饰品缩放比例(与原图相比) + public float getScaleValue() { + float preDistance = (srcPs[8] - srcPs[0]) * (srcPs[8] - srcPs[0]) + (srcPs[9] - srcPs[1]) * (srcPs[9] - srcPs[1]); + float lastDistance = (dstPs[8] - dstPs[0]) * (dstPs[8] - dstPs[0]) + (dstPs[9] - dstPs[1]) * (dstPs[9] - dstPs[1]); + float scaleValue = (float) Math.sqrt(lastDistance / preDistance); + return scaleValue; + } + + private void matrixMap() { + mMatrix.mapPoints(dstPs, srcPs); + postInvalidate(); + } + + public void setUsing(boolean isUsing) { + this.isUsing = isUsing; + postInvalidate(); + } + + public boolean isUsing() { + return this.isUsing; + } + + private float getDistanceOfTwoPoints(float x1, float y1, float x2, float y2) { + return (float) (Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))); + } + + public float computeDegree(Point p1, Point p2) { + float tran_x = p1.x - p2.x; + float tran_y = p1.y - p2.y; + float degree = 0.0f; + float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI); + if (!Float.isNaN(angle)) { + if (tran_x >= 0 && tran_y <= 0) {//第一象限 + degree = angle; + } else if (tran_x <= 0 && tran_y <= 0) {//第二象限 + degree = angle; + } else if (tran_x <= 0 && tran_y >= 0) {//第三象限 + degree = -180 - angle; + } else if (tran_x >= 0 && tran_y >= 0) {//第四象限 + degree = 180 - angle; + } + } + return degree; + } + + private enum ClickType { + DELETE, MIRROR, SCALE, ROTATE, IMAGE, OUT + } + + private class StickerGestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + switch (clickType) { + case DELETE: + delete(); + break; + case MIRROR: + mirror(); + break; + case SCALE: + break; + case ROTATE: + break; + case IMAGE: + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { +// Log.d("scroll", "onScroll: " + distanceX); + switch (clickType) { + case DELETE: + break; + case MIRROR: + break; + case SCALE: + if (e2.getPointerCount() > 1) break; + controller(e2); + break; + case ROTATE: +// rotate(e2); + break; + case IMAGE: + if (e2.getPointerCount() == 2) { + if (downX1 + downY1 + downX2 + downY2 == 0) { + setDoubleDownPoints(e2.getX(0), e2.getY(0), e2.getX(1), e2.getY(1)); + } + controller(e2); + } else if (e2.getPointerCount() == 1) { + move(-distanceX, -distanceY); + } + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + isChecked = true; + calculateClickType((int) e.getX(), (int) e.getY()); + if (clickType == ClickType.IMAGE) { + top(); + } + return true; + } + + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java new file mode 100644 index 0000000..d132e1a --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/EditFragment.java @@ -0,0 +1,201 @@ +package com.huantansheng.easyphotos.models.sticker.view; + +import android.content.Context; +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import androidx.annotation.IdRes; +import androidx.annotation.Nullable; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentManager; +import androidx.core.content.ContextCompat; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.SeekBar; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; + +/** + * 文字贴纸,编辑界面 + * Created by huan on 2017/12/13. + */ + +public class EditFragment extends DialogFragment implements View.OnClickListener { + + private TextView tvSample; + private EditText et; + private SeekBar seekBar; + + private TextSticker textSticker = null; + + private InputMethodManager inputMethodManager; + + public static EditFragment show(FragmentManager fm, TextSticker sticker) { + EditFragment editFragment = new EditFragment(); + editFragment.textSticker = sticker; + editFragment.show(fm, "edit"); + return editFragment; + } + + @Nullable + @Override + public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + + getDialog().requestWindowFeature(Window.FEATURE_NO_TITLE); + + View rootView = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_text_sticker_easy_photos, container); + + tvSample = (TextView) rootView.findViewById(R.id.tv_sample); + et = (EditText) rootView.findViewById(R.id.et); + seekBar = (SeekBar) rootView.findViewById(R.id.m_seek_bar); + + l(rootView, R.id.iv_red, R.id.iv_orange, R.id.iv_yellow, R.id.iv_green, R.id.iv_cyan, R.id.iv_blue, R.id.iv_purple, R.id.iv_black, R.id.iv_gray, R.id.iv_white, R.id.tv_done, R.id.iv_clear); + + seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int i, boolean b) { + if (b) { + setTextAlpha(i); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + + + et.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { + + } + + @Override + public void afterTextChanged(Editable editable) { + tvSample.setText(editable.toString()); + if (null != textSticker) { + textSticker.resetText(editable.toString()); + } + } + }); + return rootView; + } + + private void l(View view, @IdRes int... resIds) { + for (int resId : resIds) { + view.findViewById(resId).setOnClickListener(this); + } + } + + @Override + public void onResume() { + super.onResume(); + bindingSticker(); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + Window dialogWindow = getDialog().getWindow(); + if (null != dialogWindow) { + WindowManager.LayoutParams attrs = dialogWindow.getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + dialogWindow.setAttributes(attrs); + dialogWindow.requestFeature(Window.FEATURE_NO_TITLE); + } + + super.onActivityCreated(savedInstanceState); + + if (null != dialogWindow) { + dialogWindow.setBackgroundDrawable(new ColorDrawable(0x00000000)); + dialogWindow.setLayout(WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT); + dialogWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } + } + + + public void bindingSticker() { + String text = textSticker.getText(); + tvSample.setText(text); + et.setText(text); + et.setSelection(text.length()); + int alpha = textSticker.getTextAlpha(); + float alphaF = (float) alpha / (float) 255; + seekBar.setProgress(alpha); + tvSample.setTextColor(textSticker.getTextColor()); + tvSample.setAlpha(alphaF); + inputMethodManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + if (null != inputMethodManager) { + inputMethodManager.showSoftInput(et, 0); + } + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (R.id.iv_red == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_red_easy_photos)); + + } else if (R.id.iv_orange == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_orange_easy_photos)); + + } else if (R.id.iv_yellow == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_yellow_easy_photos)); + + } else if (R.id.iv_green == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_green_easy_photos)); + + } else if (R.id.iv_cyan == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_cyan_easy_photos)); + + } else if (R.id.iv_blue == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_blue_easy_photos)); + + } else if (R.id.iv_purple == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_purple_easy_photos)); + + } else if (R.id.iv_black == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_black_easy_photos)); + + } else if (R.id.iv_gray == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_gray_easy_photos)); + + } else if (R.id.iv_white == id) { + setTextColor(ContextCompat.getColor(getContext(), R.color.text_sticker_white_easy_photos)); + + } else if (R.id.tv_done == id) { + dismiss(); + } else if (R.id.iv_clear == id) { + et.setText(null); + } + + } + + private void setTextColor(int color) { + tvSample.setTextColor(color); + textSticker.setTextColor(color); + } + + private void setTextAlpha(int alpha) { + tvSample.setAlpha((float) alpha / (float) 225); + textSticker.setTextAlpha(alpha); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java new file mode 100644 index 0000000..45ac106 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/models/sticker/view/TextSticker.java @@ -0,0 +1,553 @@ +package com.huantansheng.easyphotos.models.sticker.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Path; +import android.graphics.Point; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.graphics.Typeface; +import androidx.core.content.ContextCompat; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.TextUtils; +import android.view.GestureDetector; +import android.view.MotionEvent; +import android.view.View; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.sticker.listener.OnStickerClickListener; + + +/** + * 自定义贴图view + * Created by huan on 2017/7/24. + */ + +public class TextSticker extends View { + + public boolean isChecked = false; + private String text; + private float textWidth; + private float textHeight; + private Bitmap bitmap; + private Bitmap btDelete; + private Bitmap btController; + // private Bitmap btEditor; + // private Bitmap btRotate; + private int btSize; + private Matrix mMatrix; + private float[] srcPs, dstPs; + private TextPaint textPaint; + private Paint bitmapPaint; + private Paint framePaint; + private boolean isUsing = true; + private float downX1; + private float downY1; + private float downX2; + private float downY2; + private ClickType clickType; + private boolean isOut = false; + private GestureDetector gestureDetector; + private float lastDegree; + private float lastDoubleDegress; + private OnStickerClickListener listener; + private int startX, startY; + private int minWidth = 300, minHeight = 100; + private float minScale; + private StaticLayout textLayout; + private int textLayoutWidth; + + private Canvas bitmapConvas; + + private Path path; + + public TextSticker(Context context, String text, int viewGroupCenterX, int viewGroupCenterY) { + super(context); + + this.text = text; + if (TextUtils.isEmpty(this.text)) { + this.text = context.getString(R.string.text_sticker_hint_easy_photos); + } + path = new Path(); + textLayoutWidth = getResources().getDisplayMetrics().widthPixels / 2; + initButtons(); + initPaints(); + resetSize(); + initStartPoint(viewGroupCenterX, viewGroupCenterY); + initPs(); + resetBitmap(); + initMatrix(); + initCanvasPosition(); + lastDegree = computeDegree(new Point((int) textWidth, (int) textHeight), new Point((int) textWidth / 2, (int) textHeight / 2)); + lastDoubleDegress = 1000; + gestureDetector = new GestureDetector(context, new StickerGestureListener()); + + } + + private void resetBitmap() { + EasyPhotos.recycle(bitmap); + bitmap = Bitmap.createBitmap((int) textWidth, (int) textHeight, Bitmap.Config.ARGB_4444); + bitmapConvas = new Canvas(bitmap); +// bitmapConvas.drawText(text, 0, Math.abs(textPaint.getFontMetrics().top), textPaint); + textLayout.draw(bitmapConvas); + } + + private void initStartPoint(int viewGroupCenterX, int viewGroupCenterY) { + this.startX = viewGroupCenterX - (int) textWidth / 2; + if (this.startX < 100) { + this.startX = viewGroupCenterX / 2; + } + this.startY = viewGroupCenterY - (int) textHeight / 2; + if (this.startY < 100) { + this.startY = viewGroupCenterY / 2; + } + } + + public void setOnStickerClickListener(OnStickerClickListener listener) { + this.listener = listener; + } + + private void initCanvasPosition() { + mMatrix.postTranslate(startX, startY); + mMatrix.mapPoints(dstPs, srcPs); + } + + private void initMatrix() { + mMatrix = new Matrix(); + } + + private void initPaints() { + textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setDither(true); + textPaint.setFilterBitmap(true); + textPaint.setTypeface(Typeface.DEFAULT_BOLD); + textPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.sticker_text_size_easy_photos)); + textPaint.setColor(Color.WHITE); + + + bitmapPaint = new Paint(); + bitmapPaint.setAntiAlias(true); + bitmapPaint.setDither(true); + bitmapPaint.setFilterBitmap(true); + + framePaint = new Paint(); + framePaint.setAntiAlias(true); + bitmapPaint.setDither(true); + bitmapPaint.setFilterBitmap(true); + framePaint.setStrokeWidth(1); + framePaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white)); + } + + + private void resetSize() { + textLayout = new StaticLayout(text, textPaint, textLayoutWidth, + Layout.Alignment.ALIGN_CENTER, 1.0F, 0.0F, true); + textWidth = minWidth; + textHeight = minHeight; + if (textWidth < textLayout.getWidth()) { + textWidth = textLayout.getWidth(); + } + if (textHeight < textLayout.getHeight()) { + textHeight = textLayout.getHeight(); + } + minScale = minWidth / textWidth; + + } + + private void initPs() { + srcPs = new float[]{0, 0, textWidth, 0, textWidth, textHeight, 0, textHeight, textWidth / 2, textHeight / 2}; + dstPs = srcPs.clone(); + } + + private void initButtons() { + btDelete = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_delete_easy_photos); +// btEditor = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_editor_easy_photos); + btController = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_controller_easy_photos); +// btRotate = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_rotate); + btSize = btDelete.getWidth(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + canvas.drawBitmap(bitmap, mMatrix, bitmapPaint); + if (isUsing) + drawOthers(canvas); + } + + private void drawOthers(Canvas canvas) { + path.reset(); + path.moveTo(dstPs[0], dstPs[1]); + path.lineTo(dstPs[2], dstPs[3]); + path.lineTo(dstPs[4], dstPs[5]); + path.lineTo(dstPs[6], dstPs[7]); + path.lineTo(dstPs[0], dstPs[1]); + for (int i = 0; i < 7; i += 2) { + if (i == 6) { + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[0], dstPs[1], framePaint); + break; + } + canvas.drawLine(dstPs[i], dstPs[i + 1], dstPs[i + 2], dstPs[i + 3], framePaint); + } + + + canvas.drawBitmap(btDelete, dstPs[2] - btSize / 2, dstPs[3] - btSize / 2, bitmapPaint); +// canvas.drawBitmap(btEditor, dstPs[0] - btSize / 2, dstPs[1] - btSize / 2, bitmapPaint); + canvas.drawBitmap(btController, dstPs[4] - btSize / 2, dstPs[5] - btSize / 2, bitmapPaint); +// canvas.drawBitmap(btRotate, dstPs[6] - btSize / 2, dstPs[7] - btSize / 2, bitmapPaint); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + gestureDetector.onTouchEvent(event); + if (MotionEvent.ACTION_UP == event.getAction() || MotionEvent.ACTION_POINTER_UP == event.getAction() || MotionEvent.ACTION_POINTER_1_UP == event.getAction() || MotionEvent.ACTION_POINTER_2_UP == event.getAction()) { + setDoubleDownPoints(0, 0, 0, 0); + lastDoubleDegress = 1000; + lastDegree = computeDegree(new Point((int) dstPs[4], (int) dstPs[5]), new Point((int) dstPs[8], (int) dstPs[9])); + } + + return !isOut; + } + + private void setDoubleDownPoints(float x1, float y1, float x2, float y2) { + downX1 = x1; + downY1 = y1; + downX2 = x2; + downY2 = y2; + } + + private void calculateClickType(int x, int y) { + RectF rectF = new RectF(x - btSize / 2 - 40, y - btSize / 2 - 40, x + btSize / 2 + 40, y + btSize / 2 + 40); + + Rect rect = new Rect(); + + + if (rectF.contains(dstPs[2] - 20, dstPs[3])) { + clickType = ClickType.DELETE; + } +// else if (rectF.contains(dstPs[0], dstPs[1])) { +// clickType = ClickType.EDITOR; +// } + else if (rectF.contains(dstPs[4] + 20, dstPs[5])) { + clickType = ClickType.SCALE; + } +// else if (rectF.contains(dstPs[6] - 20, dstPs[7])) { +// clickType = ClickType.IMAGE; +// } + else { + RectF bounds = new RectF(); + path.computeBounds(bounds, true); + Region region = new Region(); + region.setPath(path, new Region((int) bounds.left, (int) bounds.top, (int) bounds.right, (int) bounds.bottom)); + + if (region.contains(x, y)) { + if (isOut) { + isOut = false; + } + if (!isUsing) { + isUsing = true; + listener.onUsing(); + postInvalidate(); + } + clickType = ClickType.IMAGE; + } else { + if (isUsing) { + isUsing = false; + postInvalidate(); + } + if (!isOut) { + isOut = true; + } + clickType = ClickType.OUT; + } + } + } + + public void delete() { + if (null == listener) { + throw new NullPointerException("OnStickerClickListener listener is null"); + } + setVisibility(GONE); + EasyPhotos.recycle(bitmap); + listener.onDelete(); + } + + private void editor() { + listener.onEditor(); + } + + private void top() { + bringToFront();//置顶 + invalidate(); + listener.onTop(); + } + + private void move(float distansX, float distansY) { + mMatrix.postTranslate(distansX, distansY); + matrixMap(); + } + + public void moveTo(float x, float y) { + move(x - dstPs[8], y - dstPs[1]); + } + + private void controller(MotionEvent event) { + scale(event); + rotate(event); + } + + private void scale(MotionEvent event) { + float originalX1; + float originalY1; + float originalX2; + float originalY2; + float moveX1; + float moveY1; + float moveX2; + float moveY2; + if (event.getPointerCount() == 2) { + originalX2 = downX2; + originalY2 = downY2; + originalX1 = downX1; + originalY1 = downY1; + + moveX2 = event.getX(1); + moveY2 = event.getY(1); + moveX1 = event.getX(0); + moveY1 = event.getY(0); + + } else { + originalX2 = dstPs[4]; + originalY2 = dstPs[5]; + originalX1 = dstPs[0]; + originalY1 = dstPs[1]; + + moveX2 = event.getX(); + moveY2 = event.getY(); + moveX1 = originalX1; + moveY1 = originalY1; + } + + float temp1 = getDistanceOfTwoPoints(originalX2, originalY2, originalX1, originalY1); + float temp2 = getDistanceOfTwoPoints(moveX2, moveY2, moveX1, moveY1); + + float scalValue = temp2 / temp1; + + if (getScaleValue() < minScale && scalValue < (float) 1) { + return; + } + mMatrix.postScale(scalValue, scalValue, dstPs[8], dstPs[9]); + matrixMap(); + if (event.getPointerCount() == 2) { + setDoubleDownPoints(moveX1, moveY1, moveX2, moveY2); + } + } + + private void rotate(MotionEvent event) { + if (event.getPointerCount() == 2) { + float preDegree = computeDegree(new Point((int) event.getX(0), (int) event.getY(0)), new Point((int) event.getX(1), (int) event.getY(1))); + if (lastDoubleDegress == 1000) { + lastDoubleDegress = preDegree; + } + mMatrix.postRotate(preDegree - lastDoubleDegress, dstPs[8], dstPs[9]); + matrixMap(); + lastDoubleDegress = preDegree; + + } else { + float preDegree = computeDegree(new Point((int) event.getX(), (int) event.getY()), new Point((int) dstPs[8], (int) dstPs[9])); + mMatrix.postRotate(preDegree - lastDegree, dstPs[8], dstPs[9]); + matrixMap(); + lastDegree = preDegree; + } + } + + // 获取饰品缩放比例(与原图相比) + public float getScaleValue() { + float preDistance = (srcPs[8] - srcPs[0]) * (srcPs[8] - srcPs[0]) + (srcPs[9] - srcPs[1]) * (srcPs[9] - srcPs[1]); + float lastDistance = (dstPs[8] - dstPs[0]) * (dstPs[8] - dstPs[0]) + (dstPs[9] - dstPs[1]) * (dstPs[9] - dstPs[1]); + float scaleValue = (float) Math.sqrt(lastDistance / preDistance); + return scaleValue; + } + + private void matrixMap() { + mMatrix.mapPoints(dstPs, srcPs); + postInvalidate(); + } + + public void setUsing(boolean isUsing) { + this.isUsing = isUsing; + postInvalidate(); + } + + private float getDistanceOfTwoPoints(float x1, float y1, float x2, float y2) { + return (float) (Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))); + } + + public float computeDegree(Point p1, Point p2) { + float tran_x = p1.x - p2.x; + float tran_y = p1.y - p2.y; + float degree = 0.0f; + float angle = (float) (Math.asin(tran_x / Math.sqrt(tran_x * tran_x + tran_y * tran_y)) * 180 / Math.PI); + if (!Float.isNaN(angle)) { + if (tran_x >= 0 && tran_y <= 0) {//第一象限 + degree = angle; + } else if (tran_x <= 0 && tran_y <= 0) {//第二象限 + degree = angle; + } else if (tran_x <= 0 && tran_y >= 0) {//第三象限 + degree = -180 - angle; + } else if (tran_x >= 0 && tran_y >= 0) {//第四象限 + degree = 180 - angle; + } + } + return degree; + } + + private enum ClickType { + DELETE, EDITOR, SCALE, ROTATE, IMAGE, OUT + } + + private class StickerGestureListener extends GestureDetector.SimpleOnGestureListener { + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + + switch (clickType) { + case DELETE: + delete(); + break; + case EDITOR: +// editor(); + break; + case SCALE: + break; + case ROTATE: + break; + case IMAGE: +// editor(); + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + switch (clickType) { + case DELETE: + break; + case EDITOR: + break; + case SCALE: + break; + case ROTATE: + break; + case IMAGE: + editor(); + break; + case OUT: + break; + } + return super.onDoubleTap(e); + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + switch (clickType) { + case DELETE: + break; + case EDITOR: + break; + case SCALE: + if (e2.getPointerCount() > 1) break; + controller(e2); + break; + case ROTATE: +// rotate(e2); + break; + case IMAGE: + if (e2.getPointerCount() == 2) { + if (downX1 + downY1 + downX2 + downY2 == 0) { + setDoubleDownPoints(e2.getX(0), e2.getY(0), e2.getX(1), e2.getY(1)); + } + controller(e2); + } else if (e2.getPointerCount() == 1) { + move(-distanceX, -distanceY); + } + break; + case OUT: + break; + } + return true; + } + + @Override + public boolean onDown(MotionEvent e) { + isChecked = true; + calculateClickType((int) e.getX(), (int) e.getY()); + if (clickType == ClickType.IMAGE) { + top(); + } + return true; + } + + } + + public void resetText(String text) { + this.text = text; + resetSize(); + resetPoints(); + resetBitmap(); + matrixMap(); + } + + private void resetPoints() { + srcPs = new float[]{0, 0, textWidth, 0, textWidth, textHeight, 0, textHeight, textWidth / 2, textHeight / 2}; + } + + public String getText() { + return this.text; + } + + public void setTextColor(int color) { + textPaint.setColor(color); + resetSize(); + resetPoints(); + resetBitmap(); + matrixMap(); + } + + public int getTextColor() { + return textPaint.getColor(); + } + + public void setTextAlpha(int alpha) { + textPaint.setAlpha(alpha); + resetSize(); + resetPoints(); + resetBitmap(); + matrixMap(); + } + + public int getTextAlpha() { + return textPaint.getAlpha(); + } + + public boolean isUsing() { + return this.isUsing; + } + + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/result/Result.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/result/Result.java new file mode 100644 index 0000000..b58add9 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/result/Result.java @@ -0,0 +1,144 @@ +package com.huantansheng.easyphotos.result; + +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; + +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; + +import java.util.ArrayList; + +/** + * 存储的返回图片集 + * Created by huan on 2017/10/24. + */ + +public class Result { + public static ArrayList photos = new ArrayList<>(); + public static final int ADD_SUCCESS = 0; + public static final int PICTURE_OUT = -1; + public static final int VIDEO_OUT = -2; + public static final int SINGLE_TYPE = -3; + + /** + * @return 0:添加成功 -2:超过视频选择数 -1:超过图片选择数 + */ + public static int addPhoto(Photo photo) { + if (photos.isEmpty()) { + photo.selected = true; + photos.add(photo); + return ADD_SUCCESS; + } + if (Setting.complexSelector) { + if (Setting.complexSingleType) { + if (photos.get(0).type.contains(Type.VIDEO)) { + if (!photo.type.contains(Type.VIDEO)) { + return SINGLE_TYPE; + } + } + if (!photos.get(0).type.contains(Type.VIDEO)) { + if (photo.type.contains(Type.VIDEO)) { + return SINGLE_TYPE; + } + } + + } + int number = getVideoNumber(); + if (photo.type.contains(Type.VIDEO) && number >= Setting.complexVideoCount) { + return VIDEO_OUT; + } + number = photos.size() - number; + if ((!photo.type.contains(Type.VIDEO)) && number >= Setting.complexPictureCount) { + return PICTURE_OUT; + } + } + photo.selected = true; + photos.add(photo); + return ADD_SUCCESS; + } + + public static void removePhoto(Photo photo) { + photo.selected = false; + photos.remove(photo); + } + + public static void removePhoto(int photoIndex) { + removePhoto(photos.get(photoIndex)); + } + + public static void removeAll() { + int size = photos.size(); + for (int i = 0; i < size; i++) { + removePhoto(0); + } + } + + private static int getVideoNumber() { + int count = 0; + for (Photo p : photos) { + if (p.type.contains(Type.VIDEO)) { + count += 1; + } + } + return count; + } + + public static void processOriginal() { + boolean isIceApi = Build.VERSION.SDK_INT == Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1; + if (Setting.showOriginalMenu) { + if (Setting.originalMenuUsable) { + for (Photo photo : photos) { + photo.selectedOriginal = Setting.selectedOriginal; + if (isIceApi && photo.width == 0) { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(photo.path, options); + photo.width = options.outWidth; + photo.height = options.outHeight; + } + } + } + } + } + + public static void clear() { + photos.clear(); + } + + public static boolean isEmpty() { + return photos.isEmpty(); + } + + public static int count() { + return photos.size(); + } + + /** + * 获取选择器应该显示的数字 + * + * @param photo 当前图片 + * @return 选择器应该显示的数字 + */ + public static String getSelectorNumber(Photo photo) { + return String.valueOf(photos.indexOf(photo) + 1); + } + + public static String getPhotoPath(int position) { + return photos.get(position).path; + } + + public static Uri getPhotoUri(int position) { + return photos.get(position).uri; + } + + public static String getPhotoType(int position) { + return photos.get(position).type; + } + + public static long getPhotoDuration(int position) { + return photos.get(position).duration; + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java new file mode 100644 index 0000000..69b9539 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/setting/Setting.java @@ -0,0 +1,123 @@ +package com.huantansheng.easyphotos.setting; + +import androidx.annotation.IntDef; +import android.view.View; + +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.album.entity.Photo; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * EasyPhotos的设置值 + * Created by huan on 2017/10/24. + */ + +public class Setting { + public static int minWidth = 1; + public static int minHeight = 1; + public static long minSize = 1; + public static int count = 1; + + public static WeakReference photosAdView = null; + public static WeakReference albumItemsAdView = null; + public static boolean photoAdIsOk = false; + public static boolean albumItemsAdIsOk = false; + public static boolean useWidth = false; + public static ArrayList selectedPhotos = new ArrayList<>(); + public static boolean showOriginalMenu = false; + public static boolean originalMenuUsable = false; + public static String originalMenuUnusableHint = ""; + public static boolean selectedOriginal = false; + public static String fileProviderAuthority = null; + public static boolean isShowCamera = false; + public static int cameraLocation = 1; + public static boolean onlyStartCamera = false; + public static boolean showPuzzleMenu = true; + public static List filterTypes = new ArrayList<>(); + public static boolean showGif = false; + public static boolean showVideo = false; + public static boolean showCleanMenu = true; + public static long videoMinSecond = 0L; + public static long videoMaxSecond = Long.MAX_VALUE; + public static ImageEngine imageEngine = null; + + public static final int LIST_FIRST = 0; + public static final int BOTTOM_RIGHT = 1; + + public static boolean complexSelector = false; + public static boolean complexSingleType = false; + public static int complexVideoCount = 0; + public static int complexPictureCount = 0; + //当传入已选中图片时,是否按照之前选中的顺序排序 + public static boolean isSequentialSelectedPhotos = true; + + @Retention(RetentionPolicy.SOURCE) + @IntDef(value = {LIST_FIRST, BOTTOM_RIGHT}) + public @interface Location { + + } + + public static void clear() { + minWidth = 1; + minHeight = 1; + minSize = 1; + count = 1; + + photosAdView = null; + albumItemsAdView = null; + photoAdIsOk = false; + albumItemsAdIsOk = false; + selectedPhotos.clear(); + showOriginalMenu = false; + originalMenuUsable = false; + originalMenuUnusableHint = ""; + selectedOriginal = false; + cameraLocation = BOTTOM_RIGHT; + isShowCamera = false; + onlyStartCamera = false; + showPuzzleMenu = true; + filterTypes = new ArrayList<>(); + showGif = false; + showVideo = false; + showCleanMenu = true; + videoMinSecond = 0L; + videoMaxSecond = Long.MAX_VALUE; + complexSelector = false; + complexSingleType = false; + complexVideoCount = 0; + complexPictureCount = 0; + isSequentialSelectedPhotos = true; + } + + public static boolean isFilter(String type) { + type = type.toLowerCase(); + for (String filterType : Setting.filterTypes) { + if (type.contains(filterType)) { + return true; + } + } + return false; + } + + public static boolean isOnlyVideo() { + return filterTypes.size() == 1 && filterTypes.get(0).equals(Type.VIDEO); + } + + public static boolean hasPhotosAd() { + return photosAdView != null && photosAdView.get() != null; + } + + public static boolean hasAlbumItemsAd() { + return albumItemsAdView != null && albumItemsAdView.get() != null; + } + + public static boolean isBottomRightCamera() { + return cameraLocation == BOTTOM_RIGHT; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java new file mode 100644 index 0000000..dafea95 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/EasyPhotosActivity.java @@ -0,0 +1,1187 @@ +package com.huantansheng.easyphotos.ui; + +import android.Manifest; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Fragment; +import android.content.ContentValues; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import android.hardware.Camera; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.provider.MediaStore; +import android.text.TextUtils; +import android.util.Log; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.ScaleAnimation; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.core.content.PermissionChecker; +import androidx.exifinterface.media.ExifInterface; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.constant.Key; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.ad.AdListener; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.AlbumItemsAdapter; +import com.huantansheng.easyphotos.ui.adapter.PhotosAdapter; +import com.huantansheng.easyphotos.ui.dialog.LoadingDialog; +import com.huantansheng.easyphotos.ui.widget.PressedTextView; +import com.huantansheng.easyphotos.utils.Color.ColorUtils; +import com.huantansheng.easyphotos.utils.String.StringUtils; +import com.huantansheng.easyphotos.utils.bitmap.BitmapUtils; +import com.huantansheng.easyphotos.utils.media.DurationUtils; +import com.huantansheng.easyphotos.utils.media.MediaScannerConnectionUtils; +import com.huantansheng.easyphotos.utils.permission.PermissionUtil; +import com.huantansheng.easyphotos.utils.settings.SettingsUtils; +import com.huantansheng.easyphotos.utils.system.SystemUtils; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.Locale; +import java.util.logging.Logger; + +public class EasyPhotosActivity extends AppCompatActivity implements AlbumItemsAdapter.OnClickListener, PhotosAdapter.OnClickListener, AdListener, View.OnClickListener { + + private File mTempImageFile; + + private AlbumModel albumModel; + private ArrayList photoList = new ArrayList<>(); + private ArrayList albumItemList = new ArrayList<>(); + + private ArrayList resultList = new ArrayList<>(); + + private RecyclerView rvPhotos; + private PhotosAdapter photosAdapter; + private GridLayoutManager gridLayoutManager; + + private RecyclerView rvAlbumItems; + private AlbumItemsAdapter albumItemsAdapter; + private RelativeLayout rootViewAlbumItems; + + private PressedTextView tvAlbumItems, tvDone, tvPreview; + private TextView tvOriginal; + private AnimatorSet setHide; + private AnimatorSet setShow; + + private int currAlbumItemIndex = 0; + + private ImageView ivCamera; + private TextView tvTitle; + + private LinearLayout mSecondMenus; + + private RelativeLayout permissionView; + private TextView tvPermission; + private View mBottomBar; + + private boolean isQ = false; + + public static long startTime = 0; + + public static boolean doubleClick() { + long now = System.currentTimeMillis(); + if (now - startTime < 600) { + return true; + } + startTime = now; + return false; + } + + public static void start(Activity activity, int requestCode) { + if (doubleClick()) + return; + Intent intent = new Intent(activity, EasyPhotosActivity.class); + activity.startActivityForResult(intent, requestCode); + } + + public static void start(Fragment fragment, int requestCode) { + if (doubleClick()) + return; + Intent intent = new Intent(fragment.getActivity(), EasyPhotosActivity.class); + fragment.startActivityForResult(intent, requestCode); + } + + public static void start(androidx.fragment.app.Fragment fragment, int requestCode) { + if (doubleClick()) + return; + Intent intent = new Intent(fragment.getContext(), EasyPhotosActivity.class); + fragment.startActivityForResult(intent, requestCode); + } + + LoadingDialog loadingDialog; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_easy_photos); + hideActionBar(); + adaptationStatusBar(); + loadingDialog = LoadingDialog.get(this); + isQ = Build.VERSION.SDK_INT == Build.VERSION_CODES.Q; + if (!Setting.onlyStartCamera && null == Setting.imageEngine) { + finish(); + return; + } + initSomeViews(); + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + hasPermissions(); + } else { + permissionView.setVisibility(View.VISIBLE); + } + } + + private void adaptationStatusBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int statusColor = getWindow().getStatusBarColor(); + if (statusColor == Color.TRANSPARENT) { + statusColor = ContextCompat.getColor(this, R.color.colorPrimaryDark); + } + if (ColorUtils.isWhiteColor(statusColor)) { + SystemUtils.getInstance().setStatusDark(this, true); + } + } + } + + private void initSomeViews() { + mBottomBar = findViewById(R.id.m_bottom_bar); + permissionView = findViewById(R.id.rl_permissions_view); + tvPermission = findViewById(R.id.tv_permission); + rootViewAlbumItems = findViewById(R.id.root_view_album_items); + tvTitle = findViewById(R.id.tv_title); + if (Setting.isOnlyVideo()) { + tvTitle.setText(R.string.video_selection_easy_photos); + } + findViewById(R.id.iv_second_menu).setVisibility(Setting.showPuzzleMenu || Setting.showCleanMenu || Setting.showOriginalMenu ? View.VISIBLE : View.GONE); + setClick(R.id.iv_back); + } + + private void hasPermissions() { + permissionView.setVisibility(View.GONE); + if (Setting.onlyStartCamera) { + launchCamera(Code.REQUEST_CAMERA); + return; + } + AlbumModel.CallBack albumModelCallBack = new AlbumModel.CallBack() { + @Override + public void onAlbumWorkedCallBack() { + runOnUiThread(new Runnable() { + @Override + public void run() { + loadingDialog.dismiss(); + onAlbumWorkedDo(); + } + }); + } + }; + loadingDialog.show(); + albumModel = AlbumModel.getInstance(); + albumModel.query(this, albumModelCallBack); + } + + protected String[] getNeedPermissions() { + /*******************************官方库的源码start*******************************/ + // if (Setting.isShowCamera) { + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + // return new String[]{Manifest.permission.CAMERA, + // Manifest.permission.WRITE_EXTERNAL_STORAGE, + // Manifest.permission.READ_EXTERNAL_STORAGE}; + // } + // return new String[]{Manifest.permission.CAMERA, + // Manifest.permission.WRITE_EXTERNAL_STORAGE}; + // } else { + // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + // return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + // Manifest.permission.READ_EXTERNAL_STORAGE}; + // } + // return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + // } + /*******************************官方库的源码end*******************************/ + + + /*********************************自定义添加适配Android13相册权限start*************************/ + if (Setting.isShowCamera) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new String[]{Manifest.permission.CAMERA, + Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_VIDEO}; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return new String[]{Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + } + return new String[]{Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return new String[]{Manifest.permission.READ_MEDIA_IMAGES, + Manifest.permission.READ_MEDIA_AUDIO, Manifest.permission.READ_MEDIA_VIDEO}; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + } + return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } + /*********************************自定义添加适配Android13相册权限end*************************/ + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull final String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + + PermissionUtil.onPermissionResult(this, permissions, grantResults, + new PermissionUtil.PermissionCallBack() { + @Override + public void onSuccess() { + hasPermissions(); + } + + @Override + public void onShouldShow() { + tvPermission.setText(R.string.permissions_again_easy_photos); + permissionView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(EasyPhotosActivity.this, getNeedPermissions())) { + hasPermissions(); + } + } + }); + + } + + @Override + public void onFailed() { + tvPermission.setText(R.string.permissions_die_easy_photos); + permissionView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SettingsUtils.startMyApplicationDetailsForResult(EasyPhotosActivity.this, + getPackageName()); + } + }); + + } + }); + } + + + /** + * 启动相机 + * + * @param requestCode startActivityForResult的请求码 + */ + private void launchCamera(int requestCode) { + if (TextUtils.isEmpty(Setting.fileProviderAuthority)) + throw new RuntimeException("AlbumBuilder" + " : 请执行 setFileProviderAuthority()方法"); + if (!cameraIsCanUse()) { + permissionView.setVisibility(View.VISIBLE); + tvPermission.setText(R.string.permissions_die_easy_photos); + permissionView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + SettingsUtils.startMyApplicationDetailsForResult(EasyPhotosActivity.this, + getPackageName()); + } + }); + return; + } + toAndroidCamera(requestCode); + } + + /** + * 启动系统相机 + * + * @param requestCode 请求相机的请求码 + */ + private Uri photoUri = null; + + private void toAndroidCamera(int requestCode) { + Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + + if (cameraIntent.resolveActivity(getPackageManager()) != null || this.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY)) { + + if (isQ) { + photoUri = createImageUri(); + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); + cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + startActivityForResult(cameraIntent, requestCode); + return; + } + + createCameraTempImageFile(); + if (mTempImageFile != null && mTempImageFile.isFile()) { + + Uri imageUri = UriUtils.getUri(this, mTempImageFile); + + cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //对目标应用临时授权该Uri所代表的文件 + cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); //对目标应用临时授权该Uri所代表的文件 + + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);//将拍取的照片保存到指定URI + startActivityForResult(cameraIntent, requestCode); + } else { + Toast.makeText(getApplicationContext(), + R.string.camera_temp_file_error_easy_photos, Toast.LENGTH_SHORT).show(); + } + } else { + Toast.makeText(getApplicationContext(), R.string.msg_no_camera_easy_photos, + Toast.LENGTH_SHORT).show(); + } + } + + + /** + * 创建图片地址uri,用于保存拍照后的照片 Android 10以后使用这种方法 + */ + private Uri createImageUri() { + //设置保存参数到ContentValues中 + ContentValues contentValues = new ContentValues(); + //设置文件名 + contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, + String.valueOf(System.currentTimeMillis())); + //兼容Android Q和以下版本 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + //android Q中不再使用DATA字段,而用RELATIVE_PATH代替 + //RELATIVE_PATH是相对路径不是绝对路径;照片存储的地方为:存储/Pictures + contentValues.put(MediaStore.Images.Media.RELATIVE_PATH, "Pictures"); + } + //设置文件类型 + contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/JPEG"); + //执行insert操作,向系统文件夹中添加文件 + //EXTERNAL_CONTENT_URI代表外部存储器,该值不变 + return getContentResolver().insert(MediaStore.Images.Media.getContentUri("external"), + contentValues); + } + + + private void createCameraTempImageFile() { + File dir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + if (null == dir) { + dir = new File(Environment.getExternalStorageDirectory(), + File.separator + "DCIM" + File.separator + "Camera" + File.separator); + } + if (!dir.isDirectory()) { + if (!dir.mkdirs()) { + dir = getExternalFilesDir(null); + if (null == dir || !dir.exists()) { + dir = getFilesDir(); + if (null == dir || !dir.exists()) { + dir = getFilesDir(); + if (null == dir || !dir.exists()) { + String cacheDirPath = File.separator + "data" + File.separator + + "data" + File.separator + getPackageName() + File.separator + "cache" + File.separator; + dir = new File(cacheDirPath); + if (!dir.exists()) { + dir.mkdirs(); + } + } + } + } + } + } + + try { + mTempImageFile = File.createTempFile("IMG", ".jpg", dir); + } catch (IOException e) { + e.printStackTrace(); + mTempImageFile = null; + } + + } + + + @SuppressLint("MissingSuperCall") + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == Code.REQUEST_SETTING_APP_DETAILS) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + hasPermissions(); + } else { + permissionView.setVisibility(View.VISIBLE); + } + return; + } + switch (resultCode) { + case RESULT_OK: + if (Code.REQUEST_CAMERA == requestCode) { + if (isQ) { + onCameraResultForQ(); + return; + } + + if (mTempImageFile == null || !mTempImageFile.isFile()) { + throw new RuntimeException("EasyPhotos拍照保存的图片不存在"); + } + onCameraResult(); + return; + } + + if (Code.REQUEST_PREVIEW_ACTIVITY == requestCode) { + if (data.getBooleanExtra(Key.PREVIEW_CLICK_DONE, false)) { + done(); + return; + } + photosAdapter.change(); + processOriginalMenu(); + shouldShowMenuDone(); + return; + } + + if (Code.REQUEST_PUZZLE_SELECTOR == requestCode) { + Photo puzzlePhoto = data.getParcelableExtra(EasyPhotos.RESULT_PHOTOS); + addNewPhoto(puzzlePhoto); + return; + } + + break; + case RESULT_CANCELED: + if (Code.REQUEST_CAMERA == requestCode) { + // 删除临时文件 + if (mTempImageFile != null && mTempImageFile.exists()) { + mTempImageFile.delete(); + mTempImageFile = null; + } + if (Setting.onlyStartCamera) { + finish(); + } + return; + } + + if (Code.REQUEST_PREVIEW_ACTIVITY == requestCode) { + processOriginalMenu(); + return; + } + break; + default: + break; + } + } + + String folderPath; + String albumName; + + private void addNewPhoto(Photo photo) { + photo.selectedOriginal = Setting.selectedOriginal; + + if (!isQ) { + MediaScannerConnectionUtils.refresh(this, photo.path); + folderPath = new File(photo.path).getParentFile().getAbsolutePath(); + albumName = StringUtils.getLastPathSegment(folderPath); + } + + String albumItem_all_name = albumModel.getAllAlbumName(this); + albumModel.album.getAlbumItem(albumItem_all_name).addImageItem(0, photo); + + albumModel.album.addAlbumItem(albumName, folderPath, photo.path, photo.uri); + albumModel.album.getAlbumItem(albumName).addImageItem(0, photo); + + albumItemList.clear(); + albumItemList.addAll(albumModel.getAlbumItems()); + if (Setting.hasAlbumItemsAd()) { + int albumItemsAdIndex = 2; + if (albumItemList.size() < albumItemsAdIndex + 1) { + albumItemsAdIndex = albumItemList.size() - 1; + } + albumItemList.add(albumItemsAdIndex, Setting.albumItemsAdView); + } + albumItemsAdapter.notifyDataSetChanged(); + + if (Setting.count == 1) { + Result.clear(); + int res = Result.addPhoto(photo); + onSelectorOutOfMax(res); + } else { + if (Result.count() >= Setting.count) { + onSelectorOutOfMax(null); + } else { + int res = Result.addPhoto(photo); + onSelectorOutOfMax(res); + } + } + rvAlbumItems.scrollToPosition(0); + albumItemsAdapter.setSelectedPosition(0); + shouldShowMenuDone(); + } + + private Photo getPhoto(Uri uri) { + Photo p = null; + String path; + String name; + long dateTime; + String type; + long size; + int width = 0; + int height = 0; + int orientation = 0; + String[] projections = AlbumModel.getInstance().getProjections(); + boolean shouldReadWidth = projections.length > 8; + Cursor cursor = getContentResolver().query(uri, projections, null, null, null); + if (cursor == null) { + return null; + } + int albumNameCol = cursor.getColumnIndex(MediaStore.MediaColumns.BUCKET_DISPLAY_NAME); + + if (cursor.moveToFirst()) { + path = cursor.getString(1); + name = cursor.getString(2); + dateTime = cursor.getLong(3); + type = cursor.getString(4); + size = cursor.getLong(5); + if (shouldReadWidth) { + width = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.WIDTH)); + height = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.HEIGHT)); + orientation = + cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns.ORIENTATION)); + if (90 == orientation || 270 == orientation) { + int temp = width; + width = height; + height = temp; + } + } + if (albumNameCol > 0) { + albumName = cursor.getString(albumNameCol); + folderPath = albumName; + } + p = new Photo(name, uri, path, dateTime, width, height, orientation, size, 0, type); + } + cursor.close(); + + return p; + } + + private void onCameraResultForQ() { + loadingDialog.show(); + new Thread(new Runnable() { + @Override + public void run() { + final Photo photo = getPhoto(photoUri); + if (photo == null) { + Log.e("easyPhotos", "onCameraResultForQ() -》photo = null"); + return; + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + loadingDialog.dismiss(); + if (Setting.onlyStartCamera || albumModel.getAlbumItems().isEmpty()) { + Intent data = new Intent(); + photo.selectedOriginal = Setting.selectedOriginal; + resultList.add(photo); + + data.putParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS, resultList); + data.putExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, + Setting.selectedOriginal); + setResult(RESULT_OK, data); + finish(); + return; + } + + addNewPhoto(photo); + } + }); + + } + }).start(); + } + + private void onCameraResult() { + LoadingDialog loading = LoadingDialog.get(this); + new Thread(new Runnable() { + @Override + public void run() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HH_mm_ss", + Locale.getDefault()); + String imageName = "IMG_%s.jpg"; + String filename = String.format(imageName, dateFormat.format(new Date())); + File reNameFile = new File(mTempImageFile.getParentFile(), filename); + if (!reNameFile.exists()) { + if (mTempImageFile.renameTo(reNameFile)) { + mTempImageFile = reNameFile; + } + } + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(mTempImageFile.getAbsolutePath(), options); + MediaScannerConnectionUtils.refresh(EasyPhotosActivity.this, mTempImageFile);// + // 更新媒体库 + + Uri uri = UriUtils.getUri(EasyPhotosActivity.this, mTempImageFile); + int width = 0; + int height = 0; + int orientation = 0; + if (Setting.useWidth) { + width = options.outWidth; + height = options.outHeight; + + ExifInterface exif = null; + try { + exif = new ExifInterface(mTempImageFile); + } catch (IOException e) { + e.printStackTrace(); + } + if (null != exif) { + orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1); + if (orientation == ExifInterface.ORIENTATION_ROTATE_90 || orientation == ExifInterface.ORIENTATION_ROTATE_270) { + width = options.outHeight; + height = options.outWidth; + } + } + } + + final Photo photo = new Photo(mTempImageFile.getName(), uri, + mTempImageFile.getAbsolutePath(), mTempImageFile.lastModified() / 1000, + width, height, orientation, mTempImageFile.length(), + DurationUtils.getDuration(mTempImageFile.getAbsolutePath()), + options.outMimeType); + + runOnUiThread(new Runnable() { + @Override + public void run() { + if (Setting.onlyStartCamera || albumModel.getAlbumItems().isEmpty()) { + Intent data = new Intent(); + + photo.selectedOriginal = Setting.selectedOriginal; + resultList.add(photo); + + data.putParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS, resultList); + + data.putExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, + Setting.selectedOriginal); + + setResult(RESULT_OK, data); + finish(); + return; + } + + addNewPhoto(photo); + } + }); + } + }).start(); + + } + + + private void onAlbumWorkedDo() { + initView(); + } + + private void initView() { + + if (albumModel.getAlbumItems().isEmpty()) { + if (Setting.isOnlyVideo()) { + Toast.makeText(getApplicationContext(), R.string.no_videos_easy_photos, + Toast.LENGTH_LONG).show(); + finish(); + return; + } + Toast.makeText(getApplicationContext(), R.string.no_photos_easy_photos, + Toast.LENGTH_LONG).show(); + if (Setting.isShowCamera) + launchCamera(Code.REQUEST_CAMERA); + else + finish(); + return; + } + + EasyPhotos.setAdListener(this); + if (Setting.hasPhotosAd()) { + findViewById(R.id.m_tool_bar_bottom_line).setVisibility(View.GONE); + } + ivCamera = findViewById(R.id.fab_camera); + if (Setting.isShowCamera && Setting.isBottomRightCamera()) { + ivCamera.setVisibility(View.VISIBLE); + } + if (!Setting.showPuzzleMenu) { + findViewById(R.id.tv_puzzle).setVisibility(View.GONE); + } + mSecondMenus = findViewById(R.id.m_second_level_menu); + int columns = getResources().getInteger(R.integer.photos_columns_easy_photos); + tvAlbumItems = findViewById(R.id.tv_album_items); + tvAlbumItems.setText(albumModel.getAlbumItems().get(0).name); + tvDone = findViewById(R.id.tv_done); + rvPhotos = findViewById(R.id.rv_photos); + ((SimpleItemAnimator) rvPhotos.getItemAnimator()).setSupportsChangeAnimations(false); + //去除item更新的闪光 + photoList.clear(); + photoList.addAll(albumModel.getCurrAlbumItemPhotos(0)); + int index = 0; + if (Setting.hasPhotosAd()) { + photoList.add(index, Setting.photosAdView); + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + if (Setting.hasPhotosAd()) + index = 1; + photoList.add(index, null); + } + photosAdapter = new PhotosAdapter(this, photoList, this); + + gridLayoutManager = new GridLayoutManager(this, columns); + if (Setting.hasPhotosAd()) { + gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { + @Override + public int getSpanSize(int position) { + if (position == 0) { + return gridLayoutManager.getSpanCount();//独占一行 + } else { + return 1;//只占一行中的一列 + } + } + }); + } + rvPhotos.setLayoutManager(gridLayoutManager); + rvPhotos.setAdapter(photosAdapter); + tvOriginal = findViewById(R.id.tv_original); + if (Setting.showOriginalMenu) { + processOriginalMenu(); + } else { + tvOriginal.setVisibility(View.GONE); + } + tvPreview = findViewById(R.id.tv_preview); + + initAlbumItems(); + shouldShowMenuDone(); + setClick(R.id.iv_album_items, R.id.tv_clear, R.id.iv_second_menu, R.id.tv_puzzle); + setClick(tvAlbumItems, rootViewAlbumItems, tvDone, tvOriginal, tvPreview, ivCamera); + + } + + private void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + private void initAlbumItems() { + + rvAlbumItems = findViewById(R.id.rv_album_items); + albumItemList.clear(); + albumItemList.addAll(albumModel.getAlbumItems()); + + if (Setting.hasAlbumItemsAd()) { + int albumItemsAdIndex = 2; + if (albumItemList.size() < albumItemsAdIndex + 1) { + albumItemsAdIndex = albumItemList.size() - 1; + } + albumItemList.add(albumItemsAdIndex, Setting.albumItemsAdView); + } + albumItemsAdapter = new AlbumItemsAdapter(this, albumItemList, 0, this); + rvAlbumItems.setLayoutManager(new LinearLayoutManager(this)); + rvAlbumItems.setAdapter(albumItemsAdapter); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (R.id.tv_album_items == id || R.id.iv_album_items == id) { + showAlbumItems(View.GONE == rootViewAlbumItems.getVisibility()); + } else if (R.id.root_view_album_items == id) { + showAlbumItems(false); + } else if (R.id.iv_back == id) { + onBackPressed(); + } else if (R.id.tv_done == id) { + done(); + } else if (R.id.tv_clear == id) { + if (Result.isEmpty()) { + processSecondMenu(); + return; + } + Result.removeAll(); + photosAdapter.change(); + shouldShowMenuDone(); + processSecondMenu(); + } else if (R.id.tv_original == id) { + if (!Setting.originalMenuUsable) { + Toast.makeText(getApplicationContext(), Setting.originalMenuUnusableHint, + Toast.LENGTH_SHORT).show(); + return; + } + Setting.selectedOriginal = !Setting.selectedOriginal; + processOriginalMenu(); + processSecondMenu(); + } else if (R.id.tv_preview == id) { + PreviewActivity.start(EasyPhotosActivity.this, -1, 0); + } else if (R.id.fab_camera == id) { + launchCamera(Code.REQUEST_CAMERA); + } else if (R.id.iv_second_menu == id) { + processSecondMenu(); + } else if (R.id.tv_puzzle == id) { + processSecondMenu(); + PuzzleSelectorActivity.start(this); + } + } + + public void processSecondMenu() { + if (mSecondMenus == null) { + return; + } + if (View.VISIBLE == mSecondMenus.getVisibility()) { + mSecondMenus.setVisibility(View.INVISIBLE); + if (Setting.isShowCamera && Setting.isBottomRightCamera()) { + ivCamera.setVisibility(View.VISIBLE); + } + } else { + mSecondMenus.setVisibility(View.VISIBLE); + if (Setting.isShowCamera && Setting.isBottomRightCamera()) { + ivCamera.setVisibility(View.INVISIBLE); + } + } + } + + private boolean clickDone = false; + + private void done() { + if (clickDone) + return; + clickDone = true; + // if (Setting.useWidth) { + // resultUseWidth(); + // return; + // } + resultFast(); + } + + private void resultUseWidth() { + loadingDialog.show(); + new Thread(new Runnable() { + @Override + public void run() { + int size = Result.photos.size(); + try { + for (int i = 0; i < size; i++) { + Photo photo = Result.photos.get(i); + if (photo.width == 0 || photo.height == 0) { + BitmapUtils.calculateLocalImageSizeThroughBitmapOptions(photo); + } + if (BitmapUtils.needChangeWidthAndHeight(photo)) { + int h = photo.width; + photo.width = photo.height; + photo.height = h; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + runOnUiThread(() -> { + loadingDialog.dismiss(); + resultFast(); + }); + } + }).start(); + } + + private void resultFast() { + Intent intent = new Intent(); + Result.processOriginal(); + resultList.addAll(Result.photos); + intent.putParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS, resultList); + intent.putExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, Setting.selectedOriginal); + setResult(RESULT_OK, intent); + finish(); + } + + private void processOriginalMenu() { + if (!Setting.showOriginalMenu) + return; + if (Setting.selectedOriginal) { + tvOriginal.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + } else { + if (Setting.originalMenuUsable) { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary)); + } else { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary_dark)); + } + } + } + + private void showAlbumItems(boolean isShow) { + if (null == setShow) { + newAnimators(); + } + if (isShow) { + rootViewAlbumItems.setVisibility(View.VISIBLE); + setShow.start(); + } else { + setHide.start(); + } + + } + + private void newAnimators() { + newHideAnim(); + newShowAnim(); + } + + private void newShowAnim() { + ObjectAnimator translationShow = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", + mBottomBar.getTop(), 0); + ObjectAnimator alphaShow = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 0.0f, 1.0f); + translationShow.setDuration(300); + setShow = new AnimatorSet(); + setShow.setInterpolator(new AccelerateDecelerateInterpolator()); + setShow.play(translationShow).with(alphaShow); + } + + private void newHideAnim() { + ObjectAnimator translationHide = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", 0, + mBottomBar.getTop()); + ObjectAnimator alphaHide = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 1.0f, 0.0f); + translationHide.setDuration(200); + setHide = new AnimatorSet(); + setHide.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + rootViewAlbumItems.setVisibility(View.GONE); + } + }); + setHide.setInterpolator(new AccelerateInterpolator()); + setHide.play(translationHide).with(alphaHide); + } + + @Override + public void onAlbumItemClick(int position, int realPosition) { + updatePhotos(realPosition); + showAlbumItems(false); + tvAlbumItems.setText(albumModel.getAlbumItems().get(realPosition).name); + } + + private void updatePhotos(int currAlbumItemIndex) { + this.currAlbumItemIndex = currAlbumItemIndex; + photoList.clear(); + photoList.addAll(albumModel.getCurrAlbumItemPhotos(currAlbumItemIndex)); + int index = 0; + if (Setting.hasPhotosAd()) { + photoList.add(index, Setting.photosAdView); + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + if (Setting.hasPhotosAd()) + index = 1; + photoList.add(index, null); + } + photosAdapter.change(); + rvPhotos.scrollToPosition(0); + } + + private void shouldShowMenuDone() { + if (Result.isEmpty()) { + if (View.VISIBLE == tvDone.getVisibility()) { + ScaleAnimation scaleHide = new ScaleAnimation(1f, 0f, 1f, 0f); + scaleHide.setDuration(200); + tvDone.startAnimation(scaleHide); + } + tvDone.setVisibility(View.INVISIBLE); + tvPreview.setVisibility(View.INVISIBLE); + } else { + if (View.INVISIBLE == tvDone.getVisibility()) { + ScaleAnimation scaleShow = new ScaleAnimation(0f, 1f, 0f, 1f); + scaleShow.setDuration(200); + tvDone.startAnimation(scaleShow); + } + tvDone.setVisibility(View.VISIBLE); + tvPreview.setVisibility(View.VISIBLE); + } + + if (Result.isEmpty()) { + return; + } + + if (Setting.complexSelector) { + if (Setting.complexSingleType) { + if (Result.getPhotoType(0).contains(Type.VIDEO)) { + tvDone.setText(getString(R.string.selector_action_done_easy_photos, + Result.count(), Setting.complexVideoCount)); + return; + } + tvDone.setText(getString(R.string.selector_action_done_easy_photos, + Result.count(), Setting.complexPictureCount)); + return; + } + } + + tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(), + Setting.count)); + + } + + @Override + public void onCameraClick() { + launchCamera(Code.REQUEST_CAMERA); + } + + @Override + public void onPhotoClick(int position, int realPosition) { + PreviewActivity.start(EasyPhotosActivity.this, currAlbumItemIndex, realPosition); + } + + @Override + public void onSelectorOutOfMax(@Nullable Integer result) { + if (result == null) { + if (Setting.isOnlyVideo()) { + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_video_hint_easy_photos, + Setting.count), Toast.LENGTH_SHORT).show(); + + } else if (Setting.showVideo) { + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_hint_easy_photos), + Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.count), Toast.LENGTH_SHORT).show(); + } + return; + } + switch (result) { + case Result.PICTURE_OUT: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.complexPictureCount), Toast.LENGTH_SHORT).show(); + break; + case Result.VIDEO_OUT: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_video_hint_easy_photos, + Setting.complexVideoCount), Toast.LENGTH_SHORT).show(); + break; + case Result.SINGLE_TYPE: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_single_type_hint_easy_photos), + Toast.LENGTH_SHORT).show(); + break; + + } + } + + @Override + public void onSelectorChanged() { + shouldShowMenuDone(); + } + + + @Override + public void onBackPressed() { + + if (null != rootViewAlbumItems && rootViewAlbumItems.getVisibility() == View.VISIBLE) { + showAlbumItems(false); + return; + } + + if (null != mSecondMenus && View.VISIBLE == mSecondMenus.getVisibility()) { + processSecondMenu(); + return; + } + if (albumModel != null) + albumModel.stopQuery(); + if (Setting.hasPhotosAd()) { + photosAdapter.clearAd(); + } + if (Setting.hasAlbumItemsAd()) { + albumItemsAdapter.clearAd(); + } + setResult(RESULT_CANCELED); + finish(); + } + + @Override + protected void onDestroy() { + if (albumModel != null) + albumModel.stopQuery(); + super.onDestroy(); + } + + @Override + public void onPhotosAdLoaded() { + runOnUiThread(() -> photosAdapter.change()); + } + + @Override + public void onAlbumItemsAdLoaded() { + runOnUiThread(() -> albumItemsAdapter.notifyDataSetChanged()); + } + + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + private void setClick(View... views) { + for (View v : views) { + v.setOnClickListener(this); + } + } + + /** + * 返回true 表示可以使用 返回false表示不可以使用 + */ + public boolean cameraIsCanUse() { + boolean isCanUse = true; + Camera mCamera = null; + try { + mCamera = Camera.open(); + Camera.Parameters mParameters = mCamera.getParameters(); //针对魅族手机 + mCamera.setParameters(mParameters); + } catch (Exception e) { + isCanUse = false; + } + + if (mCamera != null) { + try { + mCamera.release(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return isCanUse; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java new file mode 100644 index 0000000..ee6ebfa --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewActivity.java @@ -0,0 +1,503 @@ +package com.huantansheng.easyphotos.ui; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.text.TextUtils; +import android.view.View; +import android.view.WindowManager; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.ScaleAnimation; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.PagerSnapHelper; +import androidx.recyclerview.widget.RecyclerView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.constant.Key; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.PreviewPhotosAdapter; +import com.huantansheng.easyphotos.ui.widget.PressedTextView; +import com.huantansheng.easyphotos.utils.Color.ColorUtils; +import com.huantansheng.easyphotos.utils.system.SystemUtils; + +import java.util.ArrayList; + +/** + * 预览页 + */ +public class PreviewActivity extends AppCompatActivity implements PreviewPhotosAdapter.OnClickListener, View.OnClickListener, PreviewFragment.OnPreviewFragmentClickListener { + + public static void start(Activity act, int albumItemIndex, int currIndex) { + Intent intent = new Intent(act, PreviewActivity.class); + intent.putExtra(Key.PREVIEW_ALBUM_ITEM_INDEX, albumItemIndex); + intent.putExtra(Key.PREVIEW_PHOTO_INDEX, currIndex); + act.startActivityForResult(intent, Code.REQUEST_PREVIEW_ACTIVITY); + } + + + /** + * 一些旧设备在UI小部件更新之间需要一个小延迟 + * and a change of the status and navigation bar. + */ + private static final int UI_ANIMATION_DELAY = 300; + private final Handler mHideHandler = new Handler(); + private final Runnable mHidePart2Runnable = new Runnable() { + @Override + public void run() { + SystemUtils.getInstance().systemUiHide(PreviewActivity.this, decorView); + } + }; + private RelativeLayout mBottomBar; + private FrameLayout mToolBar; + private final Runnable mShowPart2Runnable = new Runnable() { + @Override + public void run() { + // 延迟显示UI元素 + mBottomBar.setVisibility(View.VISIBLE); + mToolBar.setVisibility(View.VISIBLE); + } + }; + private boolean mVisible; + View decorView; + private TextView tvOriginal, tvNumber; + private PressedTextView tvDone; + private ImageView ivSelector; + private RecyclerView rvPhotos; + private PreviewPhotosAdapter adapter; + private PagerSnapHelper snapHelper; + private LinearLayoutManager lm; + private int index; + private ArrayList photos = new ArrayList<>(); + private int resultCode = RESULT_CANCELED; + private int lastPosition = 0;//记录recyclerView最后一次角标位置,用于判断是否转换了item + private boolean isSingle = Setting.count == 1; + private boolean unable = Result.count() == Setting.count; + + private FrameLayout flFragment; + private PreviewFragment previewFragment; + private int statusColor; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + decorView = getWindow().getDecorView(); + SystemUtils.getInstance().systemUiInit(this, decorView); + + setContentView(R.layout.activity_preview_easy_photos); + + hideActionBar(); + adaptationStatusBar(); + if (null == AlbumModel.instance) { + finish(); + return; + } + initData(); + initView(); + } + + private void adaptationStatusBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + statusColor = ContextCompat.getColor(this, R.color.easy_photos_status_bar); + if (ColorUtils.isWhiteColor(statusColor)) { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + } + } + } + + private void hideActionBar() { + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + } + + + private void initData() { + Intent intent = getIntent(); + int albumItemIndex = intent.getIntExtra(Key.PREVIEW_ALBUM_ITEM_INDEX, 0); + photos.clear(); + + if (albumItemIndex == -1) { + photos.addAll(Result.photos); + } else { + photos.addAll(AlbumModel.instance.getCurrAlbumItemPhotos(albumItemIndex)); + } + index = intent.getIntExtra(Key.PREVIEW_PHOTO_INDEX, 0); + + lastPosition = index; + mVisible = true; + } + + private void toggle() { + if (mVisible) { + hide(); + } else { + show(); + } + } + + private void hide() { + // Hide UI first + AlphaAnimation hideAnimation = new AlphaAnimation(1.0f, 0.0f); + hideAnimation.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + + } + + @Override + public void onAnimationEnd(Animation animation) { + mBottomBar.setVisibility(View.GONE); + mToolBar.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + + } + }); + hideAnimation.setDuration(UI_ANIMATION_DELAY); + mBottomBar.startAnimation(hideAnimation); + mToolBar.startAnimation(hideAnimation); + mVisible = false; + + // Schedule a runnable to remove the status and navigation bar after a delay + mHideHandler.removeCallbacks(mShowPart2Runnable); + + mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY); + + } + + + private void show() { + // Show the system bar + if (Build.VERSION.SDK_INT >= 16) { + SystemUtils.getInstance().systemUiShow(this, decorView); + } + + mVisible = true; + + // Schedule a runnable to display UI elements after a delay + mHideHandler.removeCallbacks(mHidePart2Runnable); + mHideHandler.post(mShowPart2Runnable); + } + + @Override + public void onPhotoClick() { + toggle(); + } + + @Override + public void onPhotoScaleChanged() { + if (mVisible) hide(); + } + + @Override + public void onBackPressed() { + doBack(); + } + + private void doBack() { + Intent intent = new Intent(); + intent.putExtra(Key.PREVIEW_CLICK_DONE, false); + setResult(resultCode, intent); + finish(); + } + + private void initView() { + setClick(R.id.iv_back, R.id.tv_edit, R.id.tv_selector); + + mToolBar = (FrameLayout) findViewById(R.id.m_top_bar_layout); + if (!SystemUtils.getInstance().hasNavigationBar(this)) { + FrameLayout mRootView = (FrameLayout) findViewById(R.id.m_root_view); + mRootView.setFitsSystemWindows(true); + mToolBar.setPadding(0, SystemUtils.getInstance().getStatusBarHeight(this), 0, 0); + if (ColorUtils.isWhiteColor(statusColor)) { + SystemUtils.getInstance().setStatusDark(this, true); + } + } + mBottomBar = (RelativeLayout) findViewById(R.id.m_bottom_bar); + ivSelector = (ImageView) findViewById(R.id.iv_selector); + tvNumber = (TextView) findViewById(R.id.tv_number); + tvDone = (PressedTextView) findViewById(R.id.tv_done); + tvOriginal = (TextView) findViewById(R.id.tv_original); + flFragment = (FrameLayout) findViewById(R.id.fl_fragment); + previewFragment = + (PreviewFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_preview); + if (Setting.showOriginalMenu) { + processOriginalMenu(); + } else { + tvOriginal.setVisibility(View.GONE); + } + + setClick(tvOriginal, tvDone, ivSelector); + + initRecyclerView(); + shouldShowMenuDone(); + } + + private void initRecyclerView() { + rvPhotos = (RecyclerView) findViewById(R.id.rv_photos); + adapter = new PreviewPhotosAdapter(this, photos, this); + lm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); + rvPhotos.setLayoutManager(lm); + rvPhotos.setAdapter(adapter); + rvPhotos.scrollToPosition(index); + toggleSelector(); + snapHelper = new PagerSnapHelper(); + snapHelper.attachToRecyclerView(rvPhotos); + rvPhotos.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { + super.onScrollStateChanged(recyclerView, newState); + + View view = snapHelper.findSnapView(lm); + if (view == null) { + return; + } + int position = lm.getPosition(view); + if (lastPosition == position) { + return; + } + lastPosition = position; + previewFragment.setSelectedPosition(-1); + tvNumber.setText(getString(R.string.preview_current_number_easy_photos, + lastPosition + 1, photos.size())); + toggleSelector(); + } + }); + tvNumber.setText(getString(R.string.preview_current_number_easy_photos, index + 1, + photos.size())); + } + + private boolean clickDone = false; + + @Override + public void onClick(View v) { + int id = v.getId(); + if (R.id.iv_back == id) { + doBack(); + } else if (R.id.tv_selector == id) { + updateSelector(); + } else if (R.id.iv_selector == id) { + updateSelector(); + } else if (R.id.tv_original == id) { + if (!Setting.originalMenuUsable) { + Toast.makeText(getApplicationContext(), Setting.originalMenuUnusableHint, Toast.LENGTH_SHORT).show(); + return; + } + Setting.selectedOriginal = !Setting.selectedOriginal; + processOriginalMenu(); + } else if (R.id.tv_done == id) { + if (clickDone) return; + clickDone = true; + Intent intent = new Intent(); + intent.putExtra(Key.PREVIEW_CLICK_DONE, true); + setResult(RESULT_OK, intent); + finish(); + } +// else if (R.id.m_bottom_bar == id) { +// +// } else if (R.id.tv_edit == id) { +// +// } + } + + private void processOriginalMenu() { + if (Setting.selectedOriginal) { + tvOriginal.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + } else { + if (Setting.originalMenuUsable) { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary)); + } else { + tvOriginal.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary_dark)); + } + } + } + + private void toggleSelector() { + if (photos.get(lastPosition).selected) { + ivSelector.setImageResource(R.drawable.ic_selector_true_easy_photos); + if (!Result.isEmpty()) { + int count = Result.count(); + for (int i = 0; i < count; i++) { + if (photos.get(lastPosition).path.equals(Result.getPhotoPath(i))) { + previewFragment.setSelectedPosition(i); + break; + } + } + } + } else { + ivSelector.setImageResource(R.drawable.ic_selector_easy_photos); + } + previewFragment.notifyDataSetChanged(); + shouldShowMenuDone(); + } + + private void updateSelector() { + resultCode = RESULT_OK; + Photo item = photos.get(lastPosition); + if (isSingle) { + singleSelector(item); + return; + } + if (unable) { + if (item.selected) { + Result.removePhoto(item); + if (unable) { + unable = false; + } + toggleSelector(); + return; + } + if (Setting.isOnlyVideo()) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_video_hint_easy_photos + , Setting.count), Toast.LENGTH_SHORT).show(); + + } else if (Setting.showVideo) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_hint_easy_photos, + Setting.count), Toast.LENGTH_SHORT).show(); + } else { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.count), Toast.LENGTH_SHORT).show(); + } + return; + } + item.selected = !item.selected; + if (item.selected) { + int res = Result.addPhoto(item); + if (res != 0) { + item.selected = false; + switch (res) { + case Result.PICTURE_OUT: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_image_hint_easy_photos, + Setting.complexPictureCount), Toast.LENGTH_SHORT).show(); + break; + case Result.VIDEO_OUT: + Toast.makeText(getApplicationContext(), + getString(R.string.selector_reach_max_video_hint_easy_photos, + Setting.complexVideoCount), Toast.LENGTH_SHORT).show(); + break; + case Result.SINGLE_TYPE: + Toast.makeText(getApplicationContext(), getString(R.string.selector_single_type_hint_easy_photos), Toast.LENGTH_SHORT).show(); + break; + } + return; + } + if (Result.count() == Setting.count) { + unable = true; + } + } else { + Result.removePhoto(item); + previewFragment.setSelectedPosition(-1); + if (unable) { + unable = false; + } + } + toggleSelector(); + } + + private void singleSelector(Photo photo) { + if (!Result.isEmpty()) { + if (Result.getPhotoPath(0).equals(photo.path)) { + Result.removePhoto(photo); + } else { + Result.removePhoto(0); + Result.addPhoto(photo); + } + } else { + Result.addPhoto(photo); + } + toggleSelector(); + } + + private void shouldShowMenuDone() { + if (Result.isEmpty()) { + if (View.VISIBLE == tvDone.getVisibility()) { + ScaleAnimation scaleHide = new ScaleAnimation(1f, 0f, 1f, 0f); + scaleHide.setDuration(200); + tvDone.startAnimation(scaleHide); + } + tvDone.setVisibility(View.GONE); + flFragment.setVisibility(View.GONE); + } else { + if (View.GONE == tvDone.getVisibility()) { + ScaleAnimation scaleShow = new ScaleAnimation(0f, 1f, 0f, 1f); + scaleShow.setDuration(200); + tvDone.startAnimation(scaleShow); + } + flFragment.setVisibility(View.VISIBLE); + tvDone.setVisibility(View.VISIBLE); + + if (Result.isEmpty()) { + return; + } + + if (Setting.complexSelector) { + if (Setting.complexSingleType) { + if (Result.getPhotoType(0).contains(Type.VIDEO)) { + tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(), + Setting.complexVideoCount)); + return; + } + tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(), + Setting.complexPictureCount)); + return; + } + } + tvDone.setText(getString(R.string.selector_action_done_easy_photos, Result.count(), + Setting.count)); + } + } + + @Override + public void onPreviewPhotoClick(int position) { + String path = Result.getPhotoPath(position); + int size = photos.size(); + for (int i = 0; i < size; i++) { + if (TextUtils.equals(path, photos.get(i).path)) { + rvPhotos.scrollToPosition(i); + lastPosition = i; + tvNumber.setText(getString(R.string.preview_current_number_easy_photos, + lastPosition + 1, photos.size())); + previewFragment.setSelectedPosition(position); + toggleSelector(); + return; + } + } + } + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + private void setClick(View... views) { + for (View v : views) { + v.setOnClickListener(this); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java new file mode 100644 index 0000000..204acef --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PreviewFragment.java @@ -0,0 +1,73 @@ +package com.huantansheng.easyphotos.ui; + +import android.content.Context; +import android.os.Bundle; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.ui.adapter.PreviewPhotosFragmentAdapter; + + +public class PreviewFragment extends Fragment implements PreviewPhotosFragmentAdapter.OnClickListener { + + private OnPreviewFragmentClickListener mListener; + + private RecyclerView rvPhotos; + private PreviewPhotosFragmentAdapter adapter; + + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View rootView = inflater.inflate(R.layout.fragment_preview_easy_photos, container, false); + rvPhotos = (RecyclerView) rootView.findViewById(R.id.rv_preview_selected_photos); + adapter = new PreviewPhotosFragmentAdapter(getActivity(), this); + rvPhotos.setLayoutManager(new LinearLayoutManager(getActivity(), LinearLayoutManager.HORIZONTAL, false)); + rvPhotos.setAdapter(adapter); + return rootView; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + if (context instanceof OnPreviewFragmentClickListener) { + mListener = (OnPreviewFragmentClickListener) context; + } else { + throw new RuntimeException(context.toString() + + " must implement OnPreviewFragmentClickListener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + @Override + public void onPhotoClick(int position) { + mListener.onPreviewPhotoClick(position); + } + + + public interface OnPreviewFragmentClickListener { + void onPreviewPhotoClick(int position); + } + + public void notifyDataSetChanged() { + adapter.notifyDataSetChanged(); + } + + public void setSelectedPosition(int position) { + adapter.setChecked(position); + if (position != -1) { + rvPhotos.smoothScrollToPosition(position); + } + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java new file mode 100644 index 0000000..8ee55e2 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleActivity.java @@ -0,0 +1,668 @@ +package com.huantansheng.easyphotos.ui; + +import android.Manifest; +import android.app.Activity; +import android.app.Fragment; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.view.View; +import android.view.WindowManager; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.annotation.IdRes; +import androidx.annotation.NonNull; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import com.google.android.material.snackbar.Snackbar; +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.constant.Key; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.models.puzzle.Area; +import com.huantansheng.easyphotos.models.puzzle.DegreeSeekBar; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; +import com.huantansheng.easyphotos.models.puzzle.PuzzlePiece; +import com.huantansheng.easyphotos.models.puzzle.PuzzleUtils; +import com.huantansheng.easyphotos.models.puzzle.PuzzleView; +import com.huantansheng.easyphotos.models.sticker.StickerModel; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.PuzzleAdapter; +import com.huantansheng.easyphotos.ui.adapter.TextStickerAdapter; +import com.huantansheng.easyphotos.utils.bitmap.SaveBitmapCallBack; +import com.huantansheng.easyphotos.utils.media.DurationUtils; +import com.huantansheng.easyphotos.utils.permission.PermissionUtil; +import com.huantansheng.easyphotos.utils.settings.SettingsUtils; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Locale; + +/** + * 拼图界面 + * Created by huan on 2017/12/4. + */ + +public class PuzzleActivity extends AppCompatActivity implements View.OnClickListener, + PuzzleAdapter.OnItemClickListener, TextStickerAdapter.OnItemClickListener { + + private static WeakReference> toClass; + + + public static void startWithPhotos(Activity act, ArrayList photos, + String puzzleSaveDirPath, String puzzleSaveNamePrefix, + int requestCode, boolean replaceCustom, + @NonNull ImageEngine imageEngine) { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + Intent intent = new Intent(act, PuzzleActivity.class); + intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true); + intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos); + intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath); + intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix); + if (replaceCustom) { + toClass = new WeakReference>(act.getClass()); + } + act.startActivityForResult(intent, requestCode); + } + + public static void startWithPhotos(Fragment fragment, ArrayList photos, + String puzzleSaveDirPath, String puzzleSaveNamePrefix, + int requestCode, boolean replaceCustom, + @NonNull ImageEngine imageEngine) { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + Intent intent = new Intent(fragment.getActivity(), PuzzleActivity.class); + intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true); + intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos); + intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath); + intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix); + if (replaceCustom) { + toClass = + new WeakReference>(fragment.getActivity().getClass()); + } + fragment.startActivityForResult(intent, requestCode); + } + + public static void startWithPhotos(androidx.fragment.app.Fragment fragmentV, + ArrayList photos, String puzzleSaveDirPath, + String puzzleSaveNamePrefix, int requestCode, + boolean replaceCustom, @NonNull ImageEngine imageEngine) { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + if (Setting.imageEngine != imageEngine) { + Setting.imageEngine = imageEngine; + } + Intent intent = new Intent(fragmentV.getActivity(), PuzzleActivity.class); + intent.putExtra(Key.PUZZLE_FILE_IS_PHOTO, true); + intent.putParcelableArrayListExtra(Key.PUZZLE_FILES, photos); + intent.putExtra(Key.PUZZLE_SAVE_DIR, puzzleSaveDirPath); + intent.putExtra(Key.PUZZLE_SAVE_NAME_PREFIX, puzzleSaveNamePrefix); + if (replaceCustom) { + if (fragmentV.getActivity() != null) { + toClass = + new WeakReference>(fragmentV.getActivity().getClass()); + } + } + fragmentV.startActivityForResult(intent, requestCode); + } + + ArrayList photos = null; + + ArrayList bitmaps = new ArrayList<>(); + String saveDirPath, saveNamePrefix; + + private PuzzleView puzzleView; + private RecyclerView rvPuzzleTemplet; + private PuzzleAdapter puzzleAdapter; + private ProgressBar progressBar; + private int fileCount = 0; + + private LinearLayout llMenu; + private DegreeSeekBar degreeSeekBar; + private ArrayList ivMenus = new ArrayList<>(); + + private ArrayList degrees = new ArrayList<>(); + private int degreeIndex = -1; + private int controlFlag; + private static final int FLAG_CONTROL_PADDING = 0; + private static final int FLAG_CONTROL_CORNER = 1; + private static final int FLAG_CONTROL_ROTATE = 2; + + private int deviceWidth = 0; + private int deviceHeight = 0; + + private TextView tvTemplate, tvTextSticker; + private RelativeLayout mRootView, mBottomLayout; + private TextStickerAdapter textStickerAdapter; + + private StickerModel stickerModel; + + FloatingActionButton fab; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + getWindow().setAttributes(attrs); + setContentView(R.layout.activity_puzzle_easy_photos); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.hide(); + } + if (null == Setting.imageEngine) { + finish(); + return; + } + initData(); + initView(); + } + + private void initView() { + initIvMenu(); + initPuzzleView(); + initRecyclerView(); + progressBar = findViewById(R.id.progress); + setClick(R.id.tv_back, R.id.tv_done); + } + + private void initIvMenu() { + fab = (FloatingActionButton) findViewById(R.id.fab); + + tvTemplate = (TextView) findViewById(R.id.tv_template); + tvTextSticker = (TextView) findViewById(R.id.tv_text_sticker); + mRootView = (RelativeLayout) findViewById(R.id.m_root_view); + mBottomLayout = (RelativeLayout) findViewById(R.id.m_bottom_layout); + + llMenu = (LinearLayout) findViewById(R.id.ll_menu); + ImageView ivRotate = (ImageView) findViewById(R.id.iv_rotate); + ImageView ivCorner = (ImageView) findViewById(R.id.iv_corner); + ImageView ivPadding = (ImageView) findViewById(R.id.iv_padding); + + setClick(R.id.iv_replace, R.id.iv_mirror, R.id.iv_flip); + setClick(ivRotate, ivCorner, ivPadding, fab, tvTextSticker, tvTemplate); + + ivMenus.add(ivRotate); + ivMenus.add(ivCorner); + ivMenus.add(ivPadding); + + degreeSeekBar = (DegreeSeekBar) findViewById(R.id.degree_seek_bar); + degreeSeekBar.setScrollingListener(new DegreeSeekBar.ScrollingListener() { + @Override + public void onScrollStart() { + } + + @Override + public void onScroll(int currentDegrees) { + switch (controlFlag) { + case FLAG_CONTROL_PADDING: + puzzleView.setPiecePadding(currentDegrees); + break; + case FLAG_CONTROL_CORNER: + if (currentDegrees < 0) { + currentDegrees = 0; + } + puzzleView.setPieceRadian(currentDegrees); + break; + case FLAG_CONTROL_ROTATE: + puzzleView.rotate(currentDegrees - degrees.get(degreeIndex)); + degrees.remove(degreeIndex); + degrees.add(degreeIndex, currentDegrees); + break; + } + } + + @Override + public void onScrollEnd() { + } + }); + } + + private void initRecyclerView() { + rvPuzzleTemplet = (RecyclerView) findViewById(R.id.rv_puzzle_template); + puzzleAdapter = new PuzzleAdapter(); + puzzleAdapter.setOnItemClickListener(this); + rvPuzzleTemplet.setLayoutManager(new LinearLayoutManager(this, + LinearLayoutManager.HORIZONTAL, false)); + rvPuzzleTemplet.setAdapter(puzzleAdapter); + puzzleAdapter.refreshData(PuzzleUtils.getPuzzleLayouts(fileCount)); + + textStickerAdapter = new TextStickerAdapter(this, this); + } + + private void initPuzzleView() { + int themeType = fileCount > 3 ? 1 : 0; + puzzleView = (PuzzleView) findViewById(R.id.puzzle_view); + puzzleView.setPuzzleLayout(PuzzleUtils.getPuzzleLayout(themeType, fileCount, 0)); + puzzleView.setOnPieceSelectedListener(new PuzzleView.OnPieceSelectedListener() { + @Override + public void onPieceSelected(PuzzlePiece piece, int position) { + + if (null == piece) { + toggleIvMenu(R.id.iv_replace); + llMenu.setVisibility(View.GONE); + degreeSeekBar.setVisibility(View.GONE); + degreeIndex = -1; + controlFlag = -1; + return; + } + + if (degreeIndex != position) { + controlFlag = -1; + toggleIvMenu(R.id.iv_replace); + degreeSeekBar.setVisibility(View.GONE); + } + llMenu.setVisibility(View.VISIBLE); + degreeIndex = position; + } + }); + } + + private void loadPhoto() { + puzzleView.addPieces(bitmaps); + } + + private void initData() { + stickerModel = new StickerModel(); + deviceWidth = getResources().getDisplayMetrics().widthPixels; + deviceHeight = getResources().getDisplayMetrics().heightPixels; + Intent intent = getIntent(); + saveDirPath = intent.getStringExtra(Key.PUZZLE_SAVE_DIR); + saveNamePrefix = intent.getStringExtra(Key.PUZZLE_SAVE_NAME_PREFIX); + + photos = intent.getParcelableArrayListExtra(Key.PUZZLE_FILES); + fileCount = photos.size() > 9 ? 9 : photos.size(); + + new Thread(new Runnable() { + @Override + public void run() { + for (int i = 0; i < fileCount; i++) { + Bitmap bitmap = getScaleBitmap(photos.get(i).path, photos.get(i).uri); + bitmaps.add(bitmap); + degrees.add(0); + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + puzzleView.post(new Runnable() { + @Override + public void run() { + loadPhoto(); + } + }); + } + }); + + } + }).start(); + + + } + + private Bitmap getScaleBitmap(String path, Uri uri) { + Bitmap bitmap = null; + + try { + bitmap = Setting.imageEngine.getCacheBitmap(this, uri, deviceWidth / 2, + deviceHeight / 2); + } catch (Exception e) { + bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path), + deviceWidth / 2, deviceHeight / 2, true); + } + + if (bitmap == null) { + bitmap = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path), + deviceWidth / 2, deviceHeight / 2, true); + } + return bitmap; + + } + + @Override + public void onClick(View view) { + + int id = view.getId(); + + if (R.id.tv_back == id) { + finish(); + } else if (R.id.tv_done == id) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + savePhoto(); + } + + } else if (R.id.iv_replace == id) { + controlFlag = -1; + degreeSeekBar.setVisibility(View.GONE); + toggleIvMenu(R.id.iv_replace); + if (null == toClass) { + EasyPhotos.createAlbum(this, true, false, Setting.imageEngine).setCount(1).start(91); + } else { + Intent intent = new Intent(this, toClass.get()); + startActivityForResult(intent, 91); + } + } else if (R.id.iv_rotate == id) { + if (controlFlag == FLAG_CONTROL_ROTATE) { + if (degrees.get(degreeIndex) % 90 != 0) { + puzzleView.rotate(-degrees.get(degreeIndex)); + degrees.remove(degreeIndex); + degrees.add(degreeIndex, 0); + degreeSeekBar.setCurrentDegrees(0); + return; + } + puzzleView.rotate(90); + int degree = degrees.get(degreeIndex) + 90; + if (degree == 360 || degree == -360) { + degree = 0; + } + degrees.remove(degreeIndex); + degrees.add(degreeIndex, degree); + degreeSeekBar.setCurrentDegrees(degrees.get(degreeIndex)); + return; + } + handleSeekBar(FLAG_CONTROL_ROTATE, -360, 360, degrees.get(degreeIndex)); + toggleIvMenu(R.id.iv_rotate); + } else if (R.id.iv_mirror == id) { + degreeSeekBar.setVisibility(View.GONE); + controlFlag = -1; + toggleIvMenu(R.id.iv_mirror); + puzzleView.flipHorizontally(); + } else if (R.id.iv_flip == id) { + controlFlag = -1; + degreeSeekBar.setVisibility(View.GONE); + toggleIvMenu(R.id.iv_flip); + puzzleView.flipVertically(); + } else if (R.id.iv_corner == id) { + handleSeekBar(FLAG_CONTROL_CORNER, 0, 1000, puzzleView.getPieceRadian()); + toggleIvMenu(R.id.iv_corner); + } else if (R.id.iv_padding == id) { + handleSeekBar(FLAG_CONTROL_PADDING, 0, 100, puzzleView.getPiecePadding()); + toggleIvMenu(R.id.iv_padding); + } else if (R.id.tv_template == id) { + tvTemplate.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + tvTextSticker.setTextColor(ContextCompat.getColor(this, + R.color.easy_photos_fg_primary)); + + rvPuzzleTemplet.setAdapter(puzzleAdapter); + + } else if (R.id.tv_text_sticker == id) { + tvTextSticker.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + tvTemplate.setTextColor(ContextCompat.getColor(this, R.color.easy_photos_fg_primary)); + + rvPuzzleTemplet.setAdapter(textStickerAdapter); + } else if (R.id.fab == id) { + processBottomLayout(); + } + } + + private void processBottomLayout() { + if (View.VISIBLE == mBottomLayout.getVisibility()) { + mBottomLayout.setVisibility(View.GONE); + fab.setImageResource(R.drawable.ic_arrow_up_easy_photos); + } else { + mBottomLayout.setVisibility(View.VISIBLE); + fab.setImageResource(R.drawable.ic_arrow_down_easy_photos); + } + } + + private void handleSeekBar(int controlFlag, int rangeStart, int rangeEnd, float degrees) { + this.controlFlag = controlFlag; + degreeSeekBar.setVisibility(View.VISIBLE); + degreeSeekBar.setDegreeRange(rangeStart, rangeEnd); + degreeSeekBar.setCurrentDegrees((int) degrees); + } + + private void savePhoto() { + mBottomLayout.setVisibility(View.GONE); + fab.setVisibility(View.GONE); + progressBar.setVisibility(View.VISIBLE); + findViewById(R.id.tv_done).setVisibility(View.INVISIBLE); + findViewById(R.id.progress_frame).setVisibility(View.VISIBLE); + + puzzleView.clearHandling(); + puzzleView.invalidate(); + + stickerModel.save(this, mRootView, puzzleView, puzzleView.getWidth(), + puzzleView.getHeight(), saveDirPath, saveNamePrefix, true, + new SaveBitmapCallBack() { + @Override + public void onSuccess(File file) { + Intent intent = new Intent(); + + Photo photo = new Photo(file.getName(), UriUtils.getUri(PuzzleActivity.this, + file), file.getAbsolutePath(), file.lastModified() / 1000, + puzzleView.getWidth(), puzzleView.getHeight(), 0, file.length(), + DurationUtils.getDuration(file.getAbsolutePath()), "image/png"); + intent.putExtra(EasyPhotos.RESULT_PHOTOS, photo); + setResult(RESULT_OK, intent); + PuzzleActivity.this.finish(); + } + + @Override + public void onIOFailed(IOException exception) { + exception.printStackTrace(); + setResult(RESULT_OK); + PuzzleActivity.this.finish(); + } + + @Override + public void onCreateDirFailed() { + setResult(RESULT_OK); + PuzzleActivity.this.finish(); + } + + }); + } + + private void toggleIvMenu(@IdRes int resId) { + int size = ivMenus.size(); + for (int i = 0; i < size; i++) { + ImageView ivMenu = ivMenus.get(i); + if (ivMenu.getId() == resId) { + ivMenu.setColorFilter(ContextCompat.getColor(this, R.color.easy_photos_fg_accent)); + } else { + ivMenu.clearColorFilter(); + } + } + + } + + @Override + public void onItemClick(int themeType, int themeId) { + puzzleView.setPuzzleLayout(PuzzleUtils.getPuzzleLayout(themeType, fileCount, themeId)); + loadPhoto(); + resetDegress(); + + } + + private void resetDegress() { + llMenu.setVisibility(View.GONE); + degreeSeekBar.setVisibility(View.GONE); + degreeIndex = -1; + int size = degrees.size(); + for (int i = 0; i < size; i++) { + degrees.remove(i); + degrees.add(i, 0); + } + } + + @Override + protected void onDestroy() { + if (null != toClass) { + toClass.clear(); + toClass = null; + } + super.onDestroy(); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == Code.REQUEST_SETTING_APP_DETAILS) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(this, getNeedPermissions())) { + savePhoto(); + } + return; + } + switch (resultCode) { + case RESULT_OK: + if (degreeIndex != -1) { + degrees.remove(degreeIndex); + degrees.add(degreeIndex, 0); + } + + String tempPath = ""; + Uri tempUri = null; + + ArrayList photos = + data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS); + Photo photo = photos.get(0); + tempPath = photo.path; + tempUri = photo.uri; + + + final String path = tempPath; + final Uri uri = tempUri; + new Thread(new Runnable() { + @Override + public void run() { + final Bitmap bitmap = getScaleBitmap(path, uri); + runOnUiThread(new Runnable() { + @Override + public void run() { + puzzleView.replace(bitmap); + } + }); + } + }).start(); + + break; + case RESULT_CANCELED: + break; + default: + break; + } + } + + protected String[] getNeedPermissions() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return new String[]{Manifest.permission.CAMERA, + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE}; + } + return new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}; + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, + @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + PermissionUtil.onPermissionResult(this, permissions, grantResults, + new PermissionUtil.PermissionCallBack() { + @Override + public void onSuccess() { + savePhoto(); + } + + @Override + public void onShouldShow() { + Snackbar.make(rvPuzzleTemplet, R.string.permissions_again_easy_photos, + Snackbar.LENGTH_INDEFINITE).setAction("go", + new View.OnClickListener() { + @Override + public void onClick(View view) { + if (PermissionUtil.checkAndRequestPermissionsInActivity(PuzzleActivity.this, getNeedPermissions())) { + savePhoto(); + } + } + }).show(); + } + + @Override + public void onFailed() { + Snackbar.make(rvPuzzleTemplet, R.string.permissions_die_easy_photos, + Snackbar.LENGTH_INDEFINITE).setAction("go", + new View.OnClickListener() { + @Override + public void onClick(View view) { + SettingsUtils.startMyApplicationDetailsForResult(PuzzleActivity.this, + getPackageName()); + } + }).show(); + } + }); + } + + @Override + public void onItemClick(String stickerValue) { + if (stickerValue.equals("-1")) { + + PuzzleLayout puzzleLayout = puzzleView.getPuzzleLayout(); + int size = puzzleLayout.getAreaCount(); + for (int i = 0; i < size; i++) { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()); + String date = format.format(photos.get(i).time); + stickerModel.addTextSticker(this, getSupportFragmentManager(), date, mRootView); + stickerModel.currTextSticker.isChecked = true; + Area area = puzzleLayout.getArea(i); + stickerModel.currTextSticker.moveTo(area.centerX(), area.centerY()); + } + + return; + } + + stickerModel.addTextSticker(this, getSupportFragmentManager(), stickerValue, mRootView); + } + + @Override + public void onBackPressed() { + if (View.VISIBLE == mBottomLayout.getVisibility()) { + processBottomLayout(); + return; + } + super.onBackPressed(); + } + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + private void setClick(View... views) { + for (View v : views) { + v.setOnClickListener(this); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java new file mode 100644 index 0000000..0f50c2a --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/PuzzleSelectorActivity.java @@ -0,0 +1,264 @@ +package com.huantansheng.easyphotos.ui; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.Intent; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import androidx.annotation.IdRes; +import androidx.core.content.ContextCompat; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.SimpleItemAnimator; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AccelerateInterpolator; +import android.widget.RelativeLayout; +import android.widget.Toast; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Code; +import com.huantansheng.easyphotos.models.album.AlbumModel; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.adapter.AlbumItemsAdapter; +import com.huantansheng.easyphotos.ui.adapter.PuzzleSelectorAdapter; +import com.huantansheng.easyphotos.ui.adapter.PuzzleSelectorPreviewAdapter; +import com.huantansheng.easyphotos.ui.widget.PressedTextView; +import com.huantansheng.easyphotos.utils.Color.ColorUtils; +import com.huantansheng.easyphotos.utils.system.SystemUtils; + +import java.util.ArrayList; + +public class PuzzleSelectorActivity extends AppCompatActivity implements View.OnClickListener, AlbumItemsAdapter.OnClickListener, PuzzleSelectorAdapter.OnClickListener, PuzzleSelectorPreviewAdapter.OnClickListener { + + public static void start(Activity activity) { + Intent intent = new Intent(activity, PuzzleSelectorActivity.class); + activity.startActivityForResult(intent, Code.REQUEST_PUZZLE_SELECTOR); + } + + private AlbumModel albumModel; + + private AnimatorSet setShow; + private AnimatorSet setHide; + + private RelativeLayout rootViewAlbumItems, rootSelectorView; + private RecyclerView rvAlbumItems; + private AlbumItemsAdapter albumItemsAdapter; + private PressedTextView tvAlbumItems; + + private ArrayList photoList = new ArrayList<>(); + private PuzzleSelectorAdapter photosAdapter; + private RecyclerView rvPhotos, rvPreview; + private PuzzleSelectorPreviewAdapter previewAdapter; + private ArrayList selectedPhotos = new ArrayList<>(); + + private PressedTextView tvDone; + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_puzzle_selector_easy_photos); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + int statusColor = getWindow().getStatusBarColor(); + if(statusColor == Color.TRANSPARENT){ + statusColor = ContextCompat.getColor(this, R.color.easy_photos_status_bar); + } + if (ColorUtils.isWhiteColor(statusColor)) { + SystemUtils.getInstance().setStatusDark(this, true); + } + } + albumModel = AlbumModel.getInstance(); +// albumModel.query(this, null); + if (null == albumModel||albumModel.getAlbumItems().isEmpty()) { + finish(); + return; + } + initView(); + } + + private void initView() { + setClick(R.id.iv_back); + tvAlbumItems = (PressedTextView) findViewById(R.id.tv_album_items); + tvAlbumItems.setText(albumModel.getAlbumItems().get(0).name); + rootSelectorView = (RelativeLayout) findViewById(R.id.m_selector_root); + tvDone = (PressedTextView) findViewById(R.id.tv_done); + tvDone.setOnClickListener(this); + tvAlbumItems.setOnClickListener(this); + initAlbumItems(); + initPhotos(); + initPreview(); + } + + private void initPreview() { + rvPreview = (RecyclerView) findViewById(R.id.rv_preview_selected_photos); + LinearLayoutManager lm = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false); + previewAdapter = new PuzzleSelectorPreviewAdapter(this, selectedPhotos, this); + rvPreview.setLayoutManager(lm); + rvPreview.setAdapter(previewAdapter); + } + + private void initPhotos() { + rvPhotos = (RecyclerView) findViewById(R.id.rv_photos); + ((SimpleItemAnimator) rvPhotos.getItemAnimator()).setSupportsChangeAnimations(false);//去除item更新的闪光 + + photoList.addAll(albumModel.getCurrAlbumItemPhotos(0)); + photosAdapter = new PuzzleSelectorAdapter(this, photoList, this); + + int columns = getResources().getInteger(R.integer.photos_columns_easy_photos); + + GridLayoutManager gridLayoutManager = new GridLayoutManager(this, columns); + rvPhotos.setLayoutManager(gridLayoutManager); + rvPhotos.setAdapter(photosAdapter); + } + + private void initAlbumItems() { + rootViewAlbumItems = (RelativeLayout) findViewById(R.id.root_view_album_items); + rootViewAlbumItems.setOnClickListener(this); + setClick(R.id.iv_album_items); + rvAlbumItems = (RecyclerView) findViewById(R.id.rv_album_items); + LinearLayoutManager lm = new LinearLayoutManager(this); + ArrayList list = new ArrayList(albumModel.getAlbumItems()); + albumItemsAdapter = new AlbumItemsAdapter(this, list, 0, this); + rvAlbumItems.setLayoutManager(lm); + rvAlbumItems.setAdapter(albumItemsAdapter); + } + + private void setClick(@IdRes int... ids) { + for (int id : ids) { + findViewById(id).setOnClickListener(this); + } + } + + @Override + public void onClick(View view) { + int id = view.getId(); + if (R.id.iv_back == id) { + setResult(RESULT_CANCELED); + finish(); + } else if (R.id.tv_album_items == id || R.id.iv_album_items == id) { + showAlbumItems(View.GONE == rootViewAlbumItems.getVisibility()); + } else if (R.id.root_view_album_items == id) { + showAlbumItems(false); + } else if (R.id.tv_done == id) { + PuzzleActivity.startWithPhotos(this, selectedPhotos, Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + getString(R.string.app_name), "IMG", Code.REQUEST_PUZZLE, false, Setting.imageEngine); + + } + } + + private void showAlbumItems(boolean isShow) { + if (null == setShow) { + newAnimators(); + } + if (isShow) { + rootViewAlbumItems.setVisibility(View.VISIBLE); + setShow.start(); + } else { + setHide.start(); + } + } + + private void newAnimators() { + newHideAnim(); + newShowAnim(); + } + + private void newShowAnim() { + ObjectAnimator translationShow = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", rootSelectorView.getTop(), 0); + ObjectAnimator alphaShow = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 0.0f, 1.0f); + translationShow.setDuration(300); + setShow = new AnimatorSet(); + setShow.setInterpolator(new AccelerateDecelerateInterpolator()); + setShow.play(translationShow).with(alphaShow); + } + + private void newHideAnim() { + ObjectAnimator translationHide = ObjectAnimator.ofFloat(rvAlbumItems, "translationY", 0, rootSelectorView.getTop()); + ObjectAnimator alphaHide = ObjectAnimator.ofFloat(rootViewAlbumItems, "alpha", 1.0f, 0.0f); + translationHide.setDuration(200); + setHide = new AnimatorSet(); + setHide.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + rootViewAlbumItems.setVisibility(View.GONE); + } + }); + setHide.setInterpolator(new AccelerateInterpolator()); + setHide.play(translationHide).with(alphaHide); + } + + @Override + public void onAlbumItemClick(int position, int realPosition) { + updatePhotos(realPosition); + showAlbumItems(false); + tvAlbumItems.setText(albumModel.getAlbumItems().get(realPosition).name); + } + + private void updatePhotos(int currAlbumItemIndex) { + photoList.clear(); + photoList.addAll(albumModel.getCurrAlbumItemPhotos(currAlbumItemIndex)); + + photosAdapter.notifyDataSetChanged(); + rvPhotos.scrollToPosition(0); + } + + + @Override + public void onBackPressed() { + + if (null != rootViewAlbumItems && rootViewAlbumItems.getVisibility() == View.VISIBLE) { + showAlbumItems(false); + return; + } + + super.onBackPressed(); + } + + @Override + public void onPhotoClick(int position) { + if (selectedPhotos.size() > 8) { + Toast.makeText(getApplicationContext(), getString(R.string.selector_reach_max_image_hint_easy_photos, 9), Toast.LENGTH_SHORT).show(); + return; + } + + selectedPhotos.add(photoList.get(position)); + previewAdapter.notifyDataSetChanged(); + rvPreview.smoothScrollToPosition(selectedPhotos.size() - 1); + + tvDone.setText(getString(R.string.selector_action_done_easy_photos, selectedPhotos.size(), 9)); + if (selectedPhotos.size() > 1) { + tvDone.setVisibility(View.VISIBLE); + } + } + + @Override + public void onDeleteClick(int position) { + selectedPhotos.remove(position); + previewAdapter.notifyDataSetChanged(); + tvDone.setText(getString(R.string.selector_action_done_easy_photos, selectedPhotos.size(), 9)); + if (selectedPhotos.size() < 2) { + tvDone.setVisibility(View.INVISIBLE); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode == RESULT_OK) { + if (requestCode == Code.REQUEST_PUZZLE) { + setResult(RESULT_OK, data); + finish(); + } + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java new file mode 100644 index 0000000..17d0727 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/AlbumItemsAdapter.java @@ -0,0 +1,182 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.ad.AdViewHolder; +import com.huantansheng.easyphotos.models.album.entity.AlbumItem; +import com.huantansheng.easyphotos.setting.Setting; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * 媒体列表适配器 + * Created by huan on 2017/10/23. + */ + +public class AlbumItemsAdapter extends RecyclerView.Adapter { + private static final int TYPE_AD = 0; + private static final int TYPE_ALBUM_ITEMS = 1; + + private ArrayList dataList; + private LayoutInflater mInflater; + private int selectedPosition; + private OnClickListener listener; + private int adPosition = 0; + private int padding = 0; + + private boolean clearAd = false; + + public interface OnClickListener { + void onAlbumItemClick(int position, int realPosition); + } + + + public AlbumItemsAdapter(Context cxt, ArrayList list, int selectedPosition, OnClickListener listener) { + this.dataList = list; + this.mInflater = LayoutInflater.from(cxt); + this.listener = listener; + this.selectedPosition = selectedPosition; + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + switch (viewType) { + case TYPE_AD: + return new AdViewHolder(mInflater.inflate(R.layout.item_ad_easy_photos, parent, false)); + default: + return new AlbumItemsViewHolder(mInflater.inflate(R.layout.item_dialog_album_items_easy_photos, parent, false)); + } + } + + @Override + public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { + final int p = position; + if (holder instanceof AlbumItemsViewHolder) { + if (padding == 0) { + padding = ((AlbumItemsViewHolder) holder).mRoot.getPaddingLeft(); + } + if (p == getItemCount() - 1) { + ((AlbumItemsViewHolder) holder).mRoot.setPadding(padding, padding, padding, padding); + } else { + ((AlbumItemsViewHolder) holder).mRoot.setPadding(padding, padding, padding, 0); + } + AlbumItem item = (AlbumItem) dataList.get(p); + Setting.imageEngine.loadPhoto(((AlbumItemsViewHolder) holder).ivAlbumCover.getContext(), item.coverImageUri, ((AlbumItemsViewHolder) holder).ivAlbumCover); + ((AlbumItemsViewHolder) holder).tvAlbumName.setText(item.name); + ((AlbumItemsViewHolder) holder).tvAlbumPhotosCount.setText(String.valueOf(item.photos.size())); + if (selectedPosition == p) { + ((AlbumItemsViewHolder) holder).ivSelected.setVisibility(View.VISIBLE); + } else { + ((AlbumItemsViewHolder) holder).ivSelected.setVisibility(View.INVISIBLE); + } + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + int realPosition = p; + if (Setting.hasAlbumItemsAd()) { + if (p > adPosition) { + realPosition--; + } + } + int tempSelected = selectedPosition; + selectedPosition = p; + notifyItemChanged(tempSelected); + notifyItemChanged(p); + listener.onAlbumItemClick(p, realPosition); + } + }); + return; + } + + if (holder instanceof AdViewHolder) { + if (clearAd) { + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + adPosition = p; + if (!Setting.albumItemsAdIsOk) { + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + + WeakReference weakReference = (WeakReference) dataList.get(p); + + if (null != weakReference) { + View adView = (View) weakReference.get(); + if (null != adView) { + if (null != adView.getParent()) { + if (adView.getParent() instanceof FrameLayout) { + ((FrameLayout) adView.getParent()).removeAllViews(); + } + } + ((AdViewHolder) holder).adFrame.setVisibility(View.VISIBLE); + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.addView(adView); + } + } + } + } + + public void clearAd() { + clearAd = true; + notifyDataSetChanged(); + } + + public void setSelectedPosition(int position) { + int realPosition = position; + if (Setting.hasAlbumItemsAd()) { + if (position > adPosition) { + realPosition--; + } + } + int tempSelected = selectedPosition; + selectedPosition = position; + notifyItemChanged(tempSelected); + notifyItemChanged(position); + listener.onAlbumItemClick(position, realPosition); + } + + @Override + public int getItemCount() { + return dataList.size(); + } + + @Override + public int getItemViewType(int position) { + Object item = dataList.get(position); + if (null == item || item instanceof WeakReference) { + return TYPE_AD; + } else { + return TYPE_ALBUM_ITEMS; + } + } + + public class AlbumItemsViewHolder extends RecyclerView.ViewHolder { + ImageView ivAlbumCover; + TextView tvAlbumName; + TextView tvAlbumPhotosCount; + ImageView ivSelected; + ConstraintLayout mRoot; + + AlbumItemsViewHolder(View itemView) { + super(itemView); + this.ivAlbumCover = (ImageView) itemView.findViewById(R.id.iv_album_cover); + this.tvAlbumName = (TextView) itemView.findViewById(R.id.tv_album_name); + this.tvAlbumPhotosCount = (TextView) itemView.findViewById(R.id.tv_album_photos_count); + this.ivSelected = (ImageView) itemView.findViewById(R.id.iv_selected); + this.mRoot = (ConstraintLayout) itemView.findViewById(R.id.m_root_view); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java new file mode 100644 index 0000000..554055d --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PhotosAdapter.java @@ -0,0 +1,306 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; + +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.ad.AdViewHolder; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.widget.PressedImageView; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; + +/** + * 专辑相册适配器 + * Created by huan on 2017/10/23. + */ +public class PhotosAdapter extends RecyclerView.Adapter { + private static final int TYPE_AD = 0; + private static final int TYPE_CAMERA = 1; + private static final int TYPE_ALBUM_ITEMS = 2; + + private ArrayList dataList; + private LayoutInflater mInflater; + private OnClickListener listener; + private boolean unable, isSingle; + private int singlePosition; + + private boolean clearAd = false; + + + public PhotosAdapter(Context cxt, ArrayList dataList, OnClickListener listener) { + this.dataList = dataList; + this.listener = listener; + this.mInflater = LayoutInflater.from(cxt); + this.unable = Result.count() == Setting.count; + this.isSingle = Setting.count == 1; + } + + public void change() { + unable = Result.count() == Setting.count; + notifyDataSetChanged(); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + switch (viewType) { + case TYPE_AD: + return new AdViewHolder(mInflater.inflate(R.layout.item_ad_easy_photos, parent, false)); + case TYPE_CAMERA: + return new CameraViewHolder(mInflater.inflate(R.layout.item_camera_easy_photos, parent, false)); + default: + return new PhotoViewHolder(mInflater.inflate(R.layout.item_rv_photos_easy_photos, parent, false)); + } + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, int position) { + final int p = position; + if (holder instanceof PhotoViewHolder) { + final Photo item = (Photo) dataList.get(p); + if (item == null) return; + updateSelector(((PhotoViewHolder) holder).tvSelector, item.selected, item, p); + String path = item.path; + Uri uri = item.uri; + String type = item.type; + long duration = item.duration; + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).ivVideo.setVisibility(View.GONE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration)); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).ivVideo.setVisibility(View.VISIBLE); + + } else { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setVisibility(View.GONE); + ((PhotoViewHolder) holder).ivVideo.setVisibility(View.GONE); + } + + ((PhotoViewHolder) holder).vSelector.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).tvSelector.setVisibility(View.VISIBLE); + ((PhotoViewHolder) holder).ivPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + int realPosition = p; + if (Setting.hasPhotosAd()) { + realPosition--; + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + realPosition--; + } + listener.onPhotoClick(p, realPosition); + } + }); + + + ((PhotoViewHolder) holder).vSelector.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (isSingle) { + singleSelector(item, p); + return; + } + if (unable) { + if (item.selected) { + Result.removePhoto(item); + if (unable) { + unable = false; + } + listener.onSelectorChanged(); + notifyDataSetChanged(); + return; + } + listener.onSelectorOutOfMax(null); + return; + } + item.selected = !item.selected; + if (item.selected) { + int res = Result.addPhoto(item); + if (res != 0) { + listener.onSelectorOutOfMax(res); + item.selected = false; + return; + } + ((PhotoViewHolder) holder).tvSelector.setBackgroundResource(R.drawable.bg_select_true_easy_photos); + ((PhotoViewHolder) holder).tvSelector.setText(String.valueOf(Result.count())); + if (Result.count() == Setting.count) { + unable = true; + notifyDataSetChanged(); + } + } else { + Result.removePhoto(item); + if (unable) { + unable = false; + } + notifyDataSetChanged(); + } + listener.onSelectorChanged(); + } + }); + return; + } + + if (holder instanceof AdViewHolder) { + if (clearAd) { + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + if (!Setting.photoAdIsOk) { + ((AdViewHolder) holder).adFrame.setVisibility(View.GONE); + return; + } + + WeakReference weakReference = (WeakReference) dataList.get(p); + + if (null != weakReference) { + View adView = (View) weakReference.get(); + if (null != adView) { + if (null != adView.getParent()) { + if (adView.getParent() instanceof FrameLayout) { + ((FrameLayout) adView.getParent()).removeAllViews(); + } + } + ((AdViewHolder) holder).adFrame.setVisibility(View.VISIBLE); + ((AdViewHolder) holder).adFrame.removeAllViews(); + ((AdViewHolder) holder).adFrame.addView(adView); + } + } + } + + if (holder instanceof CameraViewHolder) { + ((CameraViewHolder) holder).flCamera.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onCameraClick(); + } + }); + } + } + + public void clearAd() { + clearAd = true; + notifyDataSetChanged(); + } + + private void singleSelector(Photo photo, int position) { + if (!Result.isEmpty()) { + if (Result.getPhotoPath(0).equals(photo.path)) { + Result.removePhoto(photo); + } else { + Result.removePhoto(0); + Result.addPhoto(photo); + notifyItemChanged(singlePosition); + } + } else { + Result.addPhoto(photo); + } + notifyItemChanged(position); + listener.onSelectorChanged(); + } + + private void updateSelector(TextView tvSelector, boolean selected, Photo photo, int position) { + if (selected) { + String number = Result.getSelectorNumber(photo); + if (number.equals("0")) { + tvSelector.setBackgroundResource(R.drawable.bg_select_false_easy_photos); + tvSelector.setText(null); + return; + } + tvSelector.setText(number); + tvSelector.setBackgroundResource(R.drawable.bg_select_true_easy_photos); + if (isSingle) { + singlePosition = position; + tvSelector.setText("1"); + } + } else { + if (unable) { + tvSelector.setBackgroundResource(R.drawable.bg_select_false_unable_easy_photos); + } else { + tvSelector.setBackgroundResource(R.drawable.bg_select_false_easy_photos); + } + tvSelector.setText(null); + } + } + + @Override + public int getItemCount() { + return dataList.size(); + } + + @Override + public int getItemViewType(int position) { + if (0 == position) { + if (Setting.hasPhotosAd()) { + return TYPE_AD; + } + if (Setting.isShowCamera && !Setting.isBottomRightCamera()) { + return TYPE_CAMERA; + } + } + if (1 == position && !Setting.isBottomRightCamera()) { + if (Setting.hasPhotosAd() && Setting.isShowCamera) { + return TYPE_CAMERA; + } + } + return TYPE_ALBUM_ITEMS; + } + + public interface OnClickListener { + void onCameraClick(); + + void onPhotoClick(int position, int realPosition); + + void onSelectorOutOfMax(@Nullable Integer result); + + void onSelectorChanged(); + } + + private static class CameraViewHolder extends RecyclerView.ViewHolder { + final FrameLayout flCamera; + + CameraViewHolder(View itemView) { + super(itemView); + this.flCamera = itemView.findViewById(R.id.fl_camera); + } + } + + public static class PhotoViewHolder extends RecyclerView.ViewHolder { + final PressedImageView ivPhoto; + final TextView tvSelector; + final View vSelector; + final TextView tvType; + final ImageView ivVideo; + + PhotoViewHolder(View itemView) { + super(itemView); + this.ivPhoto = itemView.findViewById(R.id.iv_photo); + this.tvSelector = itemView.findViewById(R.id.tv_selector); + this.vSelector = itemView.findViewById(R.id.v_selector); + this.tvType = itemView.findViewById(R.id.tv_type); + this.ivVideo = itemView.findViewById(R.id.iv_play); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java new file mode 100644 index 0000000..d93cd8d --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosAdapter.java @@ -0,0 +1,165 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import android.content.Intent; +import android.graphics.PointF; +import android.net.Uri; +import android.os.Build; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; + +import androidx.annotation.NonNull; +import androidx.core.content.FileProvider; +import androidx.recyclerview.widget.RecyclerView; + +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; +import com.github.chrisbanes.photoview.OnScaleChangedListener; +import com.github.chrisbanes.photoview.PhotoView; +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; + +import java.io.File; +import java.util.ArrayList; + +/** + * 大图预览界面图片集合的适配器 + * Created by huan on 2017/10/26. + */ +public class PreviewPhotosAdapter extends RecyclerView.Adapter { + private ArrayList photos; + private OnClickListener listener; + private LayoutInflater inflater; + + public interface OnClickListener { + void onPhotoClick(); + + void onPhotoScaleChanged(); + } + + public PreviewPhotosAdapter(Context cxt, ArrayList photos, OnClickListener listener) { + this.photos = photos; + this.inflater = LayoutInflater.from(cxt); + this.listener = listener; + } + + @NonNull + @Override + public PreviewPhotosViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new PreviewPhotosViewHolder(inflater.inflate(R.layout.item_preview_photo_easy_photos, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull final PreviewPhotosViewHolder holder, int position) { + final Uri uri = photos.get(position).uri; + final String path = photos.get(position).path; + final String type = photos.get(position).type; + final double ratio = + (double) photos.get(position).height / (double) photos.get(position).width; + + holder.ivPlay.setVisibility(View.GONE); + holder.ivPhotoView.setVisibility(View.GONE); + holder.ivLongPhoto.setVisibility(View.GONE); + + if (type.contains(Type.VIDEO)) { + holder.ivPhotoView.setVisibility(View.VISIBLE); + Setting.imageEngine.loadPhoto(holder.ivPhotoView.getContext(), uri, holder.ivPhotoView); + holder.ivPlay.setVisibility(View.VISIBLE); + holder.ivPlay.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + toPlayVideo(v, uri, type); + } + }); + } else if (path.endsWith(Type.GIF) || type.endsWith(Type.GIF)) { + holder.ivPhotoView.setVisibility(View.VISIBLE); + Setting.imageEngine.loadGif(holder.ivPhotoView.getContext(), uri, holder.ivPhotoView); + } else { + if (ratio > 2.3) { + holder.ivLongPhoto.setVisibility(View.VISIBLE); + holder.ivLongPhoto.setImage(ImageSource.uri(path)); + } else { + holder.ivPhotoView.setVisibility(View.VISIBLE); + Setting.imageEngine.loadPhoto(holder.ivPhotoView.getContext(), uri, + holder.ivPhotoView); + } + } + + holder.ivLongPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onPhotoClick(); + } + }); + holder.ivPhotoView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onPhotoClick(); + } + }); + holder.ivLongPhoto.setOnStateChangedListener(new SubsamplingScaleImageView.OnStateChangedListener() { + @Override + public void onScaleChanged(float newScale, int origin) { + listener.onPhotoScaleChanged(); + } + + @Override + public void onCenterChanged(PointF newCenter, int origin) { + + } + }); + + holder.ivPhotoView.setScale(1f); + + holder.ivPhotoView.setOnScaleChangeListener(new OnScaleChangedListener() { + @Override + public void onScaleChange(float scaleFactor, float focusX, float focusY) { + listener.onPhotoScaleChanged(); + } + }); + } + + private void toPlayVideo(View v, Uri uri, String type) { + Context context = v.getContext(); + Intent intent = new Intent(Intent.ACTION_VIEW); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + intent.setDataAndType(uri, type); + context.startActivity(intent); + } + + private Uri getUri(Context context, String path, Intent intent) { + File file = new File(path); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + return FileProvider.getUriForFile(context, Setting.fileProviderAuthority, file); + } else { + return Uri.fromFile(file); + } + } + + @Override + public int getItemCount() { + return photos.size(); + } + + public class PreviewPhotosViewHolder extends RecyclerView.ViewHolder { + public SubsamplingScaleImageView ivLongPhoto; + ImageView ivPlay; + PhotoView ivPhotoView; + + PreviewPhotosViewHolder(View itemView) { + super(itemView); + ivLongPhoto = itemView.findViewById(R.id.iv_long_photo); + ivPhotoView = itemView.findViewById(R.id.iv_photo_view); + ivPlay = itemView.findViewById(R.id.iv_play); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java new file mode 100644 index 0000000..8687d30 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PreviewPhotosFragmentAdapter.java @@ -0,0 +1,104 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import androidx.recyclerview.widget.RecyclerView; + +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.result.Result; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.ui.widget.PressedImageView; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +/** + * 预览所有选中图片集合的适配器 + * Created by huan on 2017/12/1. + */ + +public class PreviewPhotosFragmentAdapter extends RecyclerView.Adapter { + private LayoutInflater inflater; + private OnClickListener listener; + private int checkedPosition = -1; + + public PreviewPhotosFragmentAdapter(Context context, OnClickListener listener) { + this.inflater = LayoutInflater.from(context); + this.listener = listener; + } + + + @Override + public PreviewPhotoVH onCreateViewHolder(ViewGroup parent, int viewType) { + return new PreviewPhotoVH(inflater.inflate(R.layout.item_preview_selected_photos_easy_photos, parent, false)); + } + + @Override + public void onBindViewHolder(PreviewPhotoVH holder, int position) { + final int p = position; + String path = Result.getPhotoPath(position); + String type = Result.getPhotoType(position); + Uri uri = Result.getPhotoUri(position); + long duration = Result.getPhotoDuration(position); + + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(holder.ivPhoto.getContext(), uri, holder.ivPhoto); + holder.tvType.setText(R.string.gif_easy_photos); + holder.tvType.setVisibility(View.VISIBLE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(holder.ivPhoto.getContext(), uri, holder.ivPhoto); + holder.tvType.setText(DurationUtils.format(duration)); + holder.tvType.setVisibility(View.VISIBLE); + } else { + Setting.imageEngine.loadPhoto(holder.ivPhoto.getContext(), uri, holder.ivPhoto); + holder.tvType.setVisibility(View.GONE); + } + + if (checkedPosition == p) { + holder.frame.setVisibility(View.VISIBLE); + } else { + holder.frame.setVisibility(View.GONE); + } + holder.ivPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + listener.onPhotoClick(p); + } + }); + } + + @Override + public int getItemCount() { + return Result.count(); + } + + public void setChecked(int position) { + if (checkedPosition == position) { + return; + } + checkedPosition = position; + notifyDataSetChanged(); + } + + class PreviewPhotoVH extends RecyclerView.ViewHolder { + PressedImageView ivPhoto; + View frame; + TextView tvType; + + public PreviewPhotoVH(View itemView) { + super(itemView); + ivPhoto = (PressedImageView) itemView.findViewById(R.id.iv_photo); + frame = itemView.findViewById(R.id.v_selector); + tvType = (TextView) itemView.findViewById(R.id.tv_type); + } + } + + public interface OnClickListener { + void onPhotoClick(int position); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java new file mode 100644 index 0000000..4365b08 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleAdapter.java @@ -0,0 +1,103 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.puzzle.PuzzleLayout; +import com.huantansheng.easyphotos.models.puzzle.SquarePuzzleView; +import com.huantansheng.easyphotos.models.puzzle.template.slant.NumberSlantLayout; +import com.huantansheng.easyphotos.models.puzzle.template.straight.NumberStraightLayout; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author wupanjie + */ +public class PuzzleAdapter extends RecyclerView.Adapter { + + private List layoutData = new ArrayList<>(); + private OnItemClickListener onItemClickListener; + private int selectedNumber = 0; + + @Override + public PuzzleViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = + LayoutInflater.from(parent.getContext()).inflate(R.layout.item_puzzle_easy_photos, parent, false); + return new PuzzleViewHolder(itemView); + } + + @Override + public void onBindViewHolder(PuzzleViewHolder holder, int position) { + final PuzzleLayout puzzleLayout = layoutData.get(position); + final int p = position; + if (selectedNumber == position) { + holder.mFrame.setVisibility(View.VISIBLE); + } else { + holder.mFrame.setVisibility(View.GONE); + } + holder.puzzleView.setNeedDrawLine(true); + holder.puzzleView.setNeedDrawOuterLine(true); + holder.puzzleView.setTouchEnable(false); + + holder.puzzleView.setPuzzleLayout(puzzleLayout); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (selectedNumber == p) { + return; + } + if (onItemClickListener != null) { + int themeType = 0; + int themeId = 0; + if (puzzleLayout instanceof NumberSlantLayout) { + themeType = 0; + themeId = ((NumberSlantLayout) puzzleLayout).getTheme(); + } else if (puzzleLayout instanceof NumberStraightLayout) { + themeType = 1; + themeId = ((NumberStraightLayout) puzzleLayout).getTheme(); + } + selectedNumber = p; + onItemClickListener.onItemClick(themeType, themeId); + notifyDataSetChanged(); + } + } + }); + + } + + @Override + public int getItemCount() { + return layoutData == null ? 0 : layoutData.size(); + } + + public void refreshData(List layoutData) { + this.layoutData = layoutData; + + notifyDataSetChanged(); + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + this.onItemClickListener = onItemClickListener; + } + + public static class PuzzleViewHolder extends RecyclerView.ViewHolder { + + SquarePuzzleView puzzleView; + View mFrame; + + public PuzzleViewHolder(View itemView) { + super(itemView); + puzzleView = (SquarePuzzleView) itemView.findViewById(R.id.puzzle); + mFrame = itemView.findViewById(R.id.m_selector); + } + } + + public interface OnItemClickListener { + void onItemClick(int themeType, int themeId); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java new file mode 100644 index 0000000..edb2bbf --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorAdapter.java @@ -0,0 +1,99 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import androidx.recyclerview.widget.RecyclerView; + +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +import java.util.ArrayList; + +/** + * 拼图相册适配器 + * Created by huan on 2017/10/23. + */ + +public class PuzzleSelectorAdapter extends RecyclerView.Adapter { + + + private ArrayList dataList; + private LayoutInflater mInflater; + private OnClickListener listener; + + + public PuzzleSelectorAdapter(Context cxt, ArrayList dataList, OnClickListener listener) { + this.dataList = dataList; + this.listener = listener; + this.mInflater = LayoutInflater.from(cxt); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + return new PhotoViewHolder(mInflater.inflate(R.layout.item_puzzle_selector_easy_photos, parent, false)); + + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + + final int p = position; + Photo photo = dataList.get(position); + String path = photo.path; + String type = photo.type; + Uri uri = photo.uri; + long duration = photo.duration; + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration)); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setVisibility(View.GONE); + } + + ((PhotoViewHolder) holder).ivPhoto.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onPhotoClick(p); + } + }); + } + + + @Override + public int getItemCount() { + return null == dataList ? 0 : dataList.size(); + } + + + public interface OnClickListener { + void onPhotoClick(int position); + } + + public class PhotoViewHolder extends RecyclerView.ViewHolder { + ImageView ivPhoto; + TextView tvType; + + public PhotoViewHolder(View itemView) { + super(itemView); + this.ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo); + this.tvType = (TextView) itemView.findViewById(R.id.tv_type); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java new file mode 100644 index 0000000..a8c60bf --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/PuzzleSelectorPreviewAdapter.java @@ -0,0 +1,101 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import androidx.recyclerview.widget.RecyclerView; + +import android.net.Uri; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.constant.Type; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.setting.Setting; +import com.huantansheng.easyphotos.utils.media.DurationUtils; + +import java.util.ArrayList; + +/** + * 拼图相册适配器 + * Created by huan on 2017/10/23. + */ + +public class PuzzleSelectorPreviewAdapter extends RecyclerView.Adapter { + + + private ArrayList dataList; + private LayoutInflater mInflater; + private OnClickListener listener; + + + public PuzzleSelectorPreviewAdapter(Context cxt, ArrayList dataList, OnClickListener listener) { + this.dataList = dataList; + this.listener = listener; + this.mInflater = LayoutInflater.from(cxt); + } + + @Override + public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + + return new PhotoViewHolder(mInflater.inflate(R.layout.item_puzzle_selector_preview_easy_photos, parent, false)); + + } + + @Override + public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) { + + final int p = position; + Photo photo = dataList.get(position); + String path = photo.path; + String type = photo.type; + Uri uri = photo.uri; + long duration = photo.duration; + final boolean isGif = path.endsWith(Type.GIF) || type.endsWith(Type.GIF); + if (Setting.showGif && isGif) { + Setting.imageEngine.loadGifAsBitmap(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(R.string.gif_easy_photos); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else if (Setting.showVideo && type.contains(Type.VIDEO)) { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setText(DurationUtils.format(duration)); + ((PhotoViewHolder) holder).tvType.setVisibility(View.VISIBLE); + } else { + Setting.imageEngine.loadPhoto(((PhotoViewHolder) holder).ivPhoto.getContext(), uri, ((PhotoViewHolder) holder).ivPhoto); + ((PhotoViewHolder) holder).tvType.setVisibility(View.GONE); + } + + ((PhotoViewHolder) holder).ivDelete.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + listener.onDeleteClick(p); + } + }); + } + + + @Override + public int getItemCount() { + return null == dataList ? 0 : dataList.size(); + } + + + public interface OnClickListener { + void onDeleteClick(int position); + } + + public class PhotoViewHolder extends RecyclerView.ViewHolder { + ImageView ivPhoto; + ImageView ivDelete; + TextView tvType; + + public PhotoViewHolder(View itemView) { + super(itemView); + this.ivPhoto = (ImageView) itemView.findViewById(R.id.iv_photo); + this.ivDelete = (ImageView) itemView.findViewById(R.id.iv_delete); + this.tvType = (TextView) itemView.findViewById(R.id.tv_type); + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java new file mode 100644 index 0000000..7171bd7 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/adapter/TextStickerAdapter.java @@ -0,0 +1,80 @@ +package com.huantansheng.easyphotos.ui.adapter; + +import android.content.Context; +import androidx.recyclerview.widget.RecyclerView; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.huantansheng.easyphotos.R; +import com.huantansheng.easyphotos.models.sticker.StickerModel; +import com.huantansheng.easyphotos.models.sticker.entity.TextStickerData; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author huan + */ +public class TextStickerAdapter extends RecyclerView.Adapter { + + private List datas; + private OnItemClickListener onItemClickListener; + + public TextStickerAdapter(Context cxt, OnItemClickListener listener) { + super(); + this.onItemClickListener = listener; + this.datas = new ArrayList<>(); + TextStickerData data = new TextStickerData(cxt.getString(R.string.text_sticker_hint_name_easy_photos), cxt.getString(R.string.text_sticker_hint_easy_photos)); + this.datas.add(0, data); + TextStickerData d = new TextStickerData(cxt.getString(R.string.text_sticker_date_easy_photos), "-1"); + datas.add(d); + datas.addAll(StickerModel.textDataList); + } + + @Override + public TextViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = + LayoutInflater.from(parent.getContext()).inflate(R.layout.item_text_sticker_easy_photos, parent, false); + return new TextViewHolder(itemView); + } + + @Override + public void onBindViewHolder(TextViewHolder holder, int position) { + final TextStickerData data = datas.get(position); + + holder.tvSticker.setText(data.stickerName); + + holder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (onItemClickListener != null) { + onItemClickListener.onItemClick(data.stickerValue); + notifyDataSetChanged(); + } + } + }); + + } + + @Override + public int getItemCount() { + return datas == null ? 0 : datas.size(); + } + + + public static class TextViewHolder extends RecyclerView.ViewHolder { + + TextView tvSticker; + + public TextViewHolder(View itemView) { + super(itemView); + tvSticker = (TextView) itemView.findViewById(R.id.puzzle); + } + } + + public interface OnItemClickListener { + void onItemClick(String stickerValue); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java new file mode 100644 index 0000000..3658498 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/dialog/LoadingDialog.java @@ -0,0 +1,49 @@ +package com.huantansheng.easyphotos.ui.dialog; + +import android.app.Dialog; +import android.content.Context; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import androidx.annotation.NonNull; + +import com.huantansheng.easyphotos.R; + +public class LoadingDialog extends Dialog { + + private LoadingDialog(@NonNull Context context) { + super(context); + Window window = getWindow(); + if (null != window) { + window.requestFeature(Window.FEATURE_NO_TITLE); + } + } + + public static LoadingDialog get(Context context) { + LoadingDialog loading = new LoadingDialog(context); + View view = LayoutInflater.from(context).inflate(R.layout.dialog_loading_easy_photos, null); + loading.setContentView(view); + loading.setCancelable(false); + loading.setCanceledOnTouchOutside(false); + return loading; + } + + @Override + public void show() { + Window window = getWindow(); + if (null != window) { + WindowManager.LayoutParams attributes = window.getAttributes(); + window.setBackgroundDrawableResource(R.color.transparent_easy_photos); + window.setGravity(Gravity.CENTER); + attributes.width = WindowManager.LayoutParams.MATCH_PARENT; + attributes.height = WindowManager.LayoutParams.WRAP_CONTENT; + window.setAttributes(attributes); + } + super.show(); + } + +} + diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java new file mode 100644 index 0000000..e613e53 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedImageView.java @@ -0,0 +1,47 @@ +package com.huantansheng.easyphotos.ui.widget; + +import android.content.Context; +import androidx.annotation.Nullable; +import android.util.AttributeSet; + +/** + * 自带点击效果的imageview + * Created by huan on 2017/8/15. + */ + +public class PressedImageView extends androidx.appcompat.widget.AppCompatImageView { + private float scaleSize;//按压颜色 + + public PressedImageView(Context context) { + super(context); + this.scaleSize = 0.97f; + } + + public PressedImageView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.scaleSize = 0.97f; + + } + + public PressedImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.scaleSize = 0.97f; + + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (isPressed()) { + setScaleX(this.scaleSize); + setScaleY(this.scaleSize); + } else { + setScaleX(1.0f); + setScaleY(1.0f); + } + } + + public void setScaleSize(float scaleSize) { + this.scaleSize = scaleSize; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java new file mode 100644 index 0000000..613c11c --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PressedTextView.java @@ -0,0 +1,72 @@ +package com.huantansheng.easyphotos.ui.widget; + +import android.animation.AnimatorSet; +import android.animation.ObjectAnimator; +import android.content.Context; +import androidx.annotation.Nullable; +import android.util.AttributeSet; + +/** + * 带点击状态的textview + * Created by huan on 2017/9/15. + */ + +public class PressedTextView extends androidx.appcompat.widget.AppCompatTextView { + private float pressedScale; + private AnimatorSet set; + private int pressedFlag; + + public PressedTextView(Context context) { + super(context); + this.pressedScale = 1.1f; + this.pressedFlag = 1; + } + + public PressedTextView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + this.pressedScale = 1.1f; + this.pressedFlag = 1; + } + + public PressedTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + this.pressedScale = 1.1f; + this.pressedFlag = 1; + } + + @Override + public void setPressed(boolean pressed) { + super.setPressed(pressed); + if (isPressed()) { + pressedFlag = 1; + if (null == set) { + set = new AnimatorSet(); + set.setDuration(5); + } + if (set.isRunning()) set.cancel(); + ObjectAnimator pScaleX = ObjectAnimator.ofFloat(this, "scaleX", 1.0f, pressedScale); + ObjectAnimator pScaleY = ObjectAnimator.ofFloat(this, "scaleY", 1.0f, pressedScale); + set.play(pScaleX).with(pScaleY); + set.start(); + } else { + if (pressedFlag != 1) { + return; + } + pressedFlag = 2; + if (null == set) { + set = new AnimatorSet(); + set.setDuration(5); + } + if (set.isRunning()) set.cancel(); + ObjectAnimator nScaleX = ObjectAnimator.ofFloat(this, "scaleX", pressedScale, 1.0f); + ObjectAnimator nScaleY = ObjectAnimator.ofFloat(this, "scaleY", pressedScale, 1.0f); + set.play(nScaleX).with(nScaleY); + set.start(); + } + } + + public void setPressedScale(float pressedScale) { + this.pressedScale = pressedScale; + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java new file mode 100644 index 0000000..4aab6cf --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/ui/widget/PreviewRecyclerView.java @@ -0,0 +1,59 @@ +package com.huantansheng.easyphotos.ui.widget; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.MotionEvent; + +/** + * 图片预览 RecyclerView + * Create By lishilin On 2019/3/25 + */ +public class PreviewRecyclerView extends RecyclerView { + + private boolean isLock;// 是否锁住 RecyclerView ,避免和 PhotoView 双指放大缩小操作冲突 + + public PreviewRecyclerView(@NonNull Context context) { + super(context); + } + + public PreviewRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + } + + public PreviewRecyclerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_DOWN:// 非第一个触点按下 + isLock = true; + break; + case MotionEvent.ACTION_UP:// 最后一个触点抬起 + isLock = false; + break; + } + if (isLock) { + return false;// 不拦截,交给子View处理 + } + return super.onInterceptTouchEvent(event); + } + + @Override + public boolean dispatchTouchEvent(MotionEvent event) { + switch (event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_DOWN:// 非第一个触点按下 + isLock = true; + break; + case MotionEvent.ACTION_UP:// 最后一个触点抬起 + isLock = false; + break; + } + return super.dispatchTouchEvent(event); + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/Color/ColorUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/Color/ColorUtils.java new file mode 100644 index 0000000..f709089 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/Color/ColorUtils.java @@ -0,0 +1,37 @@ +package com.huantansheng.easyphotos.utils.Color; + +/** + * Created by huan on 2018/1/9. + */ + +public class ColorUtils { + + /** + * 判断颜色是否偏黑色 + * + * @param color 颜色 + * @return + */ + public static boolean isBlackColor(int color) { + int grey = toGrey(color); + return grey < 50; + } + + /** + * 颜色转换成灰度值 + * + * @param rgb 颜色 + * @return 灰度值 + */ + public static int toGrey(int rgb) { + int blue = rgb & 0x000000FF; + int green = (rgb & 0x0000FF00) >> 8; + int red = (rgb & 0x00FF0000) >> 16; + return (red * 38 + green * 75 + blue * 15) >> 7; + } + + public static boolean isWhiteColor(int color) { + int grey = toGrey(color); + return grey > 200; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/String/StringUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/String/StringUtils.java new file mode 100644 index 0000000..8a7f8cf --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/String/StringUtils.java @@ -0,0 +1,19 @@ +package com.huantansheng.easyphotos.utils.String; + +/** + * 字符串工具类 + * Created by huan on 2017/10/20. + */ +public class StringUtils { + public static String getLastPathSegment(String content) { + if (content == null || content.length() == 0) { + return ""; + } + String[] segments = content.split("/"); + if (segments.length > 0) { + return segments[segments.length - 1]; + } + return ""; + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java new file mode 100644 index 0000000..8548a3e --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/BitmapUtils.java @@ -0,0 +1,412 @@ +package com.huantansheng.easyphotos.utils.bitmap; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.ContentValues; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Rect; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.MediaStore; +import android.text.TextPaint; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.exifinterface.media.ExifInterface; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.utils.uri.UriUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +/** + * bitmap工具类 + * Created by huan on 2017/9/4. + */ + +public class BitmapUtils { + /** + * 回收bitmap + * + * @param bitmap 回收的bitmap + */ + public static void recycle(Bitmap bitmap) { + if (null != bitmap && !bitmap.isRecycled()) { + bitmap.recycle(); + bitmap = null; + } + System.gc(); + } + + public static void recycle(Bitmap... bitmaps) { + for (Bitmap b : bitmaps) { + recycle(b); + } + } + + public static void recycle(List bitmaps) { + for (Bitmap b : bitmaps) { + recycle(b); + } + } + + /** + * 给图片添加水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印 + * @param image 添加水印的图片 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param srcWaterMarkImageWidth 水印对应的原图片宽度,即ui制作水印时候参考的图片画布宽度,应该是已知的图片最大宽度 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermark(Bitmap watermark, Bitmap image, int srcWaterMarkImageWidth, + int offsetX, int offsetY, boolean addInLeft, int orientation) { + int imageWidth = image.getWidth(); + int imageHeight = image.getHeight(); + if (0 == imageWidth || 0 == imageHeight) { + throw new RuntimeException("AlbumBuilder: 加水印的原图宽或高不能为0!"); + } + switch (orientation) { + case 90: + case 270: + image = adjustBitmapRotation(image, orientation); + int temp = imageHeight; + imageHeight = imageWidth; + imageWidth = temp; + break; + case 180: + image = adjustBitmapRotation(image, orientation); + break; + + } + int watermarkWidth = watermark.getWidth(); + int watermarkHeight = watermark.getHeight(); + float scale = imageWidth / (float) srcWaterMarkImageWidth; + if (scale > 1) scale = 1; + else if (scale < 0.4) scale = 0.4f; + int scaleWatermarkWidth = (int) (watermarkWidth * scale); + int scaleWatermarkHeight = (int) (watermarkHeight * scale); + Bitmap scaleWatermark = Bitmap.createScaledBitmap(watermark, scaleWatermarkWidth, + scaleWatermarkHeight, true); + Canvas canvas = new Canvas(image); + Paint paint = new Paint(); + paint.setAntiAlias(true); + if (addInLeft) { + canvas.drawBitmap(scaleWatermark, offsetX, + imageHeight - scaleWatermarkHeight - offsetY, paint); + } else { + canvas.drawBitmap(scaleWatermark, imageWidth - offsetX - scaleWatermarkWidth, + imageHeight - scaleWatermarkHeight - offsetY, paint); + } + recycle(scaleWatermark); + return image; + } + + /** + * 给图片添加带文字和图片的水印,水印会根据图片宽高自动缩放处理 + * + * @param watermark 水印图片 + * @param image 要加水印的图片 + * @param srcWaterMarkImageWidth 水印对应的原图片宽度,即ui制作水印时候参考的图片画布宽度,应该是已知的图片最大宽度 + * @param text 要添加的文字 + * @param offsetX 添加水印的X轴偏移量 + * @param offsetY 添加水印的Y轴偏移量 + * @param addInLeft true 在左下角添加水印,false 在右下角添加水印 + * @param orientation Bitmap的旋转角度。当useWidth为true时,Photo实体类中会有orientation,若bitmap + * 不是用户手机内图片,填0即可。 + * @return 添加水印后的bitmap + */ + public static Bitmap addWatermarkWithText(@NonNull Bitmap watermark, Bitmap image, + int srcWaterMarkImageWidth, @NonNull String text, + int offsetX, int offsetY, boolean addInLeft, + int orientation) { + float imageWidth = image.getWidth(); + float imageHeight = image.getHeight(); + if (0 == imageWidth || 0 == imageHeight) { + throw new RuntimeException("AlbumBuilder: 加水印的原图宽或高不能为0!"); + } + switch (orientation) { + case 90: + case 270: + image = adjustBitmapRotation(image, orientation); + float temp = imageHeight; + imageHeight = imageWidth; + imageWidth = temp; + break; + case 180: + image = adjustBitmapRotation(image, orientation); + break; + + } + float watermarkWidth = watermark.getWidth(); + float watermarkHeight = watermark.getHeight(); + float scale = imageWidth / (float) srcWaterMarkImageWidth; + if (scale > 1) scale = 1; + else if (scale < 0.4) scale = 0.4f; + float scaleWatermarkWidth = watermarkWidth * scale; + float scaleWatermarkHeight = watermarkHeight * scale; + Bitmap scaleWatermark = Bitmap.createScaledBitmap(watermark, (int) scaleWatermarkWidth, + (int) scaleWatermarkHeight, true); + Canvas canvas = new Canvas(image); + + Paint textPaint = new TextPaint(); + textPaint.setAntiAlias(true); + textPaint.setColor(Color.WHITE); + float textsize = (float) (scaleWatermark.getHeight() * 2) / (float) 3; + textPaint.setTextSize(textsize); + Rect textRect = new Rect(); + textPaint.getTextBounds(text, 0, text.length(), textRect); + if (addInLeft) { + canvas.drawText(text, scaleWatermarkWidth + offsetX, + imageHeight - textRect.height() - textRect.top - offsetY, textPaint); + } else { + canvas.drawText(text, imageWidth - offsetX - textRect.width() - textRect.left, + imageHeight - textRect.height() - textRect.top - offsetY, textPaint); + } + + Paint sacleWatermarkPaint = new Paint(); + sacleWatermarkPaint.setAntiAlias(true); + if (addInLeft) { + canvas.drawBitmap(scaleWatermark, offsetX, + imageHeight - textRect.height() - offsetY - scaleWatermarkHeight / 6, + sacleWatermarkPaint); + } else { + canvas.drawBitmap(scaleWatermark, + imageWidth - textRect.width() - offsetX - scaleWatermarkWidth / 6, + imageHeight - textRect.height() - offsetY - scaleWatermarkHeight / 6, + sacleWatermarkPaint); + } + recycle(scaleWatermark); + return image; + } + + + /** + * 保存Bitmap到指定文件夹 + * + * @param act 上下文 + * @param dirPath 文件夹全路径 + * @param bitmap bitmap + * @param namePrefix 保存文件的前缀名,文件最终名称格式为:前缀名+自动生成的唯一数字字符+.png + * @param notifyMedia 是否更新到媒体库 + * @param callBack 保存图片后的回调,回调已经处于UI线程 + */ + public static void saveBitmapToDir(final Activity act, final String dirPath, + final String namePrefix, final Bitmap bitmap, + final boolean notifyMedia, + final SaveBitmapCallBack callBack) { + new Thread(new Runnable() { + @Override + public void run() { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { + //android10+ + saveBitmapToDirQ(act, dirPath, namePrefix, bitmap, notifyMedia, callBack); + return; + } + + File dirF = new File(dirPath); + if (!dirF.exists() || !dirF.isDirectory()) { + if (!dirF.mkdirs()) { + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onCreateDirFailed(); + } + }); + return; + } + } + + try { + final File writeFile = File.createTempFile(namePrefix, ".png", dirF); + + FileOutputStream fos = null; + fos = new FileOutputStream(writeFile); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos); + fos.flush(); + fos.close(); + if (notifyMedia) { + EasyPhotos.notifyMedia(act, writeFile); + } + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onSuccess(writeFile); + } + }); + + } catch (final IOException e) { + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onIOFailed(e); + } + }); + + } + } + }).start(); + + } + + private static void saveBitmapToDirQ(final Activity act, final String dirPath, + final String namePrefix, final Bitmap bitmap, + final boolean notifyMedia, + final SaveBitmapCallBack callBack) { + long dataTake = System.currentTimeMillis(); + String jpegName = namePrefix + dataTake + ".png"; + + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.DISPLAY_NAME, jpegName); + values.put(MediaStore.Images.Media.MIME_TYPE, "image/png"); + int dirIndex = dirPath.lastIndexOf("/"); + if (dirIndex == dirPath.length()) { + String dirPath2 = dirPath.substring(0, dirIndex - 1); + dirIndex = dirPath2.lastIndexOf("/"); + } + String dirName = dirPath.substring(dirIndex + 1); + values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/" + dirName); + + Uri external; + ContentResolver resolver = act.getContentResolver(); + String status = Environment.getExternalStorageState(); + // 判断是否有SD卡,优先使用SD卡存储,当没有SD卡时使用手机存储 + if (status.equals(Environment.MEDIA_MOUNTED)) { + external = MediaStore.Images.Media.getContentUri("external"); + } else { + external = MediaStore.Images.Media.INTERNAL_CONTENT_URI; + } + + final Uri insertUri = resolver.insert(external, values); + if (insertUri == null) { + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onCreateDirFailed(); + } + }); + return; + } + OutputStream os; + try { + os = resolver.openOutputStream(insertUri); + bitmap.compress(Bitmap.CompressFormat.JPEG, 100, os); + if (os != null) { + os.flush(); + os.close(); + } + act.runOnUiThread(new Runnable() { + @Override + public void run() { + String uriPath = UriUtils.getPathByUri(act, insertUri); + if (null == uriPath) { + callBack.onCreateDirFailed(); + } else { + callBack.onSuccess(new File(uriPath)); + } + } + }); + } catch (final IOException e) { + e.printStackTrace(); + act.runOnUiThread(new Runnable() { + @Override + public void run() { + callBack.onIOFailed(e); + } + }); + } + } + + + /** + * 把View画成Bitmap + * + * @param view 要处理的View + * @return Bitmap + */ + public static Bitmap createBitmapFromView(View view) { + Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), + Bitmap.Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + view.draw(canvas); + return bitmap; + } + + /** + * 计算获取的照片的宽高是否需要交换,如果图片是旋转了90度或270度的,那么就需要交换 + * + * @param photo 需要计算的图片 + * @return 宽高是否需要交换 + */ + public static Boolean needChangeWidthAndHeight(Photo photo) throws IOException { + + ExifInterface exifInterface = new ExifInterface(photo.path); + int exifOrientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, + ExifInterface.ORIENTATION_NORMAL); + + // 如果拿到的照片是旋转了90度或270度的,意味着通过MediaStore获取的宽高需要交换 + return exifOrientation == ExifInterface.ORIENTATION_ROTATE_90 || exifOrientation == ExifInterface.ORIENTATION_ROTATE_270; + + } + + /** + * 通过BitmapFactory.Options重设图片的宽高,在通过MediaStore获取的图片宽高为0时使用 + * + * @param photo 需要计算的图片 + */ + public static void calculateLocalImageSizeThroughBitmapOptions(Photo photo) throws IOException { + + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(photo.path, options); + + photo.width = options.outWidth; + photo.height = options.outHeight; + } + + public static Bitmap adjustBitmapRotation(Bitmap bm, final int orientationDegree) { + + Matrix m = new Matrix(); + m.setRotate(orientationDegree, (float) bm.getWidth() / 2, (float) bm.getHeight() / 2); + float targetX, targetY; + if (orientationDegree == 90) { + targetX = bm.getHeight(); + targetY = 0; + } else { + targetX = bm.getHeight(); + targetY = bm.getWidth(); + } + + final float[] values = new float[9]; + m.getValues(values); + + float x1 = values[Matrix.MTRANS_X]; + float y1 = values[Matrix.MTRANS_Y]; + + m.postTranslate(targetX - x1, targetY - y1); + + Bitmap bm1 = Bitmap.createBitmap(bm.getHeight(), bm.getWidth(), Bitmap.Config.ARGB_8888); + + Paint paint = new Paint(); + Canvas canvas = new Canvas(bm1); + canvas.drawBitmap(bm, m, paint); + + + return bm1; + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java new file mode 100644 index 0000000..9d01f40 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/bitmap/SaveBitmapCallBack.java @@ -0,0 +1,17 @@ +package com.huantansheng.easyphotos.utils.bitmap; + +import java.io.File; +import java.io.IOException; + +/** + * 保存图片到本地的回调 + * Created by huan on 2017/12/6. + */ + +public interface SaveBitmapCallBack { + void onSuccess(File file); + + void onIOFailed(IOException exception); + + void onCreateDirFailed(); +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/file/FileUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/file/FileUtils.java new file mode 100644 index 0000000..88c2303 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/file/FileUtils.java @@ -0,0 +1,633 @@ +package com.huantansheng.easyphotos.utils.file; + + +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.database.DatabaseUtils; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.provider.OpenableColumns; +import android.util.Log; +import android.webkit.MimeTypeMap; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.FileProvider; + +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.text.DecimalFormat; + + +public class FileUtils { + public static final String DOCUMENTS_DIR = "documents"; + // configured android:authorities in AndroidManifest (https://developer.android + // .com/reference/android/support/v4/content/FileProvider) + public static final String HIDDEN_PREFIX = "."; + /** + * TAG for log messages. + */ + static final String TAG = "FileUtils"; + private static final boolean DEBUG = false; // Set to true to enable logging + /** + * File and folder comparator. TODO Expose sorting option method + */ + + /** + * Folder (directories) filter. + */ + + + private FileUtils() { + } //private constructor to enforce Singleton pattern + + /** + * Gets the extension of a file name, like ".png" or ".jpg". + * + * @param uri + * @return Extension including the dot("."); "" if there is no extension; + * null if uri was null. + */ + public static String getExtension(String uri) { + if (uri == null) { + return null; + } + + int dot = uri.lastIndexOf("."); + if (dot >= 0) { + return uri.substring(dot); + } else { + // No extension. + return ""; + } + } + + /** + * @return Whether the URI is a local one. + */ + public static boolean isLocal(String url) { + return url != null && !url.startsWith("http://") && !url.startsWith("https://"); + } + + /** + * @return True if Uri is a MediaStore Uri. + * @author paulburke + */ + public static boolean isMediaUri(Uri uri) { + return "media".equalsIgnoreCase(uri.getAuthority()); + } + + /** + * Convert File into Uri. + * + * @param file + * @return uri + */ + public static Uri getUri(File file) { + return (file != null) ? Uri.fromFile(file) : null; + } + + /** + * Returns the path only (without file name). + * + * @param file + * @return + */ + public static File getPathWithoutFilename(File file) { + if (file != null) { + if (file.isDirectory()) { + // no file to be split off. Return everything + return file; + } else { + String filename = file.getName(); + String filepath = file.getAbsolutePath(); + + // Construct path without file name. + String pathwithoutname = filepath.substring(0, + filepath.length() - filename.length()); + if (pathwithoutname.endsWith("/")) { + pathwithoutname = pathwithoutname.substring(0, pathwithoutname.length() - 1); + } + return new File(pathwithoutname); + } + } + return null; + } + + /** + * @return The MIME type for the given file. + */ + public static String getMimeType(File file) { + + String extension = getExtension(file.getName()); + + if (extension.length() > 0) + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1)); + + return "application/octet-stream"; + } + + /** + * @return The MIME type for the give Uri. + */ + public static String getMimeType(Context context, Uri uri) { + File file = new File(getPath(context, uri)); + return getMimeType(file); + } + + /** + * @return The MIME type for the give String Uri. + */ + public static String getMimeType(Context context, String url) { + String type = context.getContentResolver().getType(Uri.parse(url)); + if (type == null) { + type = "application/octet-stream"; + } + return type; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is local. + */ + public static boolean isLocalStorageDocument(Context cxt, Uri uri) { + return (cxt.getApplicationContext().getPackageName() + ".fileprovider").equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is Google Photos. + */ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = MediaStore.Files.FileColumns.DATA; + final String[] projection = {column}; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs + , null); + if (cursor != null && cursor.moveToFirst()) { + if (DEBUG) DatabaseUtils.dumpCursor(cursor); + + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + if (cursor != null) cursor.close(); + } + return null; + } + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders.
+ *
+ * Callers should check whether the path is local before assuming it + * represents a local file. + * + * @param context The context. + * @param uri The Uri to query. + * @see #getFile(Context, Uri) + */ + public static String getPath(final Context context, final Uri uri) { + String absolutePath = getLocalPath(context, uri); + return absolutePath != null ? absolutePath : uri.toString(); + } + + private static String getLocalPath(final Context context, final Uri uri) { + + if (DEBUG) + Log.d(TAG + " File -", + "Authority: " + uri.getAuthority() + ", Fragment: " + uri.getFragment() + ", " + + "Port: " + uri.getPort() + ", Query: " + uri.getQuery() + ", Scheme" + + ": " + uri.getScheme() + ", Host: " + uri.getHost() + ", Segments: " + + uri.getPathSegments().toString()); + + + // DocumentProvider + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) { + // LocalStorageProvider + if (isLocalStorageDocument(context, uri)) { + // The path is the id + return DocumentsContract.getDocumentId(uri); + } + // ExternalStorageProvider + else if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else if ("home".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/documents/" + split[1]; + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + + if (id != null && id.startsWith("raw:")) { + return id.substring(4); + } + + String[] contentUriPrefixesToTry = new String[]{"content://downloads" + + "/public_downloads", "content://downloads/my_downloads"}; + + for (String contentUriPrefix : contentUriPrefixesToTry) { + Uri contentUri = ContentUris.withAppendedId(Uri.parse(contentUriPrefix), + Long.valueOf(id)); + try { + String path = getDataColumn(context, contentUri, null, null); + if (path != null) { + return path; + } + } catch (Exception e) { + } + } + + // path could not be retrieved using ContentResolver, therefore copy file to + // accessible cache using streams + String fileName = getFileName(context, uri); + File cacheDir = getDocumentCacheDir(context); + File file = generateFileName(fileName, cacheDir); + String destinationPath = null; + if (file != null) { + destinationPath = file.getAbsolutePath(); + saveFileFromUri(context, uri, destinationPath); + } + + return destinationPath; + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.getContentUri("external"); + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Convert Uri into File, if possible. + * + * @return file A local file that the Uri was pointing to, or null if the + * Uri is unsupported or pointed to a remote resource. + * @author paulburke + * @see #getPath(Context, Uri) + */ + public static File getFile(Context context, Uri uri) { + if (uri != null) { + String path = getPath(context, uri); + if (path != null && isLocal(path)) { + return new File(path); + } + } + return null; + } + + /** + * Get the file size in a human-readable string. + * + * @param size + * @return + * @author paulburke + */ + public static String getReadableFileSize(int size) { + final int BYTES_IN_KILOBYTES = 1024; + final DecimalFormat dec = new DecimalFormat("###.#"); + final String KILOBYTES = " KB"; + final String MEGABYTES = " MB"; + final String GIGABYTES = " GB"; + float fileSize = 0; + String suffix = KILOBYTES; + + if (size > BYTES_IN_KILOBYTES) { + fileSize = size / BYTES_IN_KILOBYTES; + if (fileSize > BYTES_IN_KILOBYTES) { + fileSize = fileSize / BYTES_IN_KILOBYTES; + if (fileSize > BYTES_IN_KILOBYTES) { + fileSize = fileSize / BYTES_IN_KILOBYTES; + suffix = GIGABYTES; + } else { + suffix = MEGABYTES; + } + } + } + return String.valueOf(dec.format(fileSize) + suffix); + } + + /** + * Get the Intent for selecting content to be used in an Intent Chooser. + * + * @return The intent for opening a file with Intent.createChooser() + */ + public static Intent createGetContentIntent() { + // Implicitly allow the user to select a particular kind of data + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + // The MIME data type filter + intent.setType("*/*"); + // Only return URIs that can be opened with ContentResolver + intent.addCategory(Intent.CATEGORY_OPENABLE); + return intent; + } + + + /** + * Creates View intent for given file + * + * @param file + * @return The intent for viewing file + */ + public static Intent getViewIntent(Context context, File file) { + //Uri uri = Uri.fromFile(file); + Uri uri = FileProvider.getUriForFile(context, + (context.getApplicationContext().getPackageName() + ".fileprovider"), file); + Intent intent = new Intent(Intent.ACTION_VIEW); + String url = file.toString(); + if (url.contains(".doc") || url.contains(".docx")) { + // Word document + intent.setDataAndType(uri, "application/msword"); + } else if (url.contains(".pdf")) { + // PDF file + intent.setDataAndType(uri, "application/pdf"); + } else if (url.contains(".ppt") || url.contains(".pptx")) { + // Powerpoint file + intent.setDataAndType(uri, "application/vnd.ms-powerpoint"); + } else if (url.contains(".xls") || url.contains(".xlsx")) { + // Excel file + intent.setDataAndType(uri, "application/vnd.ms-excel"); + } else if (url.contains(".zip") || url.contains(".rar")) { + // WAV audio file + intent.setDataAndType(uri, "application/x-wav"); + } else if (url.contains(".rtf")) { + // RTF file + intent.setDataAndType(uri, "application/rtf"); + } else if (url.contains(".wav") || url.contains(".mp3")) { + // WAV audio file + intent.setDataAndType(uri, "audio/x-wav"); + } else if (url.contains(".gif")) { + // GIF file + intent.setDataAndType(uri, "image/gif"); + } else if (url.contains(".jpg") || url.contains(".jpeg") || url.contains(".png")) { + // JPG file + intent.setDataAndType(uri, "image/jpeg"); + } else if (url.contains(".txt")) { + // Text file + intent.setDataAndType(uri, "text/plain"); + } else if (url.contains(".3gp") || url.contains(".mpg") || url.contains(".mpeg") || url.contains(".mpe") || url.contains(".mp4") || url.contains(".avi")) { + // Video files + intent.setDataAndType(uri, "video/*"); + } else { + intent.setDataAndType(uri, "*/*"); + } + + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + return intent; + } + + public static File getDownloadsDir() { + return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS); + } + + public static File getDocumentCacheDir(@NonNull Context context) { + File dir = new File(context.getCacheDir(), DOCUMENTS_DIR); + if (!dir.exists()) { + dir.mkdirs(); + } + logDir(context.getCacheDir()); + logDir(dir); + + return dir; + } + + private static void logDir(File dir) { + if (!DEBUG) return; + Log.d(TAG, "Dir=" + dir); + File[] files = dir.listFiles(); + for (File file : files) { + Log.d(TAG, "File=" + file.getPath()); + } + } + + @Nullable + public static File generateFileName(@Nullable String name, File directory) { + if (name == null) { + return null; + } + + File file = new File(directory, name); + + if (file.exists()) { + String fileName = name; + String extension = ""; + int dotIndex = name.lastIndexOf('.'); + if (dotIndex > 0) { + fileName = name.substring(0, dotIndex); + extension = name.substring(dotIndex); + } + + int index = 0; + + while (file.exists()) { + index++; + name = fileName + '(' + index + ')' + extension; + file = new File(directory, name); + } + } + + try { + if (!file.createNewFile()) { + return null; + } + } catch (IOException e) { + Log.w(TAG, e); + return null; + } + + logDir(directory); + + return file; + } + + + private static void saveFileFromUri(Context context, Uri uri, String destinationPath) { + InputStream is = null; + BufferedOutputStream bos = null; + try { + is = context.getContentResolver().openInputStream(uri); + bos = new BufferedOutputStream(new FileOutputStream(destinationPath, false)); + byte[] buf = new byte[1024]; + is.read(buf); + do { + bos.write(buf); + } while (is.read(buf) != -1); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) is.close(); + if (bos != null) bos.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static byte[] readBytesFromFile(String filePath) { + + FileInputStream fileInputStream = null; + byte[] bytesArray = null; + + try { + + File file = new File(filePath); + bytesArray = new byte[(int) file.length()]; + + //read file into bytes[] + fileInputStream = new FileInputStream(file); + fileInputStream.read(bytesArray); + + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (fileInputStream != null) { + try { + fileInputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + } + + return bytesArray; + + } + + public static File createTempImageFile(Context context, String fileName) throws IOException { + // Create an image file name + File storageDir = new File(context.getCacheDir(), DOCUMENTS_DIR); + return File.createTempFile(fileName, ".jpg", storageDir); + } + + public static String getFileName(@NonNull Context context, Uri uri) { + String mimeType = context.getContentResolver().getType(uri); + String filename = null; + + if (mimeType == null && context != null) { + String path = getPath(context, uri); + if (path == null) { + filename = getName(uri.toString()); + } else { + File file = new File(path); + filename = file.getName(); + } + } else { + Cursor returnCursor = context.getContentResolver().query(uri, null, null, null, null); + if (returnCursor != null) { + int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME); + returnCursor.moveToFirst(); + filename = returnCursor.getString(nameIndex); + returnCursor.close(); + } + } + + return filename; + } + + public static String getName(String filename) { + if (filename == null) { + return null; + } + int index = filename.lastIndexOf('/'); + return filename.substring(index + 1); + } +} + + diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java new file mode 100644 index 0000000..874a669 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/media/DurationUtils.java @@ -0,0 +1,54 @@ +package com.huantansheng.easyphotos.utils.media; + +import android.media.MediaMetadataRetriever; +import android.text.format.DateUtils; + +import java.io.IOException; + +/** + * DurationUtils + * Create By lishilin On 2019/3/25 + */ +public class DurationUtils { + + /** + * 获取时长 + * + * @param path path + * @return duration + */ + public static long getDuration(String path) { + MediaMetadataRetriever mmr = null; + try { + mmr = new MediaMetadataRetriever(); + mmr.setDataSource(path); + return Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)); + } catch (Exception e) { +// e.printStackTrace(); + } finally { + if (mmr != null) { + try { + mmr.release(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + return 0; + } + + /** + * 格式化时长(不足一秒则显示为一秒) + * + * @param duration duration + * @return "MM:SS" or "H:MM:SS" + */ + public static String format(long duration) { + long seconds = duration / 1000; + if (seconds == 0) { + seconds++; + } + return DateUtils.formatElapsedTime(seconds); + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java new file mode 100644 index 0000000..6dc0cfe --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/media/MediaScannerConnectionUtils.java @@ -0,0 +1,36 @@ +package com.huantansheng.easyphotos.utils.media; + +import android.content.Context; +import android.media.MediaScannerConnection; + +import java.io.File; +import java.util.List; + +/** + * 更新媒体库 + * Created by huan on 2017/8/1. + */ + +public class MediaScannerConnectionUtils { + + public static void refresh(Context cxt, String... filePaths) { + MediaScannerConnection.scanFile(cxt.getApplicationContext(), + filePaths, null, + null); + } + + + public static void refresh(Context cxt, File... files) { + for (File file : files) { + String filePath = file.getAbsolutePath(); + refresh(cxt, filePath); + } + } + + public static void refresh(Context cxt, List filePathList) { + for (String filePath : filePathList) { + refresh(cxt, filePath); + } + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java new file mode 100644 index 0000000..144f3e3 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/permission/PermissionUtil.java @@ -0,0 +1,90 @@ +package com.huantansheng.easyphotos.utils.permission; + +import android.app.Activity; +import android.content.pm.PackageManager; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; +import androidx.core.content.PermissionChecker; + +import com.huantansheng.easyphotos.constant.Code; + +import java.util.ArrayList; +import java.util.List; + +/** + * 运行时权限工具类 + * Created by huan on 2017/7/27. + */ + +public class PermissionUtil { + + + public interface PermissionCallBack { + void onSuccess(); + + void onShouldShow(); + + void onFailed(); + } + + public static boolean checkAndRequestPermissionsInActivity(Activity cxt, + String... checkPermissions) { + boolean isHas = true; + List permissions = new ArrayList<>(); + int size = checkPermissions.length; + for (int i = 0; i < size; i++) { + String checkPermission = checkPermissions[i]; + if (PermissionChecker.checkSelfPermission(cxt, checkPermission) != PermissionChecker.PERMISSION_GRANTED) { + isHas = false; + permissions.add(checkPermission); + } + } + if (!isHas) { + String[] p = permissions.toArray(new String[permissions.size()]); + requestPermissionsInActivity(cxt, Code.REQUEST_PERMISSION, p); + } + return isHas; + } + + private static void requestPermissionsInActivity(Activity cxt, int requestCode, + String... permissions) { + ActivityCompat.requestPermissions(cxt, permissions, requestCode); + } + + public static void onPermissionResult(Activity cxt, @NonNull String[] permissions, + @NonNull int[] grantResults, + PermissionCallBack listener) { + int length = grantResults.length; + List positions = new ArrayList<>(); + if (length > 0) { + for (int i = 0; i < length; i++) { + if (grantResults[i] != PackageManager.PERMISSION_GRANTED) { + positions.add(i); + } + } + } + if (positions.size() == 0) { + listener.onSuccess(); + return; + } + progressNoPermission(cxt, listener, permissions, positions, 0); + + } + + private static void progressNoPermission(Activity cxt, PermissionCallBack listener, + String[] permissions, List positions, int i) { + int index = positions.get(i); + if (ActivityCompat.shouldShowRequestPermissionRationale(cxt, permissions[index])) { + listener.onShouldShow(); + return; + } + if (i < positions.size() - 1) { + i++; + progressNoPermission(cxt, listener, permissions, positions, i); + return; + } + listener.onFailed(); + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java new file mode 100644 index 0000000..a17de5d --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/result/EasyResult.java @@ -0,0 +1,45 @@ +package com.huantansheng.easyphotos.utils.result; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +/** + * EasyResult + * + * @author joker + * @date 2019/4/9. + */ +public class EasyResult { + private static final String TAG = "com.huantansheng.easyphotos"; + + private EasyResult() { + + } + + public static HolderFragment get(FragmentActivity activity) { + return new EasyResult().getHolderFragment(activity.getSupportFragmentManager()); + } + + public static HolderFragment get(Fragment fragment) { + return new EasyResult().getHolderFragment(fragment.getChildFragmentManager()); + } + + private HolderFragment getHolderFragment(FragmentManager fragmentManager) { + HolderFragment holderFragment = findHolderFragment(fragmentManager); + if (holderFragment == null) { + holderFragment = new HolderFragment(); + fragmentManager + .beginTransaction() + .add(holderFragment, TAG) + .commitAllowingStateLoss(); + fragmentManager.executePendingTransactions(); + } + return holderFragment; + } + + private HolderFragment findHolderFragment(FragmentManager fragmentManager) { + return (HolderFragment) fragmentManager.findFragmentByTag(TAG); + } + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java new file mode 100644 index 0000000..ebad4df --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/result/HolderFragment.java @@ -0,0 +1,86 @@ +package com.huantansheng.easyphotos.utils.result; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.huantansheng.easyphotos.EasyPhotos; +import com.huantansheng.easyphotos.callback.PuzzleCallback; +import com.huantansheng.easyphotos.callback.SelectCallback; +import com.huantansheng.easyphotos.engine.ImageEngine; +import com.huantansheng.easyphotos.models.album.entity.Photo; +import com.huantansheng.easyphotos.ui.EasyPhotosActivity; +import com.huantansheng.easyphotos.ui.PuzzleActivity; + +import java.util.ArrayList; + +/** + * HolderFragment + * + * @author joker + * @date 2019/4/9. + */ +public class HolderFragment extends Fragment { + + private static final int HOLDER_SELECT_REQUEST_CODE = 0x44; + private static final int HOLDER_PUZZLE_REQUEST_CODE = 0x55; + private SelectCallback mSelectCallback; + private PuzzleCallback mPuzzleCallback; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setRetainInstance(true); + } + + public void startEasyPhoto(SelectCallback callback) { + mSelectCallback = callback; + EasyPhotosActivity.start(this, HOLDER_SELECT_REQUEST_CODE); + } + + public void startPuzzleWithPhotos(ArrayList photos, String puzzleSaveDirPath, String puzzleSaveNamePrefix, boolean replaceCustom, @NonNull ImageEngine imageEngine, PuzzleCallback callback) { + mPuzzleCallback = callback; + PuzzleActivity.startWithPhotos(this, photos, puzzleSaveDirPath, puzzleSaveNamePrefix, HOLDER_PUZZLE_REQUEST_CODE, replaceCustom, imageEngine); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (Activity.RESULT_OK == resultCode) { + switch (requestCode) { + case HOLDER_SELECT_REQUEST_CODE: + if (mSelectCallback != null) { + ArrayList resultPhotos = data.getParcelableArrayListExtra(EasyPhotos.RESULT_PHOTOS); + boolean selectedOriginal = data.getBooleanExtra(EasyPhotos.RESULT_SELECTED_ORIGINAL, false); + mSelectCallback.onResult(resultPhotos, selectedOriginal); + } + break; + case HOLDER_PUZZLE_REQUEST_CODE: + if (mPuzzleCallback != null) { + Photo puzzlePhoto = data.getParcelableExtra(EasyPhotos.RESULT_PHOTOS); + mPuzzleCallback.onResult(puzzlePhoto); + } + break; + } + return; + } + if (Activity.RESULT_CANCELED == resultCode) { + switch (requestCode) { + case HOLDER_SELECT_REQUEST_CODE: + if (mSelectCallback != null) { + mSelectCallback.onCancel(); + } + break; + case HOLDER_PUZZLE_REQUEST_CODE: + if (mPuzzleCallback != null) { + mPuzzleCallback.onCancel(); + } + break; + } + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java new file mode 100644 index 0000000..5508491 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/settings/SettingsUtils.java @@ -0,0 +1,27 @@ +package com.huantansheng.easyphotos.utils.settings; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; + +import com.huantansheng.easyphotos.constant.Code; + +/** + * 系统设置界面启动器 + * Created by huan on 2017/8/7. + */ + +public class SettingsUtils { + + /** + * 启动应用详情界面 + * @param cxt 上下文 + * @param packageName 应用包名 + */ + public static void startMyApplicationDetailsForResult(Activity cxt, String packageName) { + Uri packageUri = Uri.parse("package:" + packageName); + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageUri); + cxt.startActivityForResult(intent, Code.REQUEST_SETTING_APP_DETAILS); + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java new file mode 100644 index 0000000..d6c9271 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/system/MeiZuStatusUtils.java @@ -0,0 +1,229 @@ +package com.huantansheng.easyphotos.utils.system; + +import android.app.Activity; +import android.os.Build; +import android.view.View; +import android.view.Window; +import android.view.WindowManager; + +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +/** + * Created by wangchende on 15-9-7. + */ +public class MeiZuStatusUtils { + private static Method mSetStatusBarColorIcon; + private static Method mSetStatusBarDarkIcon; + private static Field mStatusBarColorFiled; + private static int SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = 0; + + static { + try { + mSetStatusBarColorIcon = Activity.class.getMethod("setStatusBarDarkIcon", int.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + try { + mSetStatusBarDarkIcon = Activity.class.getMethod("setStatusBarDarkIcon", boolean.class); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } + try { + mStatusBarColorFiled = WindowManager.LayoutParams.class.getField("statusBarColor"); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } + try { + Field field = View.class.getField("SYSTEM_UI_FLAG_LIGHT_STATUS_BAR"); + SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = field.getInt(null); + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + + /** + * 判断颜色是否偏黑色 + * + * @param color 颜色 + * @param level 级别 + * @return + */ + public static boolean isBlackColor(int color, int level) { + int grey = toGrey(color); + return grey < level; + } + + /** + * 颜色转换成灰度值 + * + * @param rgb 颜色 + * @return 灰度值 + */ + public static int toGrey(int rgb) { + int blue = rgb & 0x000000FF; + int green = (rgb & 0x0000FF00) >> 8; + int red = (rgb & 0x00FF0000) >> 16; + return (red * 38 + green * 75 + blue * 15) >> 7; + } + + /** + * 设置状态栏字体图标颜色 + * + * @param activity 当前activity + * @param color 颜色 + */ + public static void setStatusBarDarkIcon(Activity activity, int color) { + if (mSetStatusBarColorIcon != null) { + try { + mSetStatusBarColorIcon.invoke(activity, color); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } else { + boolean whiteColor = isBlackColor(color, 50); + if (mStatusBarColorFiled != null) { + setStatusBarDarkIcon(activity, whiteColor, whiteColor); + setStatusBarDarkIcon(activity.getWindow(), color); + } else { + setStatusBarDarkIcon(activity, whiteColor); + } + } + } + + /** + * 设置状态栏字体图标颜色(只限全屏非activity情况) + * + * @param window 当前窗口 + * @param color 颜色 + */ + public static void setStatusBarDarkIcon(Window window, int color) { + try { + setStatusBarColor(window, color); + if (Build.VERSION.SDK_INT > 22) { + setStatusBarDarkIcon(window.getDecorView(), true); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 设置状态栏字体图标颜色 + * + * @param activity 当前activity + * @param dark 是否深色 true为深色 false 为白色 + */ + public static void setStatusBarDarkIcon(Activity activity, boolean dark) { + setStatusBarDarkIcon(activity, dark, true); + } + + private static boolean changeMeizuFlag(WindowManager.LayoutParams winParams, String flagName, boolean on) { + try { + Field f = winParams.getClass().getDeclaredField(flagName); + f.setAccessible(true); + int bits = f.getInt(winParams); + Field f2 = winParams.getClass().getDeclaredField("meizuFlags"); + f2.setAccessible(true); + int meizuFlags = f2.getInt(winParams); + int oldFlags = meizuFlags; + if (on) { + meizuFlags |= bits; + } else { + meizuFlags &= ~bits; + } + if (oldFlags != meizuFlags) { + f2.setInt(winParams, meizuFlags); + return true; + } + } catch (NoSuchFieldException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (Throwable e) { + e.printStackTrace(); + } + return false; + } + + /** + * 设置状态栏颜色 + * + * @param view + * @param dark + */ + private static void setStatusBarDarkIcon(View view, boolean dark) { + int oldVis = view.getSystemUiVisibility(); + int newVis = oldVis; + if (dark) { + newVis |= SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + newVis &= ~SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } + if (newVis != oldVis) { + view.setSystemUiVisibility(newVis); + } + } + + /** + * 设置状态栏颜色 + * + * @param window + * @param color + */ + private static void setStatusBarColor(Window window, int color) { + WindowManager.LayoutParams winParams = window.getAttributes(); + if (mStatusBarColorFiled != null) { + try { + int oldColor = mStatusBarColorFiled.getInt(winParams); + if (oldColor != color) { + mStatusBarColorFiled.set(winParams, color); + window.setAttributes(winParams); + } + } catch (IllegalAccessException e) { + e.printStackTrace(); + } + } + } + + /** + * 设置状态栏字体图标颜色(只限全屏非activity情况) + * + * @param window 当前窗口 + * @param dark 是否深色 true为深色 false 为白色 + */ + public static void setStatusBarDarkIcon(Window window, boolean dark) { + if (Build.VERSION.SDK_INT < 23) { + changeMeizuFlag(window.getAttributes(), "MEIZU_FLAG_DARK_STATUS_BAR_ICON", dark); + } else { + View decorView = window.getDecorView(); + if (decorView != null) { + setStatusBarDarkIcon(decorView, dark); + setStatusBarColor(window, 0); + } + } + } + + private static void setStatusBarDarkIcon(Activity activity, boolean dark, boolean flag) { + if (mSetStatusBarDarkIcon != null) { + try { + mSetStatusBarDarkIcon.invoke(activity, dark); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + } else { + if (flag) { + setStatusBarDarkIcon(activity.getWindow(), dark); + } + } + } +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java new file mode 100644 index 0000000..79887a4 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/system/SystemUtils.java @@ -0,0 +1,272 @@ +package com.huantansheng.easyphotos.utils.system; + +import android.app.Activity; +import android.content.Context; +import android.os.Build; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.view.Display; +import android.view.KeyCharacterMap; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.Window; +import android.view.WindowManager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +/** + * 系统工具类 + * Created by huan on 2017/11/13. + */ + +public class SystemUtils { + private static SystemUtils instance = null; + private Boolean hasNavigation = null;//是否有导航栏 + + /** + * 私有构造方法 + */ + private SystemUtils() { + } + + /** + * 获取单例 + * + * @return 单例 + */ + public static SystemUtils getInstance() { + if (null == instance) { + synchronized (SystemUtils.class) { + if (null == instance) { + instance = new SystemUtils(); + } + } + } + return instance; + } + + /** + * 是否有导航栏 + * + * @return 有或没有 + */ + public boolean hasNavigationBar(Activity activity) { + if (null == hasNavigation) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + int windowHeight = activity.getResources().getDisplayMetrics().heightPixels; + DisplayMetrics dm = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getRealMetrics(dm); + int screenHeight = dm.heightPixels; + hasNavigation = screenHeight - windowHeight > 0; + } else { + hasNavigation = true; + } + } + + return hasNavigation; + } + + /** + * 全屏显示的初始化,在setContentView()方法前调用 + * + * @param decorView getWindow().getDecorView(),不同view也可以 + */ + public void systemUiInit(Activity activity, View decorView) { + if (!hasNavigationBar(activity)) { + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN); + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + } + + private void hideStatusBar(Activity activity) { + WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + activity.getWindow().setAttributes(attrs); + } + + private void showStatusBar(Activity activity) { + WindowManager.LayoutParams attrs = activity.getWindow().getAttributes(); + attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; + activity.getWindow().setAttributes(attrs); + } + + + /** + * 隐藏导航栏和状态栏 + * + * @param activity 上下文 + * @param decorView getWindow().getDecorView(),不同view也可以 + */ + public void systemUiHide(Activity activity, View decorView) { + + if (!hasNavigationBar(activity)) { + hideStatusBar(activity); + return; + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE + | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); + } + + } + + /** + * 显示导航栏和状态栏 + * + * @param activity 上下文 + * @param decorView getWindow().getDecorView(),不同view也可以 + */ + public void systemUiShow(Activity activity, View decorView) { + if (!hasNavigationBar(activity)) { + showStatusBar(activity); + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + } + } + + /** + * 获取状态栏高度 + * + * @param cxt 上下文 + * @return 状态栏高度,单位PX + */ + public int getStatusBarHeight(Context cxt) { + int statusBarHeight = 0; + int resourceId = cxt.getResources().getIdentifier("status_bar_height", "dimen", "android"); + if (resourceId > 0) { + statusBarHeight = cxt.getResources().getDimensionPixelSize(resourceId); + } else { + statusBarHeight = (int) (23 * (cxt.getResources().getDisplayMetrics().density) + 0.5f); + } + return statusBarHeight; + } + + public void setStatusDark(Activity activity, boolean darkmode) { + if (isFlymeV4OrAbove()) { + MeiZuStatusUtils.setStatusBarDarkIcon(activity, darkmode); + return; + } + if (isMIUIV6OrAbove()) { + setStatusTextBlackMi(activity, darkmode); + return; + } + setStatusTextBlackAndroid(activity, darkmode); + } + + private void setStatusTextBlackMi(Activity activity, boolean darkmode) { + Class clazz = activity.getWindow().getClass(); + try { + int darkModeFlag = 0; + Class layoutParams = Class.forName("android.view.MiuiWindowManager$LayoutParams"); + Field field = layoutParams.getField("EXTRA_FLAG_STATUS_BAR_DARK_MODE"); + darkModeFlag = field.getInt(layoutParams); + Method extraFlagField = clazz.getMethod("setExtraFlags", int.class, int.class); + extraFlagField.invoke(activity.getWindow(), darkmode ? darkModeFlag : 0, darkModeFlag); + } catch (Exception e) { + e.printStackTrace(); + } + setStatusTextBlackAndroid(activity, darkmode); + } + + + private void setStatusTextBlackAndroid(Activity activity, boolean darkmode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = activity.getWindow(); + if (darkmode) { + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + int flag = window.getDecorView().getSystemUiVisibility() & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + window.getDecorView().setSystemUiVisibility(flag); + } + } + } + + + private boolean isFlymeV4OrAbove() { + String displayId = Build.DISPLAY; + if (!TextUtils.isEmpty(displayId) && displayId.contains("Flyme")) { + String[] displayIdArray = displayId.split(" "); + for (String temp : displayIdArray) { + //版本号4以上,形如4.x. + if (temp.matches("^[4-9]\\.(\\d+\\.)+\\S*")) { + return true; + } + } + } + return false; + } + + //MIUI V6对应的versionCode是4 + //MIUI V7对应的versionCode是5 + private boolean isMIUIV6OrAbove() { + String miuiVersionCodeStr = getSystemProperty("ro.miui.ui.version.code"); + if (!TextUtils.isEmpty(miuiVersionCodeStr)) { + try { + int miuiVersionCode = Integer.parseInt(miuiVersionCodeStr); + if (miuiVersionCode >= 4) { + return true; + } + } catch (Exception e) { + } + } + return false; + } + + //Android Api 23以上 + private boolean isAndroidMOrAbove() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return true; + } + return false; + } + + private String getSystemProperty(String propName) { + String line; + BufferedReader input = null; + try { + Process p = Runtime.getRuntime().exec("getprop " + propName); + input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); + line = input.readLine(); + input.close(); + } catch (IOException ex) { + return null; + } finally { + if (input != null) { + try { + input.close(); + } catch (IOException e) { + } + } + } + return line; + } + + +} diff --git a/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java new file mode 100644 index 0000000..7398bb7 --- /dev/null +++ b/easyPhotos/src/main/java/com/huantansheng/easyphotos/utils/uri/UriUtils.java @@ -0,0 +1,178 @@ +package com.huantansheng.easyphotos.utils.uri; + +import android.annotation.SuppressLint; +import android.content.ContentResolver; +import android.content.ContentUris; +import android.content.ContentValues; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.util.Log; + +import androidx.core.content.FileProvider; + +import com.huantansheng.easyphotos.setting.Setting; + +import java.io.File; +import java.util.Locale; + +public class UriUtils { + public static Uri getUri(Context cxt, File file) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + if (null==Setting.fileProviderAuthority){ + throw new NullPointerException("Setting.fileProviderAuthority must not be null."); + } + return FileProvider.getUriForFile(cxt, Setting.fileProviderAuthority, file); + } else { + return Uri.fromFile(file); + } + } + + private static final String TAG = "UriUtils"; + + @SuppressLint("NewApi") + public static String getPathByUri(Context context, Uri uri) { + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } else { + return context.getExternalFilesDir(Environment.DIRECTORY_PICTURES) + "/" + split[1]; + } + } + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.getContentUri("external"); + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.getContentUri("external"); + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.getContentUri("external"); + } + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{split[1]}; + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + // Return the remote address + if (isGooglePhotosUri(uri)) { + return uri.getLastPathSegment(); + } + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + return null; + } + + private static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) { + Cursor cursor = null; + final String column = "_data"; + final String[] projection = {column}; + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch (IllegalArgumentException ex) { + Log.i(TAG, String.format(Locale.getDefault(), "getDataColumn: _data - [%s]", ex.getMessage())); + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + private static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + private static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + private static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } + + /** + * 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.content/... + **/ + public static boolean isGooglePhotosUri(Uri uri) { + return "com.google.android.apps.photos.content".equals(uri.getAuthority()); + } + + /** + * 判断是否是Google相册的图片,类似于content://com.google.android.apps.photos.contentprovider/0/1/mediakey:/local%3A821abd2f-9f8c-4931-bbe9-a975d1f5fabc/ORIGINAL/NONE/1075342619 + **/ + public static boolean isGooglePlayPhotosUri(Uri uri) { + return "com.google.android.apps.photos.contentprovider".equals(uri.getAuthority()); + } + + /** + * 图片路径转uri + */ + public static Uri getUriByPath(Context context, String path) { + ContentResolver contentResolver = context.getContentResolver(); + Uri contentUri = MediaStore.Files.getContentUri("external"); + Cursor cursor = contentResolver.query(contentUri, new String[]{MediaStore.Files.FileColumns._ID}, + MediaStore.Files.FileColumns.DATA + "=? ", new String[]{path}, + null); + if (cursor != null && cursor.moveToFirst()) { + int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID)); + cursor.close(); + return Uri.withAppendedPath(contentUri, "" + id); + } else { + if (new File(path).exists()) { + ContentValues values = new ContentValues(); + values.put(MediaStore.Images.Media.DATA, path); + return contentResolver.insert(contentUri, values); + } else { + return null; + } + } + } +} diff --git a/easyPhotos/src/main/res/drawable/bg_dialog_album_items_background_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_dialog_album_items_background_easy_photos.xml new file mode 100644 index 0000000..83360c5 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_dialog_album_items_background_easy_photos.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml new file mode 100644 index 0000000..f898eb6 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_dialog_loading_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_menu_done_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_menu_done_easy_photos.xml new file mode 100644 index 0000000..4957cdc --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_menu_done_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml new file mode 100644 index 0000000..29b5211 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_second_level_menu_easy_photos.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml new file mode 100644 index 0000000..3ca35f3 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_seek_bar_alpha_easy_photos.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_select_false_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_select_false_easy_photos.xml new file mode 100644 index 0000000..6793f8c --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_select_false_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml new file mode 100644 index 0000000..a7f3d10 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_select_false_unable_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_select_true_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_select_true_easy_photos.xml new file mode 100644 index 0000000..3c115bc --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_select_true_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml new file mode 100644 index 0000000..0e1a5a1 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_selected_frame_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml new file mode 100644 index 0000000..0e1a5a1 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_selected_frame_puzzle_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml b/easyPhotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml new file mode 100644 index 0000000..6e0c8d7 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/bg_text_sticker_editor_easy_photos.xml @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml new file mode 100644 index 0000000..2fe707f --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_album_item_choose_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml new file mode 100644 index 0000000..6169789 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_album_items_name_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml new file mode 100644 index 0000000..416d754 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_arrow_back_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml new file mode 100644 index 0000000..217e544 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_arrow_down_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml new file mode 100644 index 0000000..dd0f1a3 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_arrow_up_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_black_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_black_easy_photos.xml new file mode 100644 index 0000000..496e40a --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_black_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_blue_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_blue_easy_photos.xml new file mode 100644 index 0000000..6a03151 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_blue_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_camera_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_camera_easy_photos.xml new file mode 100644 index 0000000..dcf2c47 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_camera_easy_photos.xml @@ -0,0 +1,12 @@ + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_clear_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_clear_easy_photos.xml new file mode 100644 index 0000000..5ffd816 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_clear_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_cyan_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_cyan_easy_photos.xml new file mode 100644 index 0000000..2c9f2d8 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_cyan_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_delete_easyy_photos.xml b/easyPhotos/src/main/res/drawable/ic_delete_easyy_photos.xml new file mode 100644 index 0000000..adfa9cb --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_delete_easyy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_edit_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_edit_easy_photos.xml new file mode 100644 index 0000000..be1eed3 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_edit_easy_photos.xml @@ -0,0 +1,12 @@ + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_gray_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_gray_easy_photos.xml new file mode 100644 index 0000000..f9c38a2 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_gray_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_green_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_green_easy_photos.xml new file mode 100644 index 0000000..a1394e2 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_green_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_notifications_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_notifications_easy_photos.xml new file mode 100644 index 0000000..d05014a --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_notifications_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_orange_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_orange_easy_photos.xml new file mode 100644 index 0000000..a91b3f0 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_orange_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_play_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_play_easy_photos.xml new file mode 100644 index 0000000..ffc0a53 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_play_easy_photos.xml @@ -0,0 +1,12 @@ + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_purple_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_purple_easy_photos.xml new file mode 100644 index 0000000..29c24ad --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_purple_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml new file mode 100644 index 0000000..db47dcc --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_puzzle_corner_easy_photos.xml @@ -0,0 +1,14 @@ + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml new file mode 100644 index 0000000..b50a433 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_puzzle_flip_easy_photos.xml @@ -0,0 +1,23 @@ + + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml new file mode 100644 index 0000000..94faa3b --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_puzzle_mirror_easy_photos.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml new file mode 100644 index 0000000..0e552ba --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_puzzle_padding_easy_photos.xml @@ -0,0 +1,20 @@ + + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml new file mode 100644 index 0000000..ac21d46 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_puzzle_replace_easy_photos.xml @@ -0,0 +1,22 @@ + + + + + + diff --git a/easyPhotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml new file mode 100644 index 0000000..3c42147 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_puzzle_rotate_easy_photos.xml @@ -0,0 +1,12 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_red_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_red_easy_photos.xml new file mode 100644 index 0000000..98e95cd --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_red_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_selector_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_selector_easy_photos.xml new file mode 100644 index 0000000..7875efd --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_selector_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_selector_true_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_selector_true_easy_photos.xml new file mode 100644 index 0000000..cfece1e --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_selector_true_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_settings_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_settings_easy_photos.xml new file mode 100644 index 0000000..68ebe94 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_settings_easy_photos.xml @@ -0,0 +1,9 @@ + + + diff --git a/easyPhotos/src/main/res/drawable/ic_white_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_white_easy_photos.xml new file mode 100644 index 0000000..56412d6 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_white_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/ic_yelow_easy_photos.xml b/easyPhotos/src/main/res/drawable/ic_yelow_easy_photos.xml new file mode 100644 index 0000000..1a5f8e8 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/ic_yelow_easy_photos.xml @@ -0,0 +1,12 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/progress_bar_easy_photos.xml b/easyPhotos/src/main/res/drawable/progress_bar_easy_photos.xml new file mode 100644 index 0000000..abb1c54 --- /dev/null +++ b/easyPhotos/src/main/res/drawable/progress_bar_easy_photos.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml b/easyPhotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml new file mode 100644 index 0000000..c4624ba --- /dev/null +++ b/easyPhotos/src/main/res/drawable/thumb_seek_bar_alpha_easy_photos.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/activity_easy_photos.xml b/easyPhotos/src/main/res/layout/activity_easy_photos.xml new file mode 100644 index 0000000..67e902b --- /dev/null +++ b/easyPhotos/src/main/res/layout/activity_easy_photos.xml @@ -0,0 +1,288 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/activity_preview_easy_photos.xml b/easyPhotos/src/main/res/layout/activity_preview_easy_photos.xml new file mode 100644 index 0000000..58359df --- /dev/null +++ b/easyPhotos/src/main/res/layout/activity_preview_easy_photos.xml @@ -0,0 +1,166 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyPhotos/src/main/res/layout/activity_puzzle_easy_photos.xml b/easyPhotos/src/main/res/layout/activity_puzzle_easy_photos.xml new file mode 100644 index 0000000..d3eaf19 --- /dev/null +++ b/easyPhotos/src/main/res/layout/activity_puzzle_easy_photos.xml @@ -0,0 +1,213 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyPhotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml b/easyPhotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml new file mode 100644 index 0000000..32942e1 --- /dev/null +++ b/easyPhotos/src/main/res/layout/activity_puzzle_selector_easy_photos.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyPhotos/src/main/res/layout/dialog_loading_easy_photos.xml b/easyPhotos/src/main/res/layout/dialog_loading_easy_photos.xml new file mode 100644 index 0000000..b095026 --- /dev/null +++ b/easyPhotos/src/main/res/layout/dialog_loading_easy_photos.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/fragment_preview_easy_photos.xml b/easyPhotos/src/main/res/layout/fragment_preview_easy_photos.xml new file mode 100644 index 0000000..3288f22 --- /dev/null +++ b/easyPhotos/src/main/res/layout/fragment_preview_easy_photos.xml @@ -0,0 +1,17 @@ + + + + diff --git a/easyPhotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml b/easyPhotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml new file mode 100644 index 0000000..809e957 --- /dev/null +++ b/easyPhotos/src/main/res/layout/fragment_text_sticker_easy_photos.xml @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_ad_easy_photos.xml b/easyPhotos/src/main/res/layout/item_ad_easy_photos.xml new file mode 100644 index 0000000..2cedb12 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_ad_easy_photos.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_camera_easy_photos.xml b/easyPhotos/src/main/res/layout/item_camera_easy_photos.xml new file mode 100644 index 0000000..5678a13 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_camera_easy_photos.xml @@ -0,0 +1,27 @@ + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml b/easyPhotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml new file mode 100644 index 0000000..ef80903 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_dialog_album_items_easy_photos.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_preview_photo_easy_photos.xml b/easyPhotos/src/main/res/layout/item_preview_photo_easy_photos.xml new file mode 100644 index 0000000..619c65f --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_preview_photo_easy_photos.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml b/easyPhotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml new file mode 100644 index 0000000..676541d --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_preview_selected_photos_easy_photos.xml @@ -0,0 +1,43 @@ + + + + + + + + + + diff --git a/easyPhotos/src/main/res/layout/item_puzzle_easy_photos.xml b/easyPhotos/src/main/res/layout/item_puzzle_easy_photos.xml new file mode 100644 index 0000000..efdfa50 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_puzzle_easy_photos.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml b/easyPhotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml new file mode 100644 index 0000000..3123350 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_puzzle_selector_easy_photos.xml @@ -0,0 +1,44 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml b/easyPhotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml new file mode 100644 index 0000000..ad46c94 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_puzzle_selector_preview_easy_photos.xml @@ -0,0 +1,52 @@ + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_rv_photos_easy_photos.xml b/easyPhotos/src/main/res/layout/item_rv_photos_easy_photos.xml new file mode 100644 index 0000000..c208ab3 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_rv_photos_easy_photos.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/layout/item_text_sticker_easy_photos.xml b/easyPhotos/src/main/res/layout/item_text_sticker_easy_photos.xml new file mode 100644 index 0000000..5333954 --- /dev/null +++ b/easyPhotos/src/main/res/layout/item_text_sticker_easy_photos.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/mipmap-xhdpi/ic_controller_easy_photos.png b/easyPhotos/src/main/res/mipmap-xhdpi/ic_controller_easy_photos.png new file mode 100644 index 0000000..3a78517 Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xhdpi/ic_controller_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xhdpi/ic_delete_easy_photos.png b/easyPhotos/src/main/res/mipmap-xhdpi/ic_delete_easy_photos.png new file mode 100644 index 0000000..66de50f Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xhdpi/ic_delete_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xhdpi/ic_editor_easy_photos.png b/easyPhotos/src/main/res/mipmap-xhdpi/ic_editor_easy_photos.png new file mode 100644 index 0000000..f8fa4d4 Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xhdpi/ic_editor_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xhdpi/ic_mirror_easy_photos.png b/easyPhotos/src/main/res/mipmap-xhdpi/ic_mirror_easy_photos.png new file mode 100644 index 0000000..759508c Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xhdpi/ic_mirror_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xhdpi/ic_rotate_easy_photos.png b/easyPhotos/src/main/res/mipmap-xhdpi/ic_rotate_easy_photos.png new file mode 100644 index 0000000..48b33b0 Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xhdpi/ic_rotate_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xxhdpi/ic_controller_easy_photos.png b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_controller_easy_photos.png new file mode 100644 index 0000000..3a78517 Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_controller_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xxhdpi/ic_delete_easy_photos.png b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_delete_easy_photos.png new file mode 100644 index 0000000..66de50f Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_delete_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xxhdpi/ic_editor_easy_photos.png b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_editor_easy_photos.png new file mode 100644 index 0000000..f8fa4d4 Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_editor_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xxhdpi/ic_mirror_easy_photos.png b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_mirror_easy_photos.png new file mode 100644 index 0000000..759508c Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_mirror_easy_photos.png differ diff --git a/easyPhotos/src/main/res/mipmap-xxhdpi/ic_rotate_easy_photos.png b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_rotate_easy_photos.png new file mode 100644 index 0000000..48b33b0 Binary files /dev/null and b/easyPhotos/src/main/res/mipmap-xxhdpi/ic_rotate_easy_photos.png differ diff --git a/easyPhotos/src/main/res/values-v19/styles.xml b/easyPhotos/src/main/res/values-v19/styles.xml new file mode 100644 index 0000000..f5f9176 --- /dev/null +++ b/easyPhotos/src/main/res/values-v19/styles.xml @@ -0,0 +1,17 @@ + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/values-v21/styles.xml b/easyPhotos/src/main/res/values-v21/styles.xml new file mode 100644 index 0000000..18938e7 --- /dev/null +++ b/easyPhotos/src/main/res/values-v21/styles.xml @@ -0,0 +1,20 @@ + + + + + + + + + \ No newline at end of file diff --git a/easyPhotos/src/main/res/values-w480dp/integers.xml b/easyPhotos/src/main/res/values-w480dp/integers.xml new file mode 100644 index 0000000..b37c5e4 --- /dev/null +++ b/easyPhotos/src/main/res/values-w480dp/integers.xml @@ -0,0 +1,4 @@ + + + 4 + \ No newline at end of file diff --git a/easyPhotos/src/main/res/values-w640dp/integers.xml b/easyPhotos/src/main/res/values-w640dp/integers.xml new file mode 100644 index 0000000..b4a3fa5 --- /dev/null +++ b/easyPhotos/src/main/res/values-w640dp/integers.xml @@ -0,0 +1,4 @@ + + + 5 + \ No newline at end of file diff --git a/easyPhotos/src/main/res/values/attrs.xml b/easyPhotos/src/main/res/values/attrs.xml new file mode 100644 index 0000000..7454cf3 --- /dev/null +++ b/easyPhotos/src/main/res/values/attrs.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/easyPhotos/src/main/res/values/colors.xml b/easyPhotos/src/main/res/values/colors.xml new file mode 100644 index 0000000..69dfc64 --- /dev/null +++ b/easyPhotos/src/main/res/values/colors.xml @@ -0,0 +1,45 @@ + + + #303135 + #00000000 + #FFFFFF + #FFFFFF//按钮上的文字颜色,选择器上的文字颜色,选择器的边框颜色 + + + #FFFFFF//专辑项目列表的背景颜色 + #3e4145//专辑项目列表中专辑名称的字体颜色 + #4f5555//专辑项目列表中包含图片张数的字体颜色 + + + #33FFFFFF加载框背景颜色 + + + #d0021b//编辑字体颜色为红色 + #f5a623//编辑字体颜色为橙色 + #f8e71c//编辑字体颜色为黄色 + #7ed321//编辑字体颜色为绿色 + #50e3c2//编辑字体颜色为青色 + #4a90e2//编辑字体颜色为蓝色 + #9013fe//编辑字体颜色为紫色 + #000000//编辑字体颜色为黑色 + #4a4a4a//编辑字体颜色为灰色 + #ffffff//编辑字体颜色为白色 + #2c2e30//编辑界面背景色 + #393a3f//编辑界面操作栏颜色 + + + #ffffff//3e4145 前景主色,如字体颜色、icon颜色 + #9b9b9b//前景偏暗色,如不可用状态的字体颜色 + #00AA00//578fff 前景突出色,如发送按钮的背景色,选中状态的颜色 + + + #000000//f9f9f9 背景色,如每个activity的背景色,相册的图片间隔线颜色 + + + #393a3f//ffffff 操作栏主色,如相册页顶部和底部操作栏 + #303135//f9f9f9 操作栏偏暗色,操作栏间隔线颜色,如相册页返回按钮右边的竖线 + #eb212123//b4ffffff 操作栏带透明的色值,如预览页的底部操作栏颜色,相册页的相机按钮背景颜色。 + + + #393a3f + \ No newline at end of file diff --git a/easyPhotos/src/main/res/values/dimens.xml b/easyPhotos/src/main/res/values/dimens.xml new file mode 100644 index 0000000..66687c9 --- /dev/null +++ b/easyPhotos/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + 16sp + 42dp + diff --git a/easyPhotos/src/main/res/values/integers.xml b/easyPhotos/src/main/res/values/integers.xml new file mode 100644 index 0000000..d091295 --- /dev/null +++ b/easyPhotos/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 3 + \ No newline at end of file diff --git a/easyPhotos/src/main/res/values/strings.xml b/easyPhotos/src/main/res/values/strings.xml new file mode 100644 index 0000000..30a10ce --- /dev/null +++ b/easyPhotos/src/main/res/values/strings.xml @@ -0,0 +1,78 @@ + + EasyPhotos + + 所有图片 + + 图片和视频 + + 所有视频 + + 选择 + + + 完成(%1$d/%2$d) + + 预览 + + 超出最大选择数 + + 最多只能选择%d张图片 + + 最多只能选择%d个视频 + + 需要选择同种类型的文件 + + 无法启动相机! + + 图片错误 + + 权限错误,无法正常工作! + + 图片的宽度必须大于%1$d,高度必须大于%2$d + + 不支持此图片格式 + + 编辑 + + 清空 + + 没有符合要求的图片,拍一张吧 + + 没有符合要求的视频 + + 请点击并允许相关权限 + + 请点击并在设置中允许相关权限 + + 原图 + + %1$d/%2$d + + 完成 + + 取消 + + 模板 + + 文字 + + 日期 + + 双击编辑 + + 自定义 + + 图片选择 + + 视频选择 + + 拼一张 + + 请选择2 \- 9张照片 + + 动图 + + 视频 + + + diff --git a/easyPhotos/src/main/res/values/styles.xml b/easyPhotos/src/main/res/values/styles.xml new file mode 100644 index 0000000..e77cad3 --- /dev/null +++ b/easyPhotos/src/main/res/values/styles.xml @@ -0,0 +1,15 @@ + + + + + + + diff --git a/easysocket/.gitignore b/easysocket/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/easysocket/.gitignore @@ -0,0 +1 @@ +/build diff --git a/easysocket/build.gradle b/easysocket/build.gradle new file mode 100644 index 0000000..fde31cd --- /dev/null +++ b/easysocket/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'com.android.library' +} + +android { + compileSdkVersion 33 + + defaultConfig { + minSdkVersion 15 + targetSdkVersion 33 + vectorDrawables.useSupportLibrary = true + } + namespace 'com.easysocket' +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'com.google.code.gson:gson:2.13.1' +} + +sourceCompatibility = "7" +targetCompatibility = "7" diff --git a/easysocket/src/main/AndroidManifest.xml b/easysocket/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2b69a98 --- /dev/null +++ b/easysocket/src/main/AndroidManifest.xml @@ -0,0 +1,4 @@ + + + diff --git a/easysocket/src/main/java/com/easysocket/ConnectionHolder.java b/easysocket/src/main/java/com/easysocket/ConnectionHolder.java new file mode 100644 index 0000000..c21cb16 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/ConnectionHolder.java @@ -0,0 +1,136 @@ +package com.easysocket; + +import com.easysocket.config.EasySocketOptions; +import com.easysocket.connection.connect.SuperConnection; +import com.easysocket.connection.connect.TcpConnection; +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.config.IConnectionSwitchListener; +import com.easysocket.interfaces.conn.IConnectionManager; + +import java.util.HashMap; +import java.util.Map; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:socket连接管理器 + */ +public class ConnectionHolder { + + private volatile Map mConnectionManagerMap = new HashMap<>(); + + + private static class InstanceHolder { + private static final ConnectionHolder INSTANCE = new ConnectionHolder(); + } + + public static ConnectionHolder getInstance() { + return InstanceHolder.INSTANCE; + } + + private ConnectionHolder() { + mConnectionManagerMap.clear(); + } + + /** + * 移除某个连接 + * + * @param socketAddress + */ + public void removeConnection(SocketAddress socketAddress) { + removeConnection(createKey(socketAddress)); + } + + public void removeConnection(String socketAddress) { + mConnectionManagerMap.remove(socketAddress); + } + + /** + * 获取指定SocketAddress的连接,参数配置使用默认的 + * + * @param address + * @return + */ + public IConnectionManager getConnection(SocketAddress address) { + return getConnection(createKey(address)); + } + + public IConnectionManager getConnection(String address) { + IConnectionManager manager = mConnectionManagerMap.get(address); + if (manager == null) { + return getConnection(address, EasySocketOptions.getDefaultOptions()); + } else { + return getConnection(address, manager.getOptions()); + } + } + + /** + * 获取指定SocketAddress的连接 + * + * @param address + * @param socketOptions + * @return + */ + public IConnectionManager getConnection(SocketAddress address, EasySocketOptions socketOptions) { + return getConnection(createKey(address),socketOptions); + } + + public IConnectionManager getConnection(String address, EasySocketOptions socketOptions) { + IConnectionManager manager = mConnectionManagerMap.get(address); + if (manager != null) { // 有缓存 + manager.setOptions(socketOptions); + return manager; + } else { + return createNewManagerAndCache(address, socketOptions); + } + } + + /** + * 创建新的连接并缓存 + * + * @param address + * @param socketOptions + * @return + */ + private IConnectionManager createNewManagerAndCache(SocketAddress address, EasySocketOptions socketOptions) { + SuperConnection manager = new TcpConnection(address); // 创建连接管理器 + manager.setOptions(socketOptions); // 设置参数 + // 连接主机的切换监听 + manager.setOnConnectionSwitchListener(new IConnectionSwitchListener() { + @Override + public void onSwitchConnectionInfo(IConnectionManager manager, SocketAddress oldAddress, + SocketAddress newAddress) { + // 切换了另外一个主机的连接,删除旧的连接和添加新的连接 + synchronized (mConnectionManagerMap) { + // 首先断开连接,销毁相关线程和资源 + mConnectionManagerMap.get(createKey(oldAddress)).disconnect(false); + mConnectionManagerMap.remove(createKey(oldAddress)); + mConnectionManagerMap.put(createKey(newAddress), manager); + } + } + }); + + synchronized (mConnectionManagerMap) { + mConnectionManagerMap.put(createKey(address), manager); + } + return manager; + } + + private IConnectionManager createNewManagerAndCache(String address, EasySocketOptions socketOptions) { + return createNewManagerAndCache(createSocketAddress(address), socketOptions); + } + + /** + * @param socketAddress + * @return + */ + private String createKey(SocketAddress socketAddress) { + return socketAddress.getIp() + ":" + socketAddress.getPort(); + } + + private SocketAddress createSocketAddress(String address) { + String[] s = address.split(":"); + return new SocketAddress(s[0], Integer.parseInt(s[1])); + } + +} diff --git a/easysocket/src/main/java/com/easysocket/EasySocket.java b/easysocket/src/main/java/com/easysocket/EasySocket.java new file mode 100644 index 0000000..85b8ea7 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/EasySocket.java @@ -0,0 +1,335 @@ +package com.easysocket; + +import android.content.Context; + +import com.easysocket.config.EasySocketOptions; +import com.easysocket.connection.heartbeat.HeartManager; +import com.easysocket.entity.SocketAddress; +import com.easysocket.entity.basemsg.SuperCallbackSender; +import com.easysocket.exception.InitialExeption; +import com.easysocket.exception.NotNullException; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.ISocketActionListener; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:EasySocket API + */ +public class EasySocket { + + /** + * 连接的缓存 + */ + private static ConnectionHolder connectionHolder = ConnectionHolder.getInstance(); + // 单例 + private volatile static EasySocket singleton = null; + /** + * 默认的连接参数 + */ + private EasySocketOptions defOptions; + /** + * 默认的连接 + */ + private IConnectionManager defConnection; + /** + * 上下文 + */ + private Context context; + + /** + * 单例 + * + * @return + */ + public static EasySocket getInstance() { + if (singleton == null) { + synchronized (EasySocket.class) { + if (singleton == null) { + singleton = new EasySocket(); + } + } + } + return singleton; + } + + /** + * 获取上下文 + * + * @return + */ + public Context getContext() { + return context; + } + + /** + * 获取默认的配置参数 + * + * @return + */ + public EasySocketOptions getDefOptions() { + return defOptions == null ? EasySocketOptions.getDefaultOptions() : defOptions; + } + + /** + * 创建socket连接,此连接为默认的连接,如果你的项目只有一个Socket连接,可以用这个方法, + * 在方法不指定连接地址的情况下,默认使用都是这个连接, + * 比如: upMessage(byte[] message)、 connect()等 + * + * @return + */ + public EasySocket createConnection(EasySocketOptions options, Context context) { + this.defOptions = options; + this.context = context; + SocketAddress socketAddress = options.getSocketAddress(); + if (options.getSocketAddress() == null) { + throw new InitialExeption("请在初始化的时候设置SocketAddress"); + } + // 如果有备用主机则设置 + if (options.getBackupAddress() != null) { + socketAddress.setBackupAddress(options.getBackupAddress()); + } + if (defConnection == null) { + defConnection = connectionHolder.getConnection(socketAddress, + options == null ? EasySocketOptions.getDefaultOptions() : options); + } + // 执行连接 + defConnection.connect(); + return this; + } + + /** + * 连接socket,作用于默认连接 + * + * @return + */ + public EasySocket connect() { + getDefconnection().connect(); + return this; + } + + /** + * @param address socket地址,包括ip和端口 + * @return + */ + public EasySocket connect(String address) { + getConnection(address).connect(); + return this; + } + + + /** + * 关闭连接,作用于默认连接 + * + * @param isNeedReconnect 是否需要重连 + * @return + */ + public EasySocket disconnect(boolean isNeedReconnect) { + getDefconnection().disconnect(isNeedReconnect); + return this; + } + + + /** + * 关闭连接 + * + * @param isNeedReconnect 是否需要重连 + * @return + */ + public EasySocket disconnect(String address, boolean isNeedReconnect) { + getConnection(address).disconnect(isNeedReconnect); + return this; + } + + /** + * 销毁连接对象,作用于默认连接 + * + * @return + */ + public EasySocket destroyConnection() { + // 断开连接 + getDefconnection().disconnect(false); + // 移除连接 + connectionHolder.removeConnection(defOptions.getSocketAddress()); + defConnection = null; + return this; + } + + + /** + * 销毁连接对象 + * + * @return + */ + public EasySocket destroyConnection(String address) { + // 断开连接 + getConnection(address).disconnect(false); + // 移除连接 + connectionHolder.removeConnection(address); + return this; + } + + /** + * 发送有回调的消息,作用于默认连接 + * + * @param sender + * @return + */ + public IConnectionManager upCallbackMessage(SuperCallbackSender sender) { + getDefconnection().upCallbackMessage(sender); + return defConnection; + } + + /** + * 发送有回调的消息 + * + * @param sender + * @return + */ + public IConnectionManager upCallbackMessage(SuperCallbackSender sender, String address) { + return getConnection(address).upCallbackMessage(sender); + } + + + /** + * 发送byte[] + * + * @param message + * @return + */ + public IConnectionManager upMessage(byte[] message, String address) { + return getConnection(address).upBytes(message); + } + + /** + * 发送byte[],作用于默认连接 + * + * @param message + * @return + */ + public IConnectionManager upMessage(byte[] message) { + return getDefconnection().upBytes(message); + } + + + /** + * 注册监听socket行为,作用于默认连接 + * + * @param socketActionListener + */ + public EasySocket subscribeSocketAction(ISocketActionListener socketActionListener) { + getDefconnection().subscribeSocketAction(socketActionListener); + return this; + } + + + /** + * 注册监听socket行为 + * + * @param socketActionListener + */ + public EasySocket subscribeSocketAction(ISocketActionListener socketActionListener, String address) { + getConnection(address).subscribeSocketAction(socketActionListener); + return this; + } + + /** + * 开启心跳检测,作用于默认连接 + * + * @param clientHeart + * @return + */ + public EasySocket startHeartBeat(byte[] clientHeart, HeartManager.HeartbeatListener listener) { + getDefconnection().getHeartManager().startHeartbeat(clientHeart, listener); + return this; + } + + /** + * 开启心跳检测 + * + * @param clientHeart + * @return + */ + public EasySocket startHeartBeat(byte[] clientHeart, String address, HeartManager.HeartbeatListener listener) { + getConnection(address).getHeartManager().startHeartbeat(clientHeart, listener); + return this; + } + + + /** + * 获取连接 + * + * @return + */ + public IConnectionManager getDefconnection() { + if (defConnection == null) { + throw new NotNullException("你还没有创建:" + defOptions.getSocketAddress().getIp() + ":" + defOptions.getSocketAddress().getPort() + + "的Socket的连接,请使用com.easysocket.EasySocket.connect()方法创建一个默认的连接"); + } + return defConnection; + } + + /** + * 获取连接 + * + * @return + */ + public IConnectionManager getConnection(String address) { + IConnectionManager connectionManager = connectionHolder.getConnection(address); + if (connectionManager == null) { + throw new NotNullException("请先创建:" + address + "的Socket连接"); + } + return connectionManager; + } + + /** + * 创建指定的socket连接,如果你的项目有多个socket连接,可以用这个方法创建更多的连接, + * 当你使用带有socket地址为参数的方法的时候,作用的就是对应的连接 + * 比如:connect(String address)、 upMessage(byte[] message, String address)等 + * + * @param socketOptions + * @return + */ + public IConnectionManager createSpecifyConnection(EasySocketOptions socketOptions, Context context) { + this.context = context; + IConnectionManager connectionManager = connectionHolder.getConnection(socketOptions.getSocketAddress(), socketOptions == null + ? EasySocketOptions.getDefaultOptions() : socketOptions); + + connectionManager.connect(); + return connectionManager; + } + + /** + * 获取指定的连接 + * + * @param socketAddress + * @return + */ + public IConnectionManager getSpecifyConnection(String socketAddress) { + return connectionHolder.getConnection(socketAddress); + } + + /** + * 发送消息至指定的连接 + * + * @param sender + * @param socketAddress + */ + public IConnectionManager upToSpecifyConnection(byte[] sender, String socketAddress) { + IConnectionManager connect = getSpecifyConnection(socketAddress); + if (connect != null) { + connect.upBytes(sender); + } + return connect; + } + + /** + * 是否为debug + * + * @param debug + */ + public void setDebug(boolean debug) { + EasySocketOptions.setIsDebug(debug); + } + +} diff --git a/easysocket/src/main/java/com/easysocket/callback/ProgressDialogCallBack.java b/easysocket/src/main/java/com/easysocket/callback/ProgressDialogCallBack.java new file mode 100644 index 0000000..0da1ee9 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/callback/ProgressDialogCallBack.java @@ -0,0 +1,116 @@ + +package com.easysocket.callback; + +import android.app.Dialog; +import android.content.DialogInterface; + +import com.easysocket.entity.OriginReadData; +import com.easysocket.exception.RequestCancelException; +import com.easysocket.interfaces.callback.IProgressDialog; +import com.easysocket.interfaces.callback.ProgressCancelListener; + + +/** + * 自定义带有加载进度框的回调 + */ +public abstract class ProgressDialogCallBack extends SuperCallBack implements ProgressCancelListener { + + private IProgressDialog progressDialog; + private Dialog mDialog; + private boolean isShowProgress = true; + + /** + * @param + */ + public ProgressDialogCallBack(IProgressDialog progressDialog, String callbackId) { + super(callbackId); + this.progressDialog = progressDialog; + init(false); + onStart(); + } + + /** + * 自定义加载进度框,可以设置是否显示弹出框,是否可以取消 + * + * @param progressDialog dialog + * @param isShowProgress 是否显示进度 + * @param isCancel 对话框是否可以取消 + * @param + */ + public ProgressDialogCallBack(IProgressDialog progressDialog, boolean isShowProgress, + boolean isCancel, String callbackId) { + super(callbackId); + this.progressDialog = progressDialog; + this.isShowProgress = isShowProgress; + init(isCancel); + onStart(); + } + + /** + * 初始化 + * + * @param isCancel + */ + private void init(boolean isCancel) { + if (progressDialog == null) return; + mDialog = progressDialog.getDialog(); + if (mDialog == null) return; + mDialog.setCancelable(isCancel); + if (isCancel) { + mDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + @Override + public void onCancel(DialogInterface dialogInterface) { + ProgressDialogCallBack.this.onCancelProgress(); + } + }); + } + } + + /** + * 展示进度框 + */ + private void showProgress() { + if (!isShowProgress) { + return; + } + if (mDialog != null && !mDialog.isShowing()) { + mDialog.show(); + } + } + + /** + * 取消进度框 + */ + private void dismissProgress() { + if (!isShowProgress) { + return; + } + if (mDialog != null && mDialog.isShowing()) { + mDialog.dismiss(); + } + } + + @Override + public void onStart() { + showProgress(); + } + + @Override + public void onCompleted() { + dismissProgress(); + } + + public abstract void onResponse(OriginReadData data); + + @Override + public void onError(Exception e) { + onCompleted(); + } + + @Override + public void onCancelProgress() { + onCompleted(); + onError(new RequestCancelException("网络请求被取消")); + } + +} diff --git a/easysocket/src/main/java/com/easysocket/callback/SimpleCallBack.java b/easysocket/src/main/java/com/easysocket/callback/SimpleCallBack.java new file mode 100644 index 0000000..0e0c0c2 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/callback/SimpleCallBack.java @@ -0,0 +1,29 @@ +package com.easysocket.callback; + + +/** + * Created by LXR ON 2018/8/29. + */ +public abstract class SimpleCallBack extends SuperCallBack{ + + + public SimpleCallBack(String callbackId) { + super(callbackId); + } + + @Override + public void onStart() { + } + + @Override + public void onCompleted() { + + } + + @Override + public void onError(Exception e) { + + } + + +} diff --git a/easysocket/src/main/java/com/easysocket/callback/SuperCallBack.java b/easysocket/src/main/java/com/easysocket/callback/SuperCallBack.java new file mode 100644 index 0000000..cfbdde4 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/callback/SuperCallBack.java @@ -0,0 +1,44 @@ +package com.easysocket.callback; + + +import com.easysocket.entity.OriginReadData; + +/** + * Created by LXR ON 2018/8/29. + */ +public abstract class SuperCallBack { + /** + * 随机字符串,识别服务端应答消息的唯一标识 + */ + private String callbackId; + + /** + * @param callbackId 识别服务端应答消息的唯一标识 + */ + public SuperCallBack(String callbackId) { + this.callbackId = callbackId; + } + + /** + * 获取回调ID + * + * @return + */ + public String getCallbackId() { + return callbackId; + } + + public abstract void onStart(); + + public abstract void onCompleted(); + + public abstract void onError(Exception e); + + public void onSuccess(OriginReadData data) { + onCompleted(); + onResponse(data); + } + + public abstract void onResponse(OriginReadData data); + +} diff --git a/easysocket/src/main/java/com/easysocket/config/CallbackIDFactory.java b/easysocket/src/main/java/com/easysocket/config/CallbackIDFactory.java new file mode 100644 index 0000000..623fa5b --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/config/CallbackIDFactory.java @@ -0,0 +1,18 @@ +package com.easysocket.config; + +import com.easysocket.entity.OriginReadData; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:要想实现EasySocket的回调功能,必须实现此工厂类,callbackID作为回调消息的唯一标识 + */ +public abstract class CallbackIDFactory { + /** + * 返回callbackID + * + * @param + * @return 如果没有callbackID请返回null + */ + public abstract String getCallbackID(OriginReadData data); +} diff --git a/easysocket/src/main/java/com/easysocket/config/DefaultMessageProtocol.java b/easysocket/src/main/java/com/easysocket/config/DefaultMessageProtocol.java new file mode 100644 index 0000000..7d737f9 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/config/DefaultMessageProtocol.java @@ -0,0 +1,28 @@ +package com.easysocket.config; + +import com.easysocket.interfaces.config.IMessageProtocol; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; + +/** + * Author:Alex + * Date:2019/5/31 + * Note:默认的消息协议,header为4个字节,保存消息体 body的长度 + */ +public class DefaultMessageProtocol implements IMessageProtocol { + @Override + public int getHeaderLength() { + return 4; // 包头长度,用来保存body的长度值 + } + + @Override + public int getBodyLength(byte[] header, ByteOrder byteOrder) { + if (header == null || header.length < getHeaderLength()) { + return 0; + } + ByteBuffer bb = ByteBuffer.wrap(header); + bb.order(byteOrder); + return bb.getInt(); // body的长度以int的形式保存在 header + } +} diff --git a/easysocket/src/main/java/com/easysocket/config/DefaultX509ProtocolTrustManager.java b/easysocket/src/main/java/com/easysocket/config/DefaultX509ProtocolTrustManager.java new file mode 100644 index 0000000..ca7c96e --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/config/DefaultX509ProtocolTrustManager.java @@ -0,0 +1,28 @@ +package com.easysocket.config; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +import javax.net.ssl.X509TrustManager; + +/** + * Author:Alex + * Date:2019/6/3 + * Note: + */ +public class DefaultX509ProtocolTrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } +} diff --git a/easysocket/src/main/java/com/easysocket/config/EasySocketOptions.java b/easysocket/src/main/java/com/easysocket/config/EasySocketOptions.java new file mode 100644 index 0000000..0cf70b4 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/config/EasySocketOptions.java @@ -0,0 +1,478 @@ +package com.easysocket.config; + +import com.easysocket.connection.reconnect.AbsReconnection; +import com.easysocket.connection.reconnect.DefaultReConnection; +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.config.IMessageProtocol; + +import java.nio.ByteOrder; + +/** + * Author:Alex。 + * Date:2019/5/31。 + * Note:socket相关配置。 + */ +public class EasySocketOptions { + + /** + * 是否调试模式 + */ + private static boolean isDebug = true; + /** + * 主机地址 + */ + private SocketAddress socketAddress; + /** + * 备用主机地址 + */ + private SocketAddress backupAddress; + /** + * 写入Socket管道的字节序 + */ + private ByteOrder writeOrder; + /** + * 从Socket读取字节时的字节序 + */ + private ByteOrder readOrder; + /** + * 从socket读取数据时遵从的数据包结构协议,在业务层进行定义 + */ + private IMessageProtocol messageProtocol; + /** + * 写数据时单个数据包的最大值 + */ + private int maxWriteBytes; + /** + * 读数据时单次读取最大缓存值,数值越大效率越高,但是系统消耗也越大 + */ + private int maxReadBytes; + /** + * 心跳频率/毫秒 + */ + private long heartbeatFreq; + /** + * 心跳最大的丢失次数,大于这个数据,将断开socket连接 + */ + private int maxHeartbeatLoseTimes; + /** + * 连接超时时间(毫秒) + */ + private int connectTimeout; + /** + * 服务器返回数据的最大值(单位Mb),防止客户端内存溢出 + */ + private int maxResponseDataMb; + /** + * socket重连管理器 + */ + private AbsReconnection reconnectionManager; + /** + * 安全套接字相关配置 + */ + private SocketSSLConfig easySSLConfig; + /** + * socket工厂 + */ + private SocketFactory socketFactory; + /** + * 实现回调功能需要callbackID,而callbackID是保存在发送消息和应答消息中的,此工厂用来获取socket消息中 + * 保存callbackID值的key,比如json格式中的key-value中的key + */ + private CallbackIDFactory callbackIDFactory; + /** + * 请求超时时间,单位毫秒 + */ + private long requestTimeout; + /** + * 是否开启请求超时检测 + */ + private boolean isOpenRequestTimeout; + + /** + * IO字符流的编码方式,默认utf-8 + */ + private String charsetName; + + public boolean isDebug() { + return isDebug; + } + + + /** + * 静态内部类 + */ + public static class Builder { + EasySocketOptions socketOptions; + + // 首先获得一个默认的配置 + public Builder() { + this(getDefaultOptions()); + } + + public Builder(EasySocketOptions defaultOptions) { + socketOptions = defaultOptions; + } + + /** + * 设置socket 主机地址 + * + * @param socketAddress + * @return + */ + public Builder setSocketAddress(SocketAddress socketAddress) { + socketOptions.socketAddress = socketAddress; + return this; + } + + /** + * 设置备用的主机地址 + * + * @param backupAddress + * @return + */ + public Builder setBackupAddress(SocketAddress backupAddress) { + socketOptions.backupAddress = backupAddress; + return this; + } + + /** + * 设置是否开启请求超时的检测 + * + * @param openRequestTimeout + * @return + */ + public Builder setOpenRequestTimeout(boolean openRequestTimeout) { + socketOptions.isOpenRequestTimeout = openRequestTimeout; + return this; + } + + /** + * 设置请求超时时间 + * + * @param requestTimeout 毫秒 + * @return + */ + public Builder setRequestTimeout(long requestTimeout) { + socketOptions.requestTimeout = requestTimeout; + return this; + } + + /** + * 设置请求ack的工厂 + * + * @param callbackIDFactory + */ + public Builder setCallbackIDFactory(CallbackIDFactory callbackIDFactory) { + socketOptions.callbackIDFactory = callbackIDFactory; + return this; + } + + + /** + * 设置写数据的字节顺序 + * + * @param writeOrder + * @return + */ + public Builder setWriteOrder(ByteOrder writeOrder) { + socketOptions.writeOrder = writeOrder; + return this; + } + + /** + * 设置读数据的字节顺序 + * + * @param readOrder + * @return + */ + public Builder setReadOrder(ByteOrder readOrder) { + socketOptions.readOrder = readOrder; + return this; + } + + /** + * 设置读取数据的数据结构协议 + * + * @param readerProtocol + * @return + */ + public Builder setReaderProtocol(IMessageProtocol readerProtocol) { + socketOptions.messageProtocol = readerProtocol; + return this; + } + + /** + * 设置写数据时单个数据包的最大值 + * + * @param maxWriteBytes + * @return + */ + public Builder setMaxWriteBytes(int maxWriteBytes) { + socketOptions.maxWriteBytes = maxWriteBytes; + return this; + } + + /** + * 设置读数据时单次读取的最大缓存值 + * + * @param maxReadBytes + * @return + */ + public Builder setMaxReadBytes(int maxReadBytes) { + socketOptions.maxReadBytes = maxReadBytes; + return this; + } + + /** + * 设置心跳发送频率,单位毫秒 + * + * @param heartbeatFreq + * @return + */ + public Builder setHeartbeatFreq(long heartbeatFreq) { + socketOptions.heartbeatFreq = heartbeatFreq; + return this; + } + + /** + * 设置心跳丢失的最大允许数,如果超过这个最大数就断开socket连接 + * + * @param maxHeartbeatLoseTimes + * @return + */ + public Builder setMaxHeartbeatLoseTimes(int maxHeartbeatLoseTimes) { + socketOptions.maxHeartbeatLoseTimes = maxHeartbeatLoseTimes; + return this; + } + + /** + * 设置连接超时时间 + * + * @param connectTimeout + * @return + */ + public Builder setConnectTimeout(int connectTimeout) { + socketOptions.connectTimeout = connectTimeout; + return this; + } + + /** + * 设置服务器返回数据的允许的最大值,单位兆 + * + * @param maxResponseDataMb + * @return + */ + public Builder setMaxResponseDataMb(int maxResponseDataMb) { + socketOptions.maxResponseDataMb = maxResponseDataMb; + return this; + } + + /** + * 设置重连管理器 + * + * @param reconnectionManager + * @return + */ + public Builder setReconnectionManager(AbsReconnection reconnectionManager) { + socketOptions.reconnectionManager = reconnectionManager; + return this; + } + + /** + * 安全套接字的配置 + * + * @param easySSLConfig + * @return + */ + public Builder setEasySSLConfig(SocketSSLConfig easySSLConfig) { + socketOptions.easySSLConfig = easySSLConfig; + return this; + } + + /** + * 自定义创建socket工厂 + * + * @param socketFactory + * @return + */ + public Builder setSocketFactory(SocketFactory socketFactory) { + socketOptions.socketFactory = socketFactory; + return this; + } + + public Builder setCharsetName(String charsetName) { + socketOptions.charsetName = charsetName; + return this; + } + + public EasySocketOptions build() { + return socketOptions; + } + } + + + /** + * 获取默认的配置 + * + * @return + */ + public static EasySocketOptions getDefaultOptions() { + EasySocketOptions options = new EasySocketOptions(); + options.socketAddress = null; + options.backupAddress = null; + options.heartbeatFreq = 5 * 1000; + options.messageProtocol = null; + options.maxResponseDataMb = 5; + options.connectTimeout = 5 * 1000; // 连接超时默认5秒 + options.maxWriteBytes = 100; + options.maxReadBytes = 50; + options.readOrder = ByteOrder.BIG_ENDIAN; + options.writeOrder = ByteOrder.BIG_ENDIAN; + options.maxHeartbeatLoseTimes = 5; + options.reconnectionManager = new DefaultReConnection(); + options.easySSLConfig = null; + options.socketFactory = null; + options.callbackIDFactory = null; + options.requestTimeout = 10 * 1000; // 默认十秒 + options.isOpenRequestTimeout = true; // 默认开启 + options.charsetName = "UTF-8"; + return options; + } + + public String getCharsetName() { + return charsetName; + } + + public ByteOrder getWriteOrder() { + return writeOrder; + } + + public ByteOrder getReadOrder() { + return readOrder; + } + + public IMessageProtocol getMessageProtocol() { + return messageProtocol; + } + + public int getMaxWriteBytes() { + return maxWriteBytes; + } + + public int getMaxReadBytes() { + return maxReadBytes; + } + + public long getHeartbeatFreq() { + return heartbeatFreq; + } + + public int getMaxHeartbeatLoseTimes() { + return maxHeartbeatLoseTimes; + } + + public int getConnectTimeout() { + return connectTimeout; + } + + public int getMaxResponseDataMb() { + return maxResponseDataMb; + } + + public AbsReconnection getReconnectionManager() { + return reconnectionManager; + } + + public SocketSSLConfig getEasySSLConfig() { + return easySSLConfig; + } + + public SocketFactory getSocketFactory() { + return socketFactory; + } + + public long getRequestTimeout() { + return requestTimeout; + } + + public boolean isOpenRequestTimeout() { + return isOpenRequestTimeout; + } + + public CallbackIDFactory getCallbackIDFactory() { + return callbackIDFactory; + } + + public static void setIsDebug(boolean isDebug) { + EasySocketOptions.isDebug = isDebug; + } + + public void setWriteOrder(ByteOrder writeOrder) { + this.writeOrder = writeOrder; + } + + public void setReadOrder(ByteOrder readOrder) { + this.readOrder = readOrder; + } + + public void setMessageProtocol(IMessageProtocol messageProtocol) { + this.messageProtocol = messageProtocol; + } + + public void setMaxWriteBytes(int maxWriteBytes) { + this.maxWriteBytes = maxWriteBytes; + } + + public void setMaxReadBytes(int maxReadBytes) { + this.maxReadBytes = maxReadBytes; + } + + public void setHeartbeatFreq(long heartbeatFreq) { + this.heartbeatFreq = heartbeatFreq; + } + + public void setMaxHeartbeatLoseTimes(int maxHeartbeatLoseTimes) { + this.maxHeartbeatLoseTimes = maxHeartbeatLoseTimes; + } + + public void setConnectTimeout(int connectTimeout) { + this.connectTimeout = connectTimeout; + } + + public void setMaxResponseDataMb(int maxResponseDataMb) { + this.maxResponseDataMb = maxResponseDataMb; + } + + public void setReconnectionManager(AbsReconnection reconnectionManager) { + this.reconnectionManager = reconnectionManager; + } + + public void setEasySSLConfig(SocketSSLConfig easySSLConfig) { + this.easySSLConfig = easySSLConfig; + } + + public void setSocketFactory(SocketFactory socketFactory) { + this.socketFactory = socketFactory; + } + + public void setCallbackIDFactory(CallbackIDFactory callbackIDFactory) { + this.callbackIDFactory = callbackIDFactory; + } + + public void setRequestTimeout(long requestTimeout) { + this.requestTimeout = requestTimeout; + } + + public void setOpenRequestTimeout(boolean openRequestTimeout) { + isOpenRequestTimeout = openRequestTimeout; + } + + public SocketAddress getSocketAddress() { + return socketAddress; + } + + public SocketAddress getBackupAddress() { + return backupAddress; + } + +} diff --git a/easysocket/src/main/java/com/easysocket/config/SocketFactory.java b/easysocket/src/main/java/com/easysocket/config/SocketFactory.java new file mode 100644 index 0000000..1aebab0 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/config/SocketFactory.java @@ -0,0 +1,14 @@ +package com.easysocket.config; + +import com.easysocket.entity.SocketAddress; + +import java.net.Socket; + +/** + * Author:Alex + * Date:2019/5/31 + * Note:socket工厂 + */ +public abstract class SocketFactory { + public abstract Socket createSocket(SocketAddress info, EasySocketOptions options) throws Exception; +} diff --git a/easysocket/src/main/java/com/easysocket/config/SocketSSLConfig.java b/easysocket/src/main/java/com/easysocket/config/SocketSSLConfig.java new file mode 100644 index 0000000..1000cce --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/config/SocketSSLConfig.java @@ -0,0 +1,81 @@ +package com.easysocket.config; + +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +/** + * socket的ssl配置 + */ + +public class SocketSSLConfig { + /** + * 安全协议名称(缺省为SSL) + */ + private String mProtocol; + /** + * 信任证书管理器(缺省为X509) + */ + private TrustManager[] mTrustManagers; + /** + * 证书秘钥管理器(缺省为null) + */ + private KeyManager[] mKeyManagers; + /** + * 自定义SSLFactory(缺省为null) + */ + private SSLSocketFactory mCustomSSLFactory; + + private SocketSSLConfig() { + + } + + public static class Builder { + + private SocketSSLConfig mConfig; + + public Builder() { + mConfig = new SocketSSLConfig(); + } + + public Builder setProtocol(String protocol) { + mConfig.mProtocol = protocol; + return this; + } + + public Builder setTrustManagers(TrustManager[] trustManagers) { + mConfig.mTrustManagers = trustManagers; + return this; + } + + public Builder setKeyManagers(KeyManager[] keyManagers) { + mConfig.mKeyManagers = keyManagers; + return this; + } + + public Builder setCustomSSLFactory(SSLSocketFactory customSSLFactory) { + mConfig.mCustomSSLFactory = customSSLFactory; + return this; + } + + public SocketSSLConfig build() { + return mConfig; + } + } + + public KeyManager[] getKeyManagers() { + return mKeyManagers; + } + + public String getProtocol() { + return mProtocol; + } + + public TrustManager[] getTrustManagers() { + return mTrustManagers; + } + + public SSLSocketFactory getCustomSSLFactory() { + return mCustomSSLFactory; + } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/action/IOAction.java b/easysocket/src/main/java/com/easysocket/connection/action/IOAction.java new file mode 100644 index 0000000..4caf13a --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/action/IOAction.java @@ -0,0 +1,11 @@ +package com.easysocket.connection.action; + +/** + * Author:Alex + * Date:2019/6/3 + * Note: + */ +public interface IOAction { + // 收到消息响应 + String ACTION_READ_COMPLETE = "action_read_complete"; +} diff --git a/easysocket/src/main/java/com/easysocket/connection/action/SocketAction.java b/easysocket/src/main/java/com/easysocket/connection/action/SocketAction.java new file mode 100644 index 0000000..f9619c8 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/action/SocketAction.java @@ -0,0 +1,15 @@ +package com.easysocket.connection.action; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public interface SocketAction { + // 连接成功 + String ACTION_CONN_SUCCESS="action_conn_success"; + // 连接失败 + String ACTION_CONN_FAIL="action_conn_fail"; + // 断开连接 + String ACTION_DISCONNECTION="action_disconnection"; +} diff --git a/easysocket/src/main/java/com/easysocket/connection/action/SocketStatus.java b/easysocket/src/main/java/com/easysocket/connection/action/SocketStatus.java new file mode 100644 index 0000000..3918289 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/action/SocketStatus.java @@ -0,0 +1,17 @@ +package com.easysocket.connection.action; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:连接状态 + */ +public interface SocketStatus { + // 已断开连接 + int SOCKET_DISCONNECTED = 0; + // 正在连接 + int SOCKET_CONNECTING = 1; + // 已连接 + int SOCKET_CONNECTED = 2; + // 正在断开连接 + int SOCKET_DISCONNECTING =3; +} diff --git a/easysocket/src/main/java/com/easysocket/connection/connect/SuperConnection.java b/easysocket/src/main/java/com/easysocket/connection/connect/SuperConnection.java new file mode 100644 index 0000000..ca7cc82 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/connect/SuperConnection.java @@ -0,0 +1,340 @@ +package com.easysocket.connection.connect; + +import com.easysocket.EasySocket; +import com.easysocket.callback.SuperCallBack; +import com.easysocket.config.EasySocketOptions; +import com.easysocket.connection.action.SocketAction; +import com.easysocket.connection.action.SocketStatus; +import com.easysocket.connection.dispatcher.CallbackResponseDispatcher; +import com.easysocket.connection.dispatcher.SocketActionDispatcher; +import com.easysocket.connection.heartbeat.HeartManager; +import com.easysocket.connection.iowork.IOManager; +import com.easysocket.connection.reconnect.AbsReconnection; +import com.easysocket.entity.SocketAddress; +import com.easysocket.entity.basemsg.SuperCallbackSender; +import com.easysocket.exception.NotNullException; +import com.easysocket.interfaces.config.IConnectionSwitchListener; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.ISocketActionListener; +import com.easysocket.utils.LogUtil; +import com.easysocket.utils.Utils; + +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Author:Alex + * Date:2019/5/29 + * Note:socket连接的超类 + */ +public abstract class SuperConnection implements IConnectionManager { + + /** + * 连接状态,初始值为断开连接 + */ + protected final AtomicInteger connectionStatus = new AtomicInteger(SocketStatus.SOCKET_DISCONNECTED); + /** + * 连接线程 + */ + private ExecutorService connExecutor; + /** + * socket地址信息 + */ + protected SocketAddress socketAddress; + /** + * socket行为分发器 + */ + private SocketActionDispatcher actionDispatcher; + /** + * 重连管理器 + */ + private AbsReconnection reconnection; + /** + * io管理器 + */ + private IOManager ioManager; + /** + * 心跳管理器 + */ + private HeartManager heartManager; + /** + * 配置信息 + */ + protected EasySocketOptions socketOptions; + /** + * socket回调消息的分发器 + */ + private CallbackResponseDispatcher callbackResponseDispatcher; + /** + * 连接切换的监听 + */ + private IConnectionSwitchListener connectionSwitchListener; + + public SuperConnection(SocketAddress socketAddress) { + this.socketAddress = socketAddress; + actionDispatcher = new SocketActionDispatcher(this, socketAddress); + } + + @Override + public void subscribeSocketAction(ISocketActionListener iSocketActionListener) { + actionDispatcher.subscribe(iSocketActionListener); + } + + @Override + public void unSubscribeSocketAction(ISocketActionListener iSocketActionListener) { + actionDispatcher.unsubscribe(iSocketActionListener); + } + + @Override + public synchronized IConnectionManager setOptions(EasySocketOptions socketOptions) { + if (socketOptions == null) return this; + + this.socketOptions = socketOptions; + + if (ioManager != null) + ioManager.setOptions(socketOptions); + + if (heartManager != null) + heartManager.setOptions(socketOptions); + + if (callbackResponseDispatcher != null) + callbackResponseDispatcher.setSocketOptions(socketOptions); + + // 更改了重连器 + if (reconnection != null && !reconnection.equals(socketOptions.getReconnectionManager())) { + reconnection.detach(); + reconnection = socketOptions.getReconnectionManager(); + reconnection.attach(this); + } + return this; + } + + @Override + public EasySocketOptions getOptions() { + return socketOptions; + } + + @Override + public synchronized void connect() { + LogUtil.d("---> socket开始连接"); + if (socketAddress.getIp() == null) { + throw new NotNullException("请检查是否设置了IP地址"); + } + // 正在连接 + connectionStatus.set(SocketStatus.SOCKET_CONNECTING); + + // 心跳管理器 + if (heartManager == null) { + heartManager = new HeartManager(this, actionDispatcher); + } + + // 重连管理器 + if (reconnection != null) { + reconnection.detach(); + } + reconnection = socketOptions.getReconnectionManager(); + if (reconnection != null) { + reconnection.attach(this); + } + + // 开启分发消息线程 + if (actionDispatcher != null) { + actionDispatcher.startDispatchThread(); + } + + // 开启连接线程 + if (connExecutor == null || connExecutor.isShutdown()) { + // 核心线程数为0,非核心线程数可以有Integer.MAX_VALUE个,存活时间为60秒,适合于在不断进行连接的情况下,避免重复创建和销毁线程 + connExecutor = Executors.newCachedThreadPool(); + } + // 执行连接任务 + connExecutor.execute(connTask); + } + + @Override + public synchronized void disconnect(boolean isNeedReconnect) { + // 判断当前socket的连接状态 + if (connectionStatus.get() == SocketStatus.SOCKET_DISCONNECTED) { + return; + } + // 正在重连中 + if (isNeedReconnect && reconnection.isReconning()) { + return; + } + // 正在断开连接 + connectionStatus.set(SocketStatus.SOCKET_DISCONNECTING); + + // 开启断开连接线程 + String info = socketAddress.getIp() + " : " + socketAddress.getPort(); + Thread disconnThread = new DisconnectThread(isNeedReconnect, "disconn thread:" + info); + disconnThread.setDaemon(true); + disconnThread.start(); + } + + /** + * 断开连接线程 + */ + private class DisconnectThread extends Thread { + boolean isNeedReconnect; // 当前连接的断开是否需要自动重连 + + public DisconnectThread(boolean isNeedReconnect, String name) { + super(name); + this.isNeedReconnect = isNeedReconnect; + } + + @Override + public void run() { + try { + // 关闭io线程 + if (ioManager != null) + ioManager.closeIO(); + // 关闭回调分发器线程 + if (callbackResponseDispatcher != null) + callbackResponseDispatcher.shutdownThread(); + // 关闭连接线程 + if (connExecutor != null && !connExecutor.isShutdown()) { + connExecutor.shutdown(); + connExecutor = null; + } + // 关闭连接 + closeConnection(); + LogUtil.d("---> 关闭socket连接"); + connectionStatus.set(SocketStatus.SOCKET_DISCONNECTED); + actionDispatcher.dispatchAction(SocketAction.ACTION_DISCONNECTION, new Boolean(isNeedReconnect)); + } catch (IOException e) { + // 断开连接发生异常 + e.printStackTrace(); + } + } + } + + // 连接任务 + private Runnable connTask = new Runnable() { + @Override + public void run() { + try { + openConnection(); + } catch (Exception e) { + // 连接异常 + e.printStackTrace(); + LogUtil.d("---> socket连接失败"); + connectionStatus.set(SocketStatus.SOCKET_DISCONNECTED); + // 第二个参数指需要重连 + actionDispatcher.dispatchAction(SocketAction.ACTION_CONN_FAIL, new Boolean(true)); + + } + } + }; + + /** + * 连接成功 + */ + protected void onConnectionOpened() { + LogUtil.d("---> socket连接成功"); + actionDispatcher.dispatchAction(SocketAction.ACTION_CONN_SUCCESS); + connectionStatus.set(SocketStatus.SOCKET_CONNECTED); + openSocketManager(); + } + + // 开启socket相关管理器 + private void openSocketManager() { + if (callbackResponseDispatcher == null) + callbackResponseDispatcher = new CallbackResponseDispatcher(this); + if (ioManager == null) { + ioManager = new IOManager(this, actionDispatcher); + } + ioManager.startIO(); + + // 启动相关线程 + callbackResponseDispatcher.engineThread(); + ioManager.startIO(); + } + + // 切换了主机IP和端口 + @Override + public synchronized void switchHost(SocketAddress socketAddress) { + if (socketAddress != null) { + SocketAddress oldAddress = this.socketAddress; + this.socketAddress = socketAddress; + + if (actionDispatcher != null) + actionDispatcher.setSocketAddress(socketAddress); + // 切换主机 + if (connectionSwitchListener != null) { + connectionSwitchListener.onSwitchConnectionInfo(this, oldAddress, socketAddress); + } + } + + } + + public void setOnConnectionSwitchListener(IConnectionSwitchListener listener) { + connectionSwitchListener = listener; + } + + @Override + public boolean isConnectViable() { + // 当前socket是否处于可连接状态 + return Utils.isNetConnected(EasySocket.getInstance().getContext()) && connectionStatus.get() == SocketStatus.SOCKET_DISCONNECTED; + } + + @Override + public int getConnectionStatus() { + return connectionStatus.get(); + } + + /** + * 打开连接 + * + * @throws IOException + */ + protected abstract void openConnection() throws Exception; + + /** + * 关闭连接 + * + * @throws IOException + */ + protected abstract void closeConnection() throws IOException; + + /** + * 发送bytes数据 + * + * @param bytes + * @return + */ + private IConnectionManager sendBytes(byte[] bytes) { + if (ioManager == null || connectionStatus.get() != SocketStatus.SOCKET_CONNECTED) { + return this; + } + ioManager.sendBytes(bytes); + return this; + } + + @Override + public void onCallBack(SuperCallBack callBack) { + callbackResponseDispatcher.addSocketCallback(callBack); + } + + + @Override + public synchronized IConnectionManager upBytes(byte[] bytes) { + sendBytes(bytes); + return this; + } + + @Override + public synchronized IConnectionManager upCallbackMessage(SuperCallbackSender sender) { + callbackResponseDispatcher.checkCallbackSender(sender); + // 发送 + sendBytes(sender.pack()); + return this; + } + + + @Override + public HeartManager getHeartManager() { + return heartManager; + } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/connect/TcpConnection.java b/easysocket/src/main/java/com/easysocket/connection/connect/TcpConnection.java new file mode 100644 index 0000000..31d83ae --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/connect/TcpConnection.java @@ -0,0 +1,141 @@ +package com.easysocket.connection.connect; + +import com.easysocket.config.DefaultX509ProtocolTrustManager; +import com.easysocket.config.SocketSSLConfig; +import com.easysocket.connection.action.SocketStatus; +import com.easysocket.entity.SocketAddress; +import com.easysocket.utils.LogUtil; +import com.easysocket.utils.Utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.SecureRandom; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; + +/** + * Author:Alex + * Date:2019/5/29 + * Note:tcp连接 + */ +public class TcpConnection extends SuperConnection { + /** + * socket对象 + */ + private Socket socket; + + public TcpConnection(SocketAddress socketAddress) { + super(socketAddress); + } + + @Override + protected void openConnection() throws Exception { + try { + socket = getSocket(); + } catch (Exception e) { + e.printStackTrace(); + connectionStatus.set(SocketStatus.SOCKET_DISCONNECTED); // 设置为未连接 + throw new RuntimeException("创建socket失败"); + } + + // 进行socket连接 + socket.connect(new InetSocketAddress(socketAddress.getIp(), socketAddress.getPort()), socketOptions.getConnectTimeout()); + + // 关闭Nagle算法,无论TCP数据报大小,立即发送 + socket.setTcpNoDelay(true); + // 连接已经打开 + if (socket.isConnected() && !socket.isClosed()) { + onConnectionOpened(); + } + } + + @Override + protected void closeConnection() throws IOException { + if (socket != null) + socket.close(); + } + + /** + * 根据配置信息获取对应的socket + * + * @return + */ + private synchronized Socket getSocket() throws Exception { + // 自定义的socket生成工厂 + if (socketOptions.getSocketFactory() != null) { + return socketOptions.getSocketFactory().createSocket(socketAddress, socketOptions); + } + // 默认操作 + SocketSSLConfig config = socketOptions.getEasySSLConfig(); + if (config == null) { + return new Socket(); + } + // 获取SSL配置工厂 + SSLSocketFactory factory = config.getCustomSSLFactory(); + if (factory == null) { + String protocol = "SSL"; + if (!Utils.isStringEmpty(config.getProtocol())) { + protocol = config.getProtocol(); + } + + TrustManager[] trustManagers = config.getTrustManagers(); + if (trustManagers == null || trustManagers.length == 0) { + // 缺省信任所有证书 + trustManagers = new TrustManager[]{new DefaultX509ProtocolTrustManager()}; + } + + try { + SSLContext sslContext = SSLContext.getInstance(protocol); + sslContext.init(config.getKeyManagers(), trustManagers, new SecureRandom()); + return sslContext.getSocketFactory().createSocket(); + } catch (Exception e) { + if (socketOptions.isDebug()) { + e.printStackTrace(); + } + LogUtil.e(e.getMessage()); + return new Socket(); + } + + } else { + try { + return factory.createSocket(); + } catch (IOException e) { + if (socketOptions.isDebug()) { + e.printStackTrace(); + } + LogUtil.e(e.getMessage()); + return new Socket(); + } + } + } + + + @Override + public InputStream getInputStream() { + if (socket != null && socket.isConnected() && !socket.isClosed()) { + try { + return socket.getInputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + @Override + public OutputStream getOutStream() { + if (socket != null && socket.isConnected() && !socket.isClosed()) { + try { + return socket.getOutputStream(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/dispatcher/CallbackResponseDispatcher.java b/easysocket/src/main/java/com/easysocket/connection/dispatcher/CallbackResponseDispatcher.java new file mode 100644 index 0000000..c0e8b90 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/dispatcher/CallbackResponseDispatcher.java @@ -0,0 +1,187 @@ +package com.easysocket.connection.dispatcher; + +import com.easysocket.callback.SuperCallBack; +import com.easysocket.config.EasySocketOptions; +import com.easysocket.entity.OriginReadData; +import com.easysocket.entity.SocketAddress; +import com.easysocket.entity.basemsg.SuperCallbackSender; +import com.easysocket.exception.RequestTimeOutException; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.SocketActionListener; +import com.easysocket.utils.LogUtil; +import com.easysocket.utils.Utils; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.DelayQueue; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:回调消息分发器 + */ +public class CallbackResponseDispatcher { + /** + * 保存发送的每个回调消息的监听实例,key为回调标识callbackId,这样回调消息有反馈的时候,就可以找到并调用 + * 对应的监听对象 + */ + private Map callbacks = new HashMap<>(); + /** + * 保存需要进行超时检测的请求,这是一个延时队列,元素超时的时候会被取出来 + */ + private DelayQueue timeoutQueue = new DelayQueue<>(); + /** + * 超时检测的线程管理器 + */ + private ExecutorService timeoutExecutor; + + /** + * 连接管理 + */ + IConnectionManager connectionManager; + + private EasySocketOptions socketOptions; + + + public CallbackResponseDispatcher(IConnectionManager connectionManager) { + this.connectionManager = connectionManager; + socketOptions = connectionManager.getOptions(); + // 注册监听 + connectionManager.subscribeSocketAction(socketActionListener); + // 开始超时检测线程 + engineThread(); + } + + /** + * 设置socketoptions + * + * @param socketOptions + */ + public void setSocketOptions(EasySocketOptions socketOptions) { + this.socketOptions = socketOptions; + } + + /** + * 超时检测线程 + */ + public void engineThread() { + if (timeoutExecutor == null || timeoutExecutor.isShutdown()) { + timeoutExecutor = Executors.newSingleThreadExecutor(); + timeoutExecutor.execute(new Runnable() { + @Override + public void run() { + try { + // 只有超时的元素才会被取出,没有的话会被等待 + timeoutItem item = timeoutQueue.take(); + if (item != null) { + SuperCallBack callBack = callbacks.remove(item.callbackId); + if (callBack != null) + callBack.onError(new RequestTimeOutException("request timeout")); + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + // 继续循环 + if (timeoutExecutor != null && !timeoutExecutor.isShutdown()) { + run(); + } + } + }); + } + } + + /** + * 关闭线程 + */ + public void shutdownThread() { + if (timeoutExecutor != null && !timeoutExecutor.isShutdown()) { + // shutdown和shutdownNow的主要区别是前者中断未执行的线程,后者中断所有线程 + timeoutExecutor.shutdownNow(); + timeoutExecutor = null; + } + + } + + /** + * socket行为监听,重写反馈消息的回调方法 + */ + private SocketActionListener socketActionListener = new SocketActionListener() { + @Override + public void onSocketResponse(SocketAddress socketAddress, OriginReadData originReadData) { + if (callbacks.size() == 0) return; + if (socketOptions.getCallbackIDFactory() == null) return; + // 获取回调ID + String callbackID = socketOptions.getCallbackIDFactory().getCallbackID(originReadData); + if (callbackID != null) { + // 获取callbackID对应的callback + SuperCallBack callBack = callbacks.get(callbackID); + if (callBack != null) { + // 回调 + callBack.onSuccess(originReadData); + callbacks.remove(callbackID); // 移除完成任务的callback + LogUtil.d("移除的callbackId-->" + callbackID); + } + } + } + }; + + + /** + * 每发一条回调消息都要在这里添加监听对象 + * + * @param superCallBack + */ + public void addSocketCallback(SuperCallBack superCallBack) { + callbacks.put(superCallBack.getCallbackId(), superCallBack); + // 放入延时队列 + long delayTime = socketOptions == null ? + EasySocketOptions.getDefaultOptions().getRequestTimeout() : socketOptions.getRequestTimeout(); + timeoutQueue.add(new timeoutItem(superCallBack.getCallbackId(), delayTime, TimeUnit.MILLISECONDS)); + } + + /** + * 延时队列的item + */ + class timeoutItem implements Delayed { + + String callbackId; // 当前callback的callbackId + long executeTime; // 触发时间 + + public timeoutItem(String callbackId, long delayTime, TimeUnit timeUnit) { + this.callbackId = callbackId; + this.executeTime = System.currentTimeMillis() + (delayTime > 0 ? timeUnit.toMillis(delayTime) : 0); + } + + @Override + public long getDelay(TimeUnit unit) { + return executeTime - System.currentTimeMillis(); + } + + @Override + public int compareTo(Delayed o) { + return (int) (this.getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); + } + } + + /** + * 同一个消息发送多次,callbackId是不能一样的,所以这里要先check一下,否则服务端反馈的时候,客户端接收就会乱套 + * + * @param callbackSender + * @return + */ + public void checkCallbackSender(SuperCallbackSender callbackSender) { + + Utils.checkNotNull(socketOptions.getCallbackIDFactory(), "要想实现EasySocket的回调功能,CallbackIdFactory不能为null," + + "请实现一个CallbackIdFactory并在初始化的时候通过EasySocketOptions的setCallbackIdFactory进行配置"); + String callbackId = callbackSender.getCallbackId(); + // 同一个消息发送两次以上,callbackId是不能一样的,否则服务端反馈的时候,客户端接收就会乱套 + if (callbacks.containsKey(callbackId)) { + callbackSender.generateCallbackId(); + } + } + +} diff --git a/easysocket/src/main/java/com/easysocket/connection/dispatcher/MainThreadExecutor.java b/easysocket/src/main/java/com/easysocket/connection/dispatcher/MainThreadExecutor.java new file mode 100644 index 0000000..37e8579 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/dispatcher/MainThreadExecutor.java @@ -0,0 +1,21 @@ +package com.easysocket.connection.dispatcher; + +import android.os.Handler; +import android.os.Looper; + +import java.util.concurrent.Executor; + +/** + * Author:Mapogo + * Date:2020/4/8 + * Note:切到主线程 + */ +public class MainThreadExecutor implements Executor { + + private final Handler handler = new Handler(Looper.getMainLooper()); + + @Override + public void execute(Runnable r) { + handler.post(r); + } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/dispatcher/SocketActionDispatcher.java b/easysocket/src/main/java/com/easysocket/connection/dispatcher/SocketActionDispatcher.java new file mode 100644 index 0000000..f0a61cf --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/dispatcher/SocketActionDispatcher.java @@ -0,0 +1,221 @@ +package com.easysocket.connection.dispatcher; + +import com.easysocket.EasySocket; +import com.easysocket.entity.OriginReadData; +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.ISocketActionDispatch; +import com.easysocket.interfaces.conn.ISocketActionListener; +import com.easysocket.utils.Utils; + +import java.io.Serializable; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.LinkedBlockingQueue; + +import static com.easysocket.connection.action.IOAction.ACTION_READ_COMPLETE; +import static com.easysocket.connection.action.SocketAction.ACTION_CONN_FAIL; +import static com.easysocket.connection.action.SocketAction.ACTION_CONN_SUCCESS; +import static com.easysocket.connection.action.SocketAction.ACTION_DISCONNECTION; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:socket行为分发器 + */ +public class SocketActionDispatcher implements ISocketActionDispatch { + /** + * 连接地址 + */ + private SocketAddress socketAddress; + /** + * 连接器 + */ + private IConnectionManager connectionManager; + /** + * 回调监听集合 + */ + private List actionListeners = new ArrayList<>(); + /** + * 处理socket行为的线程 + */ + private Thread actionThread; + /** + * 是否停止分发 + */ + private boolean isStop; + + /** + * 事件消费队列 + */ + private final LinkedBlockingQueue socketActions = new LinkedBlockingQueue(); + /** + * 切换到UI线程 + */ + private MainThreadExecutor mainThreadExecutor = new MainThreadExecutor(); + + + public SocketActionDispatcher(IConnectionManager connectionManager, SocketAddress socketAddress) { + this.socketAddress = socketAddress; + this.connectionManager = connectionManager; + } + + public void setSocketAddress(SocketAddress info) { + socketAddress = info; + } + + + @Override + public void dispatchAction(String action) { + dispatchAction(action, null); + } + + @Override + public void dispatchAction(String action, Serializable serializable) { + // 将接收到的socket行为封装入列 + ActionBean actionBean = new ActionBean(action, serializable, this); + socketActions.offer(actionBean); + } + + @Override + public void subscribe(ISocketActionListener iSocketActionListener) { + if (iSocketActionListener != null && !actionListeners.contains(iSocketActionListener)) { + actionListeners.add(iSocketActionListener); + } + } + + @Override + public void unsubscribe(ISocketActionListener iSocketActionListener) { + actionListeners.remove(iSocketActionListener); + } + + /** + * 分发线程 + */ + private class DispatchThread extends Thread { + + public DispatchThread() { + super("dispatch thread"); + } + + @Override + public void run() { + // 循环处理socket的行为信息 + while (!isStop) { + try { + ActionBean actionBean = socketActions.take(); + if (actionBean != null && actionBean.mDispatcher != null) { + SocketActionDispatcher actionDispatcher = actionBean.mDispatcher; + List copyListeners = new ArrayList<>(actionDispatcher.actionListeners); + Iterator listeners = copyListeners.iterator(); + // 通知所有监听者 + while (listeners.hasNext()) { + ISocketActionListener listener = listeners.next(); + actionDispatcher.dispatchActionToListener(actionBean.mAction, actionBean.arg, listener); + } + } + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + } + } + + /** + * socket行为的封装 + */ + protected static class ActionBean { + + public ActionBean(String action, Serializable arg, SocketActionDispatcher dispatcher) { + mAction = action; + this.arg = arg; + mDispatcher = dispatcher; + } + + String mAction = ""; + Serializable arg; + SocketActionDispatcher mDispatcher; + } + + /** + * 分发行为给监听者 + * + * @param action + * @param content + * @param actionListener + */ + private void dispatchActionToListener(String action, final Serializable content, final ISocketActionListener actionListener) { + switch (action) { + + case ACTION_CONN_SUCCESS: // 连接成功 + mainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + actionListener.onSocketConnSuccess(socketAddress); + } + }); + + break; + + case ACTION_CONN_FAIL: // 连接失败 + mainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + actionListener.onSocketConnFail(socketAddress, ((Boolean) content).booleanValue()); + } + }); + + break; + + case ACTION_DISCONNECTION: // 连接断开 + mainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + actionListener.onSocketDisconnect(socketAddress, ((Boolean) content).booleanValue()); + // 不需要重连,则释放资源 + if (!(Boolean) content) { + stopDispatchThread(); + } + } + }); + break; + + case ACTION_READ_COMPLETE: // 读取数据完成 + mainThreadExecutor.execute(new Runnable() { + @Override + public void run() { + // response有三种形式 + actionListener.onSocketResponse(socketAddress, (OriginReadData) content); + byte[] data = Utils.concatBytes(((OriginReadData) content).getHeaderData(), ((OriginReadData) content).getBodyBytes()); + actionListener.onSocketResponse(socketAddress, new String(data, Charset.forName(EasySocket.getInstance().getDefOptions().getCharsetName()))); + actionListener.onSocketResponse(socketAddress, data); + } + }); + break; + } + } + + // 开始分发线程 + @Override + public void startDispatchThread() { + isStop = false; + if (actionThread == null) { + actionThread = new DispatchThread(); + actionThread.start(); + } + } + + @Override + public void stopDispatchThread() { + if (actionThread != null && actionThread.isAlive() && !actionThread.isInterrupted()) { + socketActions.clear(); + //actionListeners.clear(); + isStop = true; + actionThread.interrupt(); + actionThread = null; + } + } + +} diff --git a/easysocket/src/main/java/com/easysocket/connection/heartbeat/HeartManager.java b/easysocket/src/main/java/com/easysocket/connection/heartbeat/HeartManager.java new file mode 100644 index 0000000..9ae8042 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/heartbeat/HeartManager.java @@ -0,0 +1,181 @@ +package com.easysocket.connection.heartbeat; + +import com.easysocket.config.EasySocketOptions; +import com.easysocket.entity.OriginReadData; +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.config.IOptions; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.IHeartManager; +import com.easysocket.interfaces.conn.ISocketActionDispatch; +import com.easysocket.interfaces.conn.SocketActionListener; + +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Author:Alex + * Date:2019/12/8 + * Note:心跳包检测管理器 + */ +public class HeartManager extends SocketActionListener implements IOptions, IHeartManager { + + /** + * 连接器 + */ + private IConnectionManager connectionManager; + /** + * 连接参数 + */ + private EasySocketOptions socketOptions; + /** + * 客户端心跳包 + */ + private byte[] clientHeart; + /** + * 心跳包发送线程 + */ + private ScheduledExecutorService heartExecutor; + /** + * 记录心跳的失联次数 + */ + private AtomicInteger loseTimes = new AtomicInteger(-1); + /** + * 心跳频率 + */ + private long freq; + /** + * 是否激活了心跳 + */ + private boolean isActivate; + + + /** + * 心跳包接收监听 + */ + private HeartbeatListener heartbeatListener; + + + public HeartManager(IConnectionManager iConnectionManager, ISocketActionDispatch actionDispatch) { + this.connectionManager = iConnectionManager; + socketOptions = iConnectionManager.getOptions(); + actionDispatch.subscribe(this); // 注册监听 + } + + /** + * 心跳发送任务 + */ + private final Runnable beatTask = new Runnable() { + @Override + public void run() { + // 心跳丢失次数判断,心跳包丢失了一定的次数则会进行socket的断开重连 + if (socketOptions.getMaxHeartbeatLoseTimes() != -1 + && loseTimes.incrementAndGet() >= socketOptions.getMaxHeartbeatLoseTimes()) { + // 断开重连 + connectionManager.disconnect(true); + resetLoseTimes(); + } else { // 发送心跳包 + connectionManager.upBytes(clientHeart); + } + } + }; + + + @Override + public void startHeartbeat(byte[] clientHeart, HeartbeatListener listener) { + this.clientHeart = clientHeart; + this.heartbeatListener = listener; + isActivate = true; + openThread(); + } + + + // 启动心跳线程 + private void openThread() { + freq = socketOptions.getHeartbeatFreq(); // 心跳频率 + // 启动线程发送心跳 + if (heartExecutor == null || heartExecutor.isShutdown()) { + heartExecutor = Executors.newSingleThreadScheduledExecutor(); + heartExecutor.scheduleWithFixedDelay(beatTask, 0, freq, TimeUnit.MILLISECONDS); + } + } + + /** + * 停止心跳发送 + */ + @Override + public void stopHeartbeat() { + isActivate = false; + closeThread(); + } + + // 停止心跳线程 + private void closeThread() { + if (heartExecutor != null && !heartExecutor.isShutdown()) { + heartExecutor.shutdownNow(); + heartExecutor = null; + resetLoseTimes(); // 重置 + } + } + + @Override + public void onReceiveHeartBeat() { + resetLoseTimes(); + } + + + private void resetLoseTimes() { + loseTimes.set(-1); + } + + @Override + public void onSocketConnSuccess(SocketAddress socketAddress) { + if (isActivate) { + openThread(); + } + } + + @Override + public void onSocketConnFail(SocketAddress socketAddress, boolean isNeedReconnect) { + // 如果不需要重连,则停止心跳频率线程 + if (!isNeedReconnect) { + closeThread(); + } + } + + @Override + public void onSocketDisconnect(SocketAddress socketAddress, boolean isNeedReconnect) { + // 如果不需要重连,则停止心跳检测 + if (!isNeedReconnect) { + closeThread(); + } + } + + @Override + public void onSocketResponse(SocketAddress socketAddress, OriginReadData originReadData) { + if (heartbeatListener != null && heartbeatListener.isServerHeartbeat(originReadData)) { + // 收到服务器心跳 + onReceiveHeartBeat(); + } + } + + @Override + public Object setOptions(EasySocketOptions socketOptions) { + this.socketOptions = socketOptions; + freq = socketOptions.getHeartbeatFreq(); + freq = freq < 1000 ? 1000 : freq; // 不能小于一秒 + return this; + } + + @Override + public EasySocketOptions getOptions() { + return socketOptions; + } + + public interface HeartbeatListener { + // 是否为服务器心跳 + boolean isServerHeartbeat(OriginReadData orginReadData); + } + +} diff --git a/easysocket/src/main/java/com/easysocket/connection/iowork/EasyReader.java b/easysocket/src/main/java/com/easysocket/connection/iowork/EasyReader.java new file mode 100644 index 0000000..a80772d --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/iowork/EasyReader.java @@ -0,0 +1,327 @@ +package com.easysocket.connection.iowork; + +import com.easysocket.config.EasySocketOptions; +import com.easysocket.connection.action.IOAction; +import com.easysocket.entity.OriginReadData; +import com.easysocket.exception.ReadRecoverableExeption; +import com.easysocket.exception.ReadUnrecoverableException; +import com.easysocket.interfaces.config.IMessageProtocol; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.ISocketActionDispatch; +import com.easysocket.interfaces.io.IReader; +import com.easysocket.utils.LogUtil; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public class EasyReader implements IReader { + /** + * 输入流 + */ + private InputStream inputStream; + /** + * 读取原始数据的缓存空间 + */ + private ByteBuffer originBuf; + /** + * socket行为分发器 + */ + private ISocketActionDispatch actionDispatch; + /** + * 连接器 + */ + private IConnectionManager connectionManager; + /** + * 连接参数 + */ + private EasySocketOptions socketOptions; + + /** + * 读数据时,余留数据的缓存 + */ + private ByteBuffer remainingBuf; + /** + * 读数据线程 + */ + private Thread readerThread; + /** + * 是否停止线程 + */ + private boolean stopThread; + + public EasyReader(IConnectionManager connectionManager, ISocketActionDispatch actionDispatch) { + this.actionDispatch = actionDispatch; + this.connectionManager = connectionManager; + socketOptions = connectionManager.getOptions(); + } + + @Override + public void read() throws IOException, ReadRecoverableExeption, ReadUnrecoverableException { + OriginReadData originalData = new OriginReadData(); + IMessageProtocol messageProtocol = socketOptions.getMessageProtocol(); + // 消息协议为null,则直接读原始消息,不建议这样使用,因为会发生黏包、分包的问题 + if (messageProtocol == null) { + readOriginDataFromSteam(originalData); + return; + } + + // 定义了消息协议 + int headerLength = messageProtocol.getHeaderLength(); // 包头长度 + ByteBuffer headBuf = ByteBuffer.allocate(headerLength); // 包头数据的buffer + headBuf.order(socketOptions.getReadOrder()); + + /*1、读 header=====>>>*/ + if (remainingBuf != null) { // 有余留 + // flip方法:一般从Buffer读数据前调用,将limit设置为当前position,将position设置为0,在读数据时,limit代表可读数据的有效长度 + remainingBuf.flip(); + // 读余留数据的长度 + int length = Math.min(remainingBuf.remaining(), headerLength); + // 读入余留数据 + headBuf.put(remainingBuf.array(), 0, length); + + if (length < headerLength) { // 余留数据小于一个header + // there are no data left + remainingBuf = null; + // 从stream中读剩下的header数据 + readHeaderFromSteam(headBuf, headerLength - length); + } else { + // 移动开始读数据的指针 + remainingBuf.position(headerLength); + } + } else { // 无余留 + // 从stream读取一个完整的 header + readHeaderFromSteam(headBuf, headBuf.capacity()); + } + + // 保存header + originalData.setHeaderData(headBuf.array()); + + /*2、读 body=====>>>*/ + int bodyLength = messageProtocol.getBodyLength(originalData.getHeaderData(), socketOptions.getReadOrder()); + if (bodyLength > 0) { + if (bodyLength > socketOptions.getMaxResponseDataMb() * 1024 * 1024) { + throw new ReadUnrecoverableException("服务器返回的单次数据超过了规定的最大值,可能你的Socket消息协议不对,一般消息格式" + + "为:Header+Body,其中Header保存消息长度和类型等,Body保存消息内容,请规范好你的协议"); + } + // 分配空间 + ByteBuffer bodyBuf = ByteBuffer.allocate(bodyLength); + bodyBuf.order(socketOptions.getReadOrder()); + + // 有余留 + if (remainingBuf != null) { + int bodyStartPosition = remainingBuf.position(); + + int length = Math.min(remainingBuf.remaining(), bodyLength); + // 读length大小的余留数据 + bodyBuf.put(remainingBuf.array(), bodyStartPosition, length); + // 移动position位置 + remainingBuf.position(bodyStartPosition + length); + + // 读的余留数据刚好等于一个body + if (length == bodyLength) { + if (remainingBuf.remaining() > 0) { // 余留数据未读完 + ByteBuffer temp = ByteBuffer.allocate(remainingBuf.remaining()); + temp.order(socketOptions.getReadOrder()); + temp.put(remainingBuf.array(), remainingBuf.position(), remainingBuf.remaining()); + remainingBuf = temp; + } else { // there are no data left + remainingBuf = null; + } + + // 保存body + originalData.setBodyData(bodyBuf.array()); + + LogUtil.d("Socket收到数据-->" + originalData.getBodyString()); + // 分发数据 + actionDispatch.dispatchAction(IOAction.ACTION_READ_COMPLETE, originalData); + + /*return读取结束*/ + return; + + } else { // there are no data left in buffer and some data pieces in channel + remainingBuf = null; + } + } + // 无余留,则从stream中读 + readBodyFromStream(bodyBuf); + // 保存body到originalData + originalData.setBodyData(bodyBuf.array()); + + } else if (bodyLength == 0) { // 没有body数据 + originalData.setBodyData(new byte[0]); + if (remainingBuf != null) { + // the body is empty so header remaining buf need set null + if (remainingBuf.hasRemaining()) { + ByteBuffer temp = ByteBuffer.allocate(remainingBuf.remaining()); + temp.order(socketOptions.getReadOrder()); + temp.put(remainingBuf.array(), remainingBuf.position(), remainingBuf.remaining()); + remainingBuf = temp; + } else { + remainingBuf = null; + } + } + } else if (bodyLength < 0) { + throw new ReadUnrecoverableException("数据body的长度不能小于0"); + } + + LogUtil.d("Socket收到数据-->" + originalData.getBodyString()); + // 分发 + actionDispatch.dispatchAction(IOAction.ACTION_READ_COMPLETE, originalData); + + } + + + /** + * 读数据任务 + */ + private Runnable readerTask = new Runnable() { + @Override + public void run() { + try { + while (!stopThread) { + read(); + } + } catch (ReadUnrecoverableException unrecoverableException) { + // 读异常 + unrecoverableException.printStackTrace(); + // 停止线程 + stopThread = true; + release(); + } catch (ReadRecoverableExeption readRecoverableExeption) { + readRecoverableExeption.printStackTrace(); + // 重连 + connectionManager.disconnect(true); + + } catch (IOException e) { + e.printStackTrace(); + // 重连 + connectionManager.disconnect(true); + } + } + }; + + + private void readHeaderFromSteam(ByteBuffer headBuf, int readLength) throws ReadRecoverableExeption, IOException { + for (int i = 0; i < readLength; i++) { + byte[] bytes = new byte[1]; + // 从输入流中读数据,无数据时会阻塞 + int value = inputStream.read(bytes); + // -1代表读到了文件的末尾,一般是因为服务器断开了连接 + if (value == -1) { + throw new ReadRecoverableExeption("读数据失败,可能是因为socket跟服务器断开了连接"); + } + headBuf.put(bytes); + } + } + + private void readOriginDataFromSteam(OriginReadData readData) throws ReadRecoverableExeption, IOException { + // 用 全局originBuf避免重复创建字节数组 + int len = inputStream.read(originBuf.array()); + // no more data + if (len == -1) { + throw new ReadRecoverableExeption("读数据失败,可能因为socket跟服务器断开了连接"); + } + // bytes复制 + byte[] data = new byte[len]; + originBuf.get(data, 0, len); + readData.setBodyData(data); + LogUtil.d("Socket收到数据-->" + readData.getBodyString()); + // 分发数据 + actionDispatch.dispatchAction(IOAction.ACTION_READ_COMPLETE, readData); + // 相当于把指针重新指向positon=0 + originBuf.clear(); + } + + private void readBodyFromStream(ByteBuffer byteBuffer) throws ReadRecoverableExeption, IOException { + // while循环直到byteBuffer装满数据 + while (byteBuffer.hasRemaining()) { + byte[] bufArray = new byte[socketOptions.getMaxReadBytes()]; // 从服务器单次读取的最大值 + int len = inputStream.read(bufArray); + if (len == -1) { // no more data + throw new ReadRecoverableExeption("读数据失败,可能是因为socket跟服务器断开了连接"); + } + int remaining = byteBuffer.remaining(); + if (len > remaining) { // 从stream读的数据超过byteBuffer的剩余空间 + byteBuffer.put(bufArray, 0, remaining); + // 将多余的数据保存到remainingBuf中缓存,等下一次读取 + remainingBuf = ByteBuffer.allocate(len - remaining); + remainingBuf.order(socketOptions.getReadOrder()); + remainingBuf.put(bufArray, remaining, len - remaining); + } else { // 从stream读的数据小于或等于byteBuffer的剩余空间 + byteBuffer.put(bufArray, 0, len); + } + } + } + + @Override + public void openReader() { + init(); + if (readerThread == null || !readerThread.isAlive()) { + readerThread = new Thread(readerTask, "reader thread"); + stopThread = false; + readerThread.start(); + } + } + + @Override + public void closeReader() { + try { + // 关闭线程释放资源 + shutDownThread(); + release(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + // 释放资源 + private void release() { + if (originBuf != null) { + originBuf = null; + } + if (remainingBuf != null) { + remainingBuf = null; + } + if (readerThread != null && !readerThread.isAlive()) { + readerThread = null; + } + + try { + if (inputStream != null) + inputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + inputStream = null; + } + } + + // 初始化 + private void init() { + inputStream = connectionManager.getInputStream(); + // 没有定义消息协议 + if (socketOptions.getMessageProtocol() == null) { + originBuf = ByteBuffer.allocate(1024 * 4); + } + } + + @Override + public void setOption(EasySocketOptions socketOptions) { + this.socketOptions = socketOptions; + } + + // 关闭读数据线程 + private void shutDownThread() throws InterruptedException { + if (readerThread != null && readerThread.isAlive() && !readerThread.isInterrupted()) { + stopThread = true; + readerThread.interrupt(); + readerThread.join(); + } + } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/iowork/EasyWriter.java b/easysocket/src/main/java/com/easysocket/connection/iowork/EasyWriter.java new file mode 100644 index 0000000..c7caf75 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/iowork/EasyWriter.java @@ -0,0 +1,146 @@ +package com.easysocket.connection.iowork; + +import com.easysocket.config.EasySocketOptions; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.ISocketActionDispatch; +import com.easysocket.interfaces.io.IWriter; +import com.easysocket.utils.LogUtil; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; +import java.util.concurrent.LinkedBlockingDeque; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public class EasyWriter implements IWriter { + + /** + * 输出流 + */ + private OutputStream outputStream; + + /** + * 连接管理器 + */ + private IConnectionManager connectionManager; + /** + * socket参数 + */ + private EasySocketOptions socketOptions; + /** + * 行为分发 + */ + private ISocketActionDispatch actionDispatch; + /** + * 写数据线程 + */ + private Thread writerThread; + /** + * 是否停止写数据 + */ + private boolean isStop; + /** + * 待写入数据 + */ + private LinkedBlockingDeque packetsToSend = new LinkedBlockingDeque<>(); + + public EasyWriter(IConnectionManager connectionManager, ISocketActionDispatch actionDispatch) { + this.connectionManager = connectionManager; + socketOptions = connectionManager.getOptions(); + this.actionDispatch = actionDispatch; + } + + @Override + public void openWriter() { + outputStream = connectionManager.getOutStream(); + if (writerThread == null) { + isStop = false; + writerThread = new Thread(writerTask, "writer thread"); + writerThread.start(); + } + } + + @Override + public void setOption(EasySocketOptions socketOptions) { + this.socketOptions = socketOptions; + } + + /** + * 写任务 + */ + private Runnable writerTask = new Runnable() { + @Override + public void run() { + // 循环写数据 + while (!isStop) { + try { + byte[] sender = packetsToSend.take(); + write(sender); + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + } + } + } + }; + + @Override + public void write(byte[] sendBytes) throws IOException { + if (sendBytes != null) { + LogUtil.d("Socket发送数据String-->" + new String(sendBytes, Charset.forName("utf-8"))); + LogUtil.d("Socket发送数据byte[]-->" + Arrays.toString(sendBytes)); + int packageSize = socketOptions.getMaxWriteBytes(); // 每次可以发送的最大数据 + int remainingCount = sendBytes.length; + ByteBuffer writeBuf = ByteBuffer.allocate(packageSize); + writeBuf.order(socketOptions.getReadOrder()); + int index = 0; + // 如果发送的数据大于单次可发送的最大数据,则分多次发送 + while (remainingCount > 0) { + int realWriteLength = Math.min(packageSize, remainingCount); + writeBuf.clear(); // 清空缓存 + writeBuf.rewind(); // 将position位置移到0 + writeBuf.put(sendBytes, index, realWriteLength); + writeBuf.flip(); + byte[] writeArr = new byte[realWriteLength]; + writeBuf.get(writeArr); + outputStream.write(writeArr); + outputStream.flush(); // 强制写入缓存中残留数据 + index += realWriteLength; + remainingCount -= realWriteLength; + } + } + } + + @Override + public void offer(byte[] sender) { + if (!isStop) + packetsToSend.offer(sender); + } + + @Override + public void closeWriter() { + try { + if (outputStream != null) + outputStream.close(); + shutDownThread(); + } catch (IOException | InterruptedException e) { + e.printStackTrace(); + } finally { + outputStream = null; + } + } + + private void shutDownThread() throws InterruptedException { + if (writerThread != null && writerThread.isAlive() && !writerThread.isInterrupted()) { + isStop = true; + writerThread.interrupt(); + writerThread.join(); + writerThread = null; + } + } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/iowork/IOManager.java b/easysocket/src/main/java/com/easysocket/connection/iowork/IOManager.java new file mode 100644 index 0000000..b1bd280 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/iowork/IOManager.java @@ -0,0 +1,98 @@ +package com.easysocket.connection.iowork; + +import com.easysocket.config.EasySocketOptions; +import com.easysocket.interfaces.config.IOptions; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.ISocketActionDispatch; +import com.easysocket.interfaces.io.IIOManager; +import com.easysocket.interfaces.io.IReader; +import com.easysocket.interfaces.io.IWriter; + +/** + * Author:Alex + * Date:2019/5/28 + * Note: + */ +public class IOManager implements IIOManager, IOptions { + /** + * socket行为回调 + */ + private ISocketActionDispatch actionDispatch; + /** + * 连接管理 + */ + private IConnectionManager connectionManager; + /** + * 写 + */ + private IWriter writer; + /** + * 读 + */ + private IReader reader; + + public IOManager(IConnectionManager connectionManager, + ISocketActionDispatch connActionDispatch) { + this.connectionManager = connectionManager; + this.actionDispatch = connActionDispatch; + initIO(); + } + + // 初始化io + private void initIO() { + //makesureHeaderProtocolNotEmpty(); + reader = new EasyReader(connectionManager, actionDispatch); // 读 + writer = new EasyWriter(connectionManager, actionDispatch); // 写 + } + + @Override + public void sendBytes(byte[] bytes) { + if (writer != null) + writer.offer(bytes); + } + + @Override + public void startIO() { + if (writer != null) + writer.openWriter(); + if (reader != null) + reader.openReader(); + } + + @Override + public void closeIO() { + if (writer != null) + writer.closeWriter(); + if (reader != null) + reader.closeReader(); + } + + @Override + public Object setOptions(EasySocketOptions socketOptions) { + //makesureHeaderProtocolNotEmpty(); + if (writer != null) + writer.setOption(socketOptions); + if (reader != null) + reader.setOption(socketOptions); + return this; + } + + @Override + public EasySocketOptions getOptions() { + return connectionManager.getOptions(); + } + + /** + * 确保包结构协议不为空 + */ +// private void makesureHeaderProtocolNotEmpty() { +// IMessageProtocol protocol = connectionManager.getOptions().getMessageProtocol(); +// if (protocol == null) { +// throw new NoNullException("The reader protocol can not be Null."); +// } +// +// if (protocol.getHeaderLength() == 0) { +// throw new NoNullException("The header length can not be zero."); +// } +// } +} diff --git a/easysocket/src/main/java/com/easysocket/connection/reconnect/AbsReconnection.java b/easysocket/src/main/java/com/easysocket/connection/reconnect/AbsReconnection.java new file mode 100644 index 0000000..81e77ff --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/reconnect/AbsReconnection.java @@ -0,0 +1,52 @@ +package com.easysocket.connection.reconnect; + +import com.easysocket.entity.OriginReadData; +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.interfaces.conn.IReconnListener; +import com.easysocket.interfaces.conn.SocketActionListener; + +/** + * Author:Alex + * Date:2019/5/31 + * Note:抽象重连器 + */ +public abstract class AbsReconnection extends SocketActionListener implements IReconnListener { + /** + * 连接管理器 + */ + protected IConnectionManager connectionManager; + /** + * socket连接管理器是否已销毁 + */ + protected boolean isDetach; + + + @Override + public synchronized void attach(IConnectionManager iConnectionManager) { + if (!isDetach) { + detach(); + } + isDetach = false; + connectionManager = iConnectionManager; + connectionManager.subscribeSocketAction(this); // 监听socket行为 + } + + @Override + public synchronized void detach() { + isDetach = true; + if (connectionManager != null) + connectionManager.unSubscribeSocketAction(this); + } + + @Override + public void onSocketResponse(SocketAddress socketAddress, OriginReadData originReadData) { + // donothing + } + + /** + * 是否正在重连 + * @return + */ + public abstract boolean isReconning(); +} diff --git a/easysocket/src/main/java/com/easysocket/connection/reconnect/DefaultReConnection.java b/easysocket/src/main/java/com/easysocket/connection/reconnect/DefaultReConnection.java new file mode 100644 index 0000000..2ecb355 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/connection/reconnect/DefaultReConnection.java @@ -0,0 +1,148 @@ +package com.easysocket.connection.reconnect; + +import android.os.Handler; +import android.os.HandlerThread; + +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.conn.IConnectionManager; +import com.easysocket.utils.LogUtil; + +/** + * Author:Alex + * Date:2019/5/28 + * Note:默认重连器 + */ +public class DefaultReConnection extends AbsReconnection { + /** + * 最大连接失败次数,超过可以切换到备用的服务器地址 + */ + private static final int MAX_CONNECTION_FAILED_TIMES = 10; + /** + * 连接失败的次数 + */ + private int connectionFailedTimes = 0; + /** + * 重连间隔不能小于10秒,为了避免全部客户端socket在同一时间连接服务端,间隔时间需要上下浮动50% + */ + private long reconnectTimeDelay = 30 * 1000; + /** + * 重连线程 + */ + private HandlerThread handlerThread; + /** + * 实现延时任务的 handler + */ + private Handler handler; + + public DefaultReConnection() { + } + + @Override + public synchronized void attach(IConnectionManager iConnectionManager) { + super.attach(iConnectionManager); + if (reconnectTimeDelay < connectionManager.getOptions().getConnectTimeout()) { + reconnectTimeDelay = connectionManager.getOptions().getConnectTimeout(); + } + } + + /** + * 重连任务 + */ + private final Runnable RcConnTask = new Runnable() { + @Override + public void run() { + LogUtil.d("---> 执行重连"); + if (isDetach) { + shutDown(); + return; + } + // 是否可连接的 + if (!connectionManager.isConnectViable()) { + LogUtil.d("当前条件不允许连接"); + // 尝试再次重连 + handler.postDelayed(RcConnTask, (long) (reconnectTimeDelay * (Math.random() + 0.5))); + return; + } + // 重连 + connectionManager.connect(); + } + }; + + /** + * 进行重连 + */ + private void reconnect() { + if (handlerThread == null) { + handlerThread = new HandlerThread("re_conn"); + handlerThread.start(); + handler = new Handler(handlerThread.getLooper()); + } + LogUtil.d("重连间隔时间-->" + reconnectTimeDelay * (Math.random() + 0.5)); + handler.postDelayed(RcConnTask, (long) (reconnectTimeDelay * (Math.random() + 0.5))); + } + + + // 关闭重连线程 + private void shutDown() { + if (handlerThread != null && handlerThread.isAlive()) { + handlerThread.quit(); + handlerThread = null; + handler = null; + } + } + + @Override + public boolean equals(Object o) { + // getClass返回Class类型的对象,比较它们的类型对象是否==,其实是比较它们是否为同一个Class创建的对象 + if (o == null || getClass() != o.getClass()) return false; + return true; + } + + @Override + public void onSocketConnSuccess(SocketAddress socketAddress) { + // 连接成功关闭重连线程 + shutDown(); + } + + @Override + public void onSocketConnFail(SocketAddress socketAddress, boolean isNeedReconnect) { + // 不需要重连,则关闭重连线程 + if (!isNeedReconnect) { + shutDown(); + return; + } + connectionFailedTimes++; + + // 如果大于最大连接次数并且有备用host,则轮流切换两个host + if (connectionFailedTimes > MAX_CONNECTION_FAILED_TIMES && socketAddress.getBackupAddress() != null) { + connectionFailedTimes = 0; // 归零 + SocketAddress backupAddress = socketAddress.getBackupAddress(); + SocketAddress nowAddress = new SocketAddress(socketAddress.getIp(), socketAddress.getPort()); + backupAddress.setBackupAddress(nowAddress); + if (connectionManager.isConnectViable()) { + connectionManager.switchHost(backupAddress); + // 切换主机地址,重新连接 + reconnect(); + } + } else { + reconnect(); + } + + } + + @Override + public void onSocketDisconnect(SocketAddress socketAddress, boolean isNeedReconnect) { + // 是否需要重连 + if (!isNeedReconnect) { + shutDown(); + return; + } + reconnect(); + } + + @Override + public boolean isReconning() { + return handlerThread != null && handlerThread.isAlive(); + } + +} diff --git a/easysocket/src/main/java/com/easysocket/entity/OriginReadData.java b/easysocket/src/main/java/com/easysocket/entity/OriginReadData.java new file mode 100644 index 0000000..da184c6 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/OriginReadData.java @@ -0,0 +1,58 @@ +package com.easysocket.entity; + +import com.easysocket.EasySocket; +import com.easysocket.utils.Utils; + +import java.io.Serializable; +import java.nio.charset.Charset; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:读到的数据 + */ +public class OriginReadData implements Serializable { + + /** + * 包头数据 + */ + private byte[] headerData; + /** + * 包体数据 + */ + private byte[] bodyData; + + public byte[] getHeaderData() { + return headerData; + } + + public void setHeaderData(byte[] headerData) { + this.headerData = headerData; + } + + public byte[] getBodyBytes() { + return bodyData; + } + + public void setBodyData(byte[] bodyData) { + this.bodyData = bodyData; + } + + /** + * 获取数据body的string + * + * @return + */ + public String getBodyString() { + return new String(getBodyBytes(), Charset.forName(EasySocket.getInstance().getDefOptions().getCharsetName())); + } + + /** + * 获取完整的数据,包括包头和包体 + * + * @return + */ + public byte[] getOriginDataBytes() { + return Utils.concatBytes(getHeaderData(), getBodyBytes()); + } +} diff --git a/easysocket/src/main/java/com/easysocket/entity/SocketAddress.java b/easysocket/src/main/java/com/easysocket/entity/SocketAddress.java new file mode 100644 index 0000000..a4187eb --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/SocketAddress.java @@ -0,0 +1,54 @@ +package com.easysocket.entity; + +/** + * Author:Alex + * Date:2019/5/31 + * Note:socket主机地址 + */ +public class SocketAddress { + + /** + * IPV4地址 + */ + private String ip; + /** + * 连接服务器端口号 + */ + private int port; + /** + * 当此IP地址Ping不通时的备用IP + */ + private SocketAddress backupAddress; + + /** + * 获取备用的Ip和端口号 + * + * @return 备用的端口号和IP地址 + */ + public SocketAddress getBackupAddress() { + return backupAddress; + } + + /** + * 设置备用的IP和端口号,可以不设置 + * + * @param backupAddress 备用的IP和端口号信息 + */ + public void setBackupAddress(SocketAddress backupAddress) { + this.backupAddress = backupAddress; + } + + public SocketAddress(String ip, int port){ + this.ip =ip; + this.port =port; + } + + public String getIp() { + return ip; + } + + public int getPort() { + return port; + } + +} diff --git a/easysocket/src/main/java/com/easysocket/entity/basemsg/IResponse.java b/easysocket/src/main/java/com/easysocket/entity/basemsg/IResponse.java new file mode 100644 index 0000000..9e08c7c --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/basemsg/IResponse.java @@ -0,0 +1,9 @@ +package com.easysocket.entity.basemsg; + +/** + * Author:Alex + * Date:2019/12/8 + * Note: + */ +public interface IResponse { +} diff --git a/easysocket/src/main/java/com/easysocket/entity/basemsg/ISender.java b/easysocket/src/main/java/com/easysocket/entity/basemsg/ISender.java new file mode 100644 index 0000000..4f34083 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/basemsg/ISender.java @@ -0,0 +1,11 @@ +package com.easysocket.entity.basemsg; + +import java.io.Serializable; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:发送数据的接口 + */ +public interface ISender extends Serializable { +} diff --git a/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperCallbackResponse.java b/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperCallbackResponse.java new file mode 100644 index 0000000..7bdb330 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperCallbackResponse.java @@ -0,0 +1,13 @@ +package com.easysocket.entity.basemsg; + +/** + * Author:Alex + * Date:2019/12/7 + */ +public abstract class SuperCallbackResponse implements IResponse { + + public abstract String getCallbackId(); + + public abstract void setCallbackId(String callbackId); + +} diff --git a/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperCallbackSender.java b/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperCallbackSender.java new file mode 100644 index 0000000..2f1480d --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperCallbackSender.java @@ -0,0 +1,36 @@ +package com.easysocket.entity.basemsg; + +import com.easysocket.utils.Utils; + +/** + * Author:Alex + * Date:2019/10/19 + */ +public abstract class SuperCallbackSender extends SuperSender { + + private String callbackId; + + public SuperCallbackSender() { + generateCallbackId(); + } + + public String getCallbackId() { + return callbackId; + } + + /** + * 根据自己的协议打包消息 + * + * @return + */ + public abstract byte[] pack(); + + /** + * 随机生成一个回调标识 CallbackId,在消息发送前执行,CallbackId作为消息的唯一标识一起传给服务器,服务器反馈 + * 当前消息的时候也是携带同样的CallbackId给客户端,用以识别 + */ + public void generateCallbackId() { + callbackId= Utils.getRandomChar(20); + } +} + diff --git a/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperSender.java b/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperSender.java new file mode 100644 index 0000000..e700263 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/entity/basemsg/SuperSender.java @@ -0,0 +1,9 @@ +package com.easysocket.entity.basemsg; + +/** + * Author:Alex + * Date:2019/10/19 + * Note:基础消息 + */ +public class SuperSender implements ISender { +} diff --git a/easysocket/src/main/java/com/easysocket/exception/InitialExeption.java b/easysocket/src/main/java/com/easysocket/exception/InitialExeption.java new file mode 100644 index 0000000..772306d --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/exception/InitialExeption.java @@ -0,0 +1,12 @@ +package com.easysocket.exception; + +/** + * Author:Alex + * Date:2019/6/5 + * Note:初始化异常 + */ +public class InitialExeption extends RuntimeException{ + public InitialExeption(String s){ + super(s); + } +} diff --git a/easysocket/src/main/java/com/easysocket/exception/NotNullException.java b/easysocket/src/main/java/com/easysocket/exception/NotNullException.java new file mode 100644 index 0000000..dc5092e --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/exception/NotNullException.java @@ -0,0 +1,12 @@ +package com.easysocket.exception; + +/** + * Author:Alex + * Date:2019/6/5 + * Note:非空异常 + */ +public class NotNullException extends RuntimeException { + public NotNullException(String e) { + super(e); + } +} diff --git a/easysocket/src/main/java/com/easysocket/exception/ReadRecoverableExeption.java b/easysocket/src/main/java/com/easysocket/exception/ReadRecoverableExeption.java new file mode 100644 index 0000000..ad70ee2 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/exception/ReadRecoverableExeption.java @@ -0,0 +1,13 @@ +package com.easysocket.exception; + +/** + * Author:Alex + * Date:2019/6/5 + * Note:可恢复socket读数据异常 + */ +public class ReadRecoverableExeption extends Exception { + + public ReadRecoverableExeption(String s){ + super(s); + } +} diff --git a/easysocket/src/main/java/com/easysocket/exception/ReadUnrecoverableException.java b/easysocket/src/main/java/com/easysocket/exception/ReadUnrecoverableException.java new file mode 100644 index 0000000..26c1956 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/exception/ReadUnrecoverableException.java @@ -0,0 +1,12 @@ +package com.easysocket.exception; + +/** + * Author:Mapogo + * Date:2020/12/29 + * Note:不可修复的读取错误 + */ +public class ReadUnrecoverableException extends Exception { + public ReadUnrecoverableException(String s) { + super(s); + } +} diff --git a/easysocket/src/main/java/com/easysocket/exception/RequestCancelException.java b/easysocket/src/main/java/com/easysocket/exception/RequestCancelException.java new file mode 100644 index 0000000..f899342 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/exception/RequestCancelException.java @@ -0,0 +1,13 @@ +package com.easysocket.exception; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:请求取消异常 + */ +public class RequestCancelException extends Exception{ + + public RequestCancelException(String s){ + super(s); + } +} diff --git a/easysocket/src/main/java/com/easysocket/exception/RequestTimeOutException.java b/easysocket/src/main/java/com/easysocket/exception/RequestTimeOutException.java new file mode 100644 index 0000000..51768ae --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/exception/RequestTimeOutException.java @@ -0,0 +1,13 @@ +package com.easysocket.exception; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:请求超时异常 + */ +public class RequestTimeOutException extends Exception{ + + public RequestTimeOutException(String s){ + super(s); + } +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/callback/ICallBack.java b/easysocket/src/main/java/com/easysocket/interfaces/callback/ICallBack.java new file mode 100644 index 0000000..5c48e23 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/callback/ICallBack.java @@ -0,0 +1,18 @@ +package com.easysocket.interfaces.callback; + +import com.easysocket.callback.SuperCallBack; + +/** + * Author:Alex + * Date:2019/6/5 + * Note: + */ +public interface ICallBack { + /** + * socket请求回调 + * @param callBack + */ + void onCallBack(SuperCallBack callBack); + + +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/callback/IProgressDialog.java b/easysocket/src/main/java/com/easysocket/interfaces/callback/IProgressDialog.java new file mode 100644 index 0000000..3e66dae --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/callback/IProgressDialog.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2017 zhouyou(478319399@qq.com) + * + * 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 + * + * http://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. + */ + +package com.easysocket.interfaces.callback; + +import android.app.Dialog; + +/** + *

描述:自定义对话框的dialog

+ */ +public interface IProgressDialog { + Dialog getDialog(); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/callback/IType.java b/easysocket/src/main/java/com/easysocket/interfaces/callback/IType.java new file mode 100644 index 0000000..4193247 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/callback/IType.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2017 zhouyou(478319399@qq.com) + * + * 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 + * + * http://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. + */ + +package com.easysocket.interfaces.callback; + +import java.lang.reflect.Type; + +/** + *

描述:获取类型接口

+ */ +public interface IType { + + Type getType(); + + Class getGenericityClazz(); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/callback/ProgressCancelListener.java b/easysocket/src/main/java/com/easysocket/interfaces/callback/ProgressCancelListener.java new file mode 100644 index 0000000..c64e9f5 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/callback/ProgressCancelListener.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2017 zhouyou(478319399@qq.com) + * + * 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 + * + * http://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. + */ + +package com.easysocket.interfaces.callback; + +/** + *

描述:进度框取消监听

+ */ +public interface ProgressCancelListener { + void onCancelProgress(); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/config/IConnectionSwitchListener.java b/easysocket/src/main/java/com/easysocket/interfaces/config/IConnectionSwitchListener.java new file mode 100644 index 0000000..1260c5d --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/config/IConnectionSwitchListener.java @@ -0,0 +1,13 @@ +package com.easysocket.interfaces.config; + +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.conn.IConnectionManager; + +/** + * Author:Alex + * Date:2019/6/4 + * Note: + */ +public interface IConnectionSwitchListener { + void onSwitchConnectionInfo(IConnectionManager manager, SocketAddress oldAddress, SocketAddress newAddress); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/config/IMessageProtocol.java b/easysocket/src/main/java/com/easysocket/interfaces/config/IMessageProtocol.java new file mode 100644 index 0000000..0808511 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/config/IMessageProtocol.java @@ -0,0 +1,20 @@ +package com.easysocket.interfaces.config; + +import java.nio.ByteOrder; + +/** + * 消息数据格式 + */ +public interface IMessageProtocol { + + /** + * 获取包头的长度 + */ + int getHeaderLength(); + + /** + * 获取数据包体的长度,根据协议这个长度应该写在包头中,在读取数据时用到 + */ + int getBodyLength(byte[] header, ByteOrder byteOrder); + +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/config/IOptions.java b/easysocket/src/main/java/com/easysocket/interfaces/config/IOptions.java new file mode 100644 index 0000000..f732839 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/config/IOptions.java @@ -0,0 +1,22 @@ +package com.easysocket.interfaces.config; + +import com.easysocket.config.EasySocketOptions; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public interface IOptions { + /** + * 设置配置信息 + * @param socketOptions + */ + T setOptions(EasySocketOptions socketOptions); + + /** + * 获取配置信息 + * @return + */ + EasySocketOptions getOptions(); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/IConnectionManager.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/IConnectionManager.java new file mode 100644 index 0000000..94d88ed --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/IConnectionManager.java @@ -0,0 +1,65 @@ +package com.easysocket.interfaces.conn; + +import com.easysocket.entity.SocketAddress; +import com.easysocket.interfaces.callback.ICallBack; +import com.easysocket.interfaces.config.IOptions; + +import java.io.InputStream; +import java.io.OutputStream; + +/** + * Author:Alex + * Date:2019/5/29 + * Note:连接管理的接口规范 + */ +public interface IConnectionManager extends ISubscribeSocketAction, IOptions,ISend, ICallBack { + /** + * 开始连接 + */ + void connect(); + + /** + * 关闭连接 + * @param isNeedReconnect 是否需要重连 + */ + void disconnect(boolean isNeedReconnect); + + + /** + * 获取socket连接状态 + * @return + */ + int getConnectionStatus(); + + /** + * 是否可连接的 + * @return + */ + boolean isConnectViable(); + + /** + * 切换host + * @param socketAddress + */ + void switchHost(SocketAddress socketAddress); + + /** + * 获取输入流 + * @return + */ + InputStream getInputStream(); + + /** + * 获取输出流 + * @return + */ + OutputStream getOutStream(); + + /** + * 获取心跳管理器 + * @return + */ + IHeartManager getHeartManager(); + + +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/IHeartManager.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/IHeartManager.java new file mode 100644 index 0000000..5bbad47 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/IHeartManager.java @@ -0,0 +1,28 @@ +package com.easysocket.interfaces.conn; + +import com.easysocket.connection.heartbeat.HeartManager; + +/** + * Author:Alex + * Date:2019/12/8 + * Note: + */ +public interface IHeartManager { + + /** + * 开始心跳 + * @param clientHeart + */ + void startHeartbeat(byte[] clientHeart, HeartManager.HeartbeatListener listener); + + /** + * 停止心跳 + */ + void stopHeartbeat(); + + + /** + * 接收到心跳 + */ + void onReceiveHeartBeat(); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/IReconnListener.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/IReconnListener.java new file mode 100644 index 0000000..ed48ac1 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/IReconnListener.java @@ -0,0 +1,20 @@ +package com.easysocket.interfaces.conn; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public interface IReconnListener { + + /** + * 关联连接器 + * @param iConnectionManager + */ + void attach(IConnectionManager iConnectionManager); + + /** + * 分离连接器 + */ + void detach(); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/ISend.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISend.java new file mode 100644 index 0000000..c9cd772 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISend.java @@ -0,0 +1,25 @@ +package com.easysocket.interfaces.conn; + +import com.easysocket.entity.basemsg.SuperCallbackSender; + +/** + * Author:Alex + * Date:2019/6/5 + * Note:发送接口 + */ +public interface ISend { + + /** + * 发送一个有回调的消息 + * @param sender + * @return + */ + IConnectionManager upCallbackMessage(SuperCallbackSender sender); + + /** + * 发送bytes + * @param bytes + * @return + */ + IConnectionManager upBytes(byte[] bytes); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/ISocketActionDispatch.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISocketActionDispatch.java new file mode 100644 index 0000000..c028978 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISocketActionDispatch.java @@ -0,0 +1,38 @@ +package com.easysocket.interfaces.conn; + +import java.io.Serializable; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:socket行为分发接口 + */ +public interface ISocketActionDispatch { + /** + * 停止分发线程 + */ + void stopDispatchThread(); + + void startDispatchThread(); + + void dispatchAction(String action); + + /** + * socket行为的分发 + * @param action + * @param serializable + */ + void dispatchAction(String action, Serializable serializable); + + /** + * 订阅socket行为 + * @param iSocketActionListener + */ + void subscribe(ISocketActionListener iSocketActionListener); + + /** + * 解除socket行为的订阅 + * @param iSocketActionListener + */ + void unsubscribe(ISocketActionListener iSocketActionListener); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/ISocketActionListener.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISocketActionListener.java new file mode 100644 index 0000000..82ecb60 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISocketActionListener.java @@ -0,0 +1,52 @@ +package com.easysocket.interfaces.conn; + +import com.easysocket.entity.OriginReadData; +import com.easysocket.entity.SocketAddress; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:socket行为监听接口 + */ +public interface ISocketActionListener { + /** + * socket连接成功 + * @param socketAddress + */ + void onSocketConnSuccess(SocketAddress socketAddress); + + /** + * socket连接失败 + * @param socketAddress + * @param isNeedReconnect 是否需要重连 + */ + void onSocketConnFail(SocketAddress socketAddress, boolean isNeedReconnect); + + /** + * 断开socket连接 + * @param socketAddress + * @param isNeedReconnect 是否需要重连 + */ + void onSocketDisconnect(SocketAddress socketAddress, boolean isNeedReconnect); + + /** + * socket数据响应 + * @param socketAddress + * @param originReadData + */ + void onSocketResponse(SocketAddress socketAddress, OriginReadData originReadData); + + /** + * socket数据响应 + * @param socketAddress + * @param readData + */ + void onSocketResponse(SocketAddress socketAddress, String readData); + + /** + * socket数据响应 + * @param socketAddress + * @param readData + */ + void onSocketResponse(SocketAddress socketAddress, byte[] readData); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/ISubscribeSocketAction.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISubscribeSocketAction.java new file mode 100644 index 0000000..27fc89e --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/ISubscribeSocketAction.java @@ -0,0 +1,20 @@ +package com.easysocket.interfaces.conn; + +/** + * Author:Alex + * Date:2019/6/1 + * Note:订阅监听socket + */ +public interface ISubscribeSocketAction { + /** + * 注册监听socket的行为 + * @param iSocketActionListener + */ + void subscribeSocketAction(ISocketActionListener iSocketActionListener); + + /** + * 注销监听socket的行为 + * @param iSocketActionListener + */ + void unSubscribeSocketAction(ISocketActionListener iSocketActionListener); +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/conn/SocketActionListener.java b/easysocket/src/main/java/com/easysocket/interfaces/conn/SocketActionListener.java new file mode 100644 index 0000000..a63a874 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/conn/SocketActionListener.java @@ -0,0 +1,57 @@ +package com.easysocket.interfaces.conn; + +import com.easysocket.entity.OriginReadData; +import com.easysocket.entity.SocketAddress; + +/** + * Author:Alex + * Date:2019/6/4 + * Note:socket行为监听的抽象类,继承此类可以选择性地重写方法 + */ +public abstract class SocketActionListener implements ISocketActionListener{ + /** + * socket连接成功 + * @param socketAddress + */ + @Override + public void onSocketConnSuccess(SocketAddress socketAddress) { + + } + /** + * socket连接失败 + * @param socketAddress + * @param isNeedReconnect 是否需要重连 + */ + @Override + public void onSocketConnFail(SocketAddress socketAddress, boolean isNeedReconnect) { + + } + /** + * 断开socket连接 + * @param socketAddress + * @param isNeedReconnect 是否需要重连 + */ + @Override + public void onSocketDisconnect(SocketAddress socketAddress, boolean isNeedReconnect) { + + } + /** + * socket读数据反馈 + * @param socketAddress + * @param originReadData + */ + @Override + public void onSocketResponse(SocketAddress socketAddress, OriginReadData originReadData) { + + } + + @Override + public void onSocketResponse(SocketAddress socketAddress, byte[] readData) { + + } + + @Override + public void onSocketResponse(SocketAddress socketAddress, String readData) { + + } +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/io/IIOManager.java b/easysocket/src/main/java/com/easysocket/interfaces/io/IIOManager.java new file mode 100644 index 0000000..3cdf538 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/io/IIOManager.java @@ -0,0 +1,27 @@ +package com.easysocket.interfaces.io; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public interface IIOManager { + + /** + * 发送字节流 + * + * @param bytes + */ + void sendBytes(byte[] bytes); + + /** + * 关闭io管理器 + */ + void closeIO(); + + /** + * 开启io操作 + */ + void startIO(); + +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/io/IReader.java b/easysocket/src/main/java/com/easysocket/interfaces/io/IReader.java new file mode 100644 index 0000000..3104c06 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/io/IReader.java @@ -0,0 +1,32 @@ +package com.easysocket.interfaces.io; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public interface IReader { + + /** + * 读数据 + */ + void read() throws Exception; + + /** + * 打开数据的读取 + */ + void openReader(); + + /** + * 关闭数据的读取 + */ + void closeReader(); + + /** + * 设置参数 + * @param t + */ + void setOption(T t); + + +} diff --git a/easysocket/src/main/java/com/easysocket/interfaces/io/IWriter.java b/easysocket/src/main/java/com/easysocket/interfaces/io/IWriter.java new file mode 100644 index 0000000..644e7f5 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/interfaces/io/IWriter.java @@ -0,0 +1,38 @@ +package com.easysocket.interfaces.io; + +import java.io.IOException; + +/** + * Author:Alex + * Date:2019/6/1 + * Note: + */ +public interface IWriter { + /** + * 保存要写的数据 + */ + void offer(byte[] sender); + + /** + * 写数据 + * @param sender + */ + void write(byte[] sender) throws IOException; + + /** + * 关闭stream + */ + void closeWriter(); + + /** + * 开启写数据 + */ + void openWriter(); + + /** + * 设置参数 + * @param t + */ + void setOption(T t); + +} diff --git a/easysocket/src/main/java/com/easysocket/utils/LogUtil.java b/easysocket/src/main/java/com/easysocket/utils/LogUtil.java new file mode 100644 index 0000000..df549bf --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/utils/LogUtil.java @@ -0,0 +1,87 @@ +package com.easysocket.utils; +import android.util.Log; + +import com.easysocket.EasySocket; + + +public class LogUtil { + public static final String LOGTAG = "easysocket"; + public static boolean debugEnabled = EasySocket.getInstance().getDefOptions().isDebug(); + + public LogUtil() { + } + + private static String getDebugInfo() { + Throwable stack = new Throwable().fillInStackTrace(); + StackTraceElement[] trace = stack.getStackTrace(); + int n = 2; + return trace[n].getClassName() + " " + trace[n].getMethodName() + "()" + ":" + trace[n].getLineNumber() + + " "; + } + + private static String getLogInfoByArray(String[] infos) { + StringBuilder sb = new StringBuilder(); + for (String info : infos) { + sb.append(info); + sb.append(" "); + } + return sb.toString(); + } + + public static void i(String... s) { + if (debugEnabled) { + i(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void e(Throwable tr) { + if (debugEnabled) { + Log.e(LOGTAG, getDebugInfo() ,tr); + } + } + + public static void e(String... s) { + if (debugEnabled) { + e(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void d(String... s) { + if (debugEnabled) { + d(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void v(String... s) { + if (debugEnabled) { + v(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + public static void w(String... s) { + if (debugEnabled) { + w(LOGTAG, getDebugInfo() + getLogInfoByArray(s)); + } + } + + private static void i(String name, String log) { + System.out.println(name + ":" + log); + } + + private static void d(String name, String log) { + System.out.println(name + ":" + log); + } + + private static void v(String name, String log) { + System.out.println(name + ":" + log); + } + + private static void e(String name, String log) { + System.err.println(name + ":" + log); + } + + private static void w(String name, String log) { + System.err.println(name + ":" + log); + } + +} diff --git a/easysocket/src/main/java/com/easysocket/utils/Utils.java b/easysocket/src/main/java/com/easysocket/utils/Utils.java new file mode 100644 index 0000000..10d97c5 --- /dev/null +++ b/easysocket/src/main/java/com/easysocket/utils/Utils.java @@ -0,0 +1,149 @@ +package com.easysocket.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.os.Handler; +import android.os.Looper; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.Random; + +/** + * Created by LXR ON 2018/8/30. + */ +public class Utils { + + /** + * 获取泛型参数的类型 + * + * @param + * @return + */ + public static Type findGenericityType(Class cls) { + Type genType = cls.getGenericSuperclass(); //返回直接继承的父类(包含泛型参数)类型,如果有泛型T,也要包括进去 + //getActualTypeArguments 获取泛型中的实际类型,比如Map中的String类型 + Type[] params = ((ParameterizedType) genType).getActualTypeArguments(); + Type type = params[0]; //泛型的实际类型 + Type finalNeedType; + if (type instanceof ParameterizedType) { //二级泛型,这里就处理最多二级吧,形如 A>,两个<> + finalNeedType = ((ParameterizedType) type).getActualTypeArguments()[0]; + } else { // 一级泛型,形如A + finalNeedType = type; + } + //如果泛型类型还是变量类型,比如T、V之类的,代表没有填写泛型参数 + if (finalNeedType instanceof TypeVariable) throw new IllegalStateException("没有填写泛型参数"); + return finalNeedType; + } + + /** + * 字符串是否为空 + * + * @param str + * @return + */ + public static boolean isStringEmpty(String str) { + return str == null || str.trim().length() == 0; + } + + /** + * 生成随机字符串 + * + * @param length + * @return + */ + public static String getRandomChar(int length) { + char[] chr = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + Random random = new Random(); + StringBuffer buffer = new StringBuffer(); + for (int i = 0; i < length; i++) { + buffer.append(chr[random.nextInt(36)]); + } + return buffer.toString(); + } + + + /** + * 获取handler对象 + * + * @param isMainHandler 是否为主线程的handler,为false时返回的是当前线程handler + * @return + */ + public static Handler getHandler(boolean isMainHandler) { + Handler handler; + if (isMainHandler) { + handler = new Handler(Looper.getMainLooper()); + } else { + Looper.prepare(); + handler = new Handler(); + } + return handler; + } + + /** + * 睡眠多少毫秒 + * + * @param milliSecond 毫秒 + */ + public static void sleep(long milliSecond) { + try { + Thread.sleep(milliSecond); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + /** + * 非空检查 + * + * @param object + * @param emsg + * @throws + */ + public static void checkNotNull(Object object, String emsg) { + try { + if (object == null) { + throw new Exception(emsg); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void throwNotNull(Object object, String emsg) throws Exception { + if (object == null) { + throw new Exception(emsg); + } + } + + // 判断是否连接网络 + public static boolean isNetConnected(Context context) { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo info = cm.getActiveNetworkInfo(); + return info != null && info.isConnected(); + } + + /** + * 拼接两个byte[] + * + * @param + * @param + * @return + */ + public static byte[] concatBytes(byte[] bt1, byte[] bt2) { + if (bt1 == null) { + return bt2; + } + if (bt2 == null) { + return bt1; + } + byte[] bt3 = new byte[bt1.length + bt2.length]; + System.arraycopy(bt1, 0, bt3, 0, bt1.length); + System.arraycopy(bt2, 0, bt3, bt1.length, bt2.length); + return bt3; + } + +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..9814e49 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,40 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +# 表示使用 AndroidX +android.useAndroidX=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# 表示将第三方库迁移到 AndroidX +android.enableJetifier = true +android.nonTransitiveRClass=false +android.nonFinalResIds=false + +#mapbox key +MAPBOX_DOWNLOADS_TOKEN=sk.eyJ1Ijoic2hhbnJlbnRlY2giLCJhIjoiY2t5bndlZnNuMDdjajJvcjBzOTcxcnVvbiJ9.VR_gh0uqZNVyUN92aACCiQ +MAPBOX_ACCESS_TOKEN=pk.eyJ1Ijoic2hhbnJlbnRlY2giLCJhIjoiY2t5bzNnNm41MWQyZzJubjd4NzN6MXJnMyJ9.NPjPb__sEUug9PK4L0hlSA +GOOGLE_MAP_KEY=AIzaSyCFEqlOoo2274NsX7tzoQjs_DA7B9N7-gY +BAIDU_MAP_KEY=YT2CmlqAkpMyzmw1AKNIBlYlBtHESEaW +BUGLY_KEY=06fc0a9cd1 +#http请求头部加密key +HTTP_REQUEST_KEY=8989JUIMSKHKehmasdfWEkjhf!*jfsadf8453 + +#stripe支付 +STRIPE_TEST_KEY=pk_test_51N0gBGCUI9XSagUNC3i1ZTiavE2pncKF3cBPWCu4p0OWowHNh22P1xeEpowp6TUNEeFNfv4OEHmLyj7sihmNuSQl00x2pvl4xH +STRIPE_RELEASE_KEY=pk_live_51N0gBGCUI9XSagUNNDNWc77OdwDWXVSGIdf8WHjvGfe5plUOfs7eq7uyTEQ3ma1xXHlavwInuw9WZOJQ6Z9FVjLq00VsFpc1Rv + +android.suppressUnsupportedCompileSdk=34 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..1777881 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Jul 21 17:24:42 CST 2025 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## 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="" + +# 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, switch paths to Windows format before running java +if $cygwin ; 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=$((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" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@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 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= + +@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 init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +: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 diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..68f8054 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,7 @@ +rootProject.name = "Tracker" +include ':app' +include ':baselibrary' +include ':easyPhotos' +include ':FastBleLib' +include ':easysocket' +//include ':ebiketracker'