From 26c3aabf4df7d41c4380d89776ad5b70e820a3f8 Mon Sep 17 00:00:00 2001 From: Fankesyooni Date: Sat, 7 May 2022 01:48:53 +0800 Subject: [PATCH] first commit --- .gitignore | 3 + .idea/.gitignore | 15 + .idea/assetWizardSettings.xml | 346 +++++++++++++ .idea/compiler.xml | 6 + .idea/gradle.xml | 20 + .idea/inspectionProfiles/Project_Default.xml | 10 + ...__androidx_activity_activity_1_2_4_aar.xml | 15 + ...__androidx_annotation_annotation_1_3_0.xml | 11 + ...tion_annotation_experimental_1_1_0_aar.xml | 13 + ...androidx_appcompat_appcompat_1_4_1_aar.xml | 16 + ...ppcompat_appcompat_resources_1_4_1_aar.xml | 13 + ...__androidx_arch_core_core_common_2_1_0.xml | 11 + ...roidx_arch_core_core_runtime_2_0_0_aar.xml | 12 + ...roidx_arch_core_core_runtime_2_1_0_aar.xml | 12 + ...__androidx_collection_collection_1_1_0.xml | 11 + .../Gradle__androidx_core_core_1_7_0_aar.xml | 16 + ..._cursoradapter_cursoradapter_1_0_0_aar.xml | 12 + ...droidx_customview_customview_1_0_0_aar.xml | 12 + ...dx_drawerlayout_drawerlayout_1_0_0_aar.xml | 15 + ...__androidx_fragment_fragment_1_3_6_aar.xml | 16 + ...dx_interpolator_interpolator_1_0_0_aar.xml | 12 + ...roidx_lifecycle_lifecycle_common_2_4_0.xml | 11 + ...lifecycle_lifecycle_livedata_2_0_0_aar.xml | 12 + ...ycle_lifecycle_livedata_core_2_3_1_aar.xml | 12 + ..._lifecycle_lifecycle_runtime_2_3_1_aar.xml | 13 + ..._lifecycle_lifecycle_runtime_2_4_0_aar.xml | 13 + ...ifecycle_lifecycle_viewmodel_2_3_1_aar.xml | 13 + ...fecycle_viewmodel_savedstate_2_3_1_aar.xml | 12 + ...adle__androidx_loader_loader_1_0_0_aar.xml | 12 + ...droidx_savedstate_savedstate_1_1_0_aar.xml | 13 + .../Gradle__androidx_test_core_1_4_0_aar.xml | 14 + ..._test_espresso_espresso_core_3_4_0_aar.xml | 14 + ...sso_espresso_idling_resource_3_4_0_aar.xml | 14 + ...dle__androidx_test_ext_junit_1_1_3_aar.xml | 14 + ...radle__androidx_test_monitor_1_4_0_aar.xml | 14 + ...Gradle__androidx_test_runner_1_4_0_aar.xml | 14 + ...droidx_test_services_storage_1_4_0_aar.xml | 12 + ...ectordrawable_vectordrawable_1_1_0_aar.xml | 12 + ...able_vectordrawable_animated_1_1_0_aar.xml | 12 + ...rcelable_versionedparcelable_1_1_1_aar.xml | 12 + ...androidx_viewpager_viewpager_1_0_0_aar.xml | 12 + ..._com_google_code_findbugs_jsr305_2_0_1.xml | 9 + ...com_highcapable_yukihookapi_api_1_0_86.xml | 13 + .../Gradle__com_squareup_javawriter_2_1_1.xml | 13 + .../Gradle__de_robv_android_xposed_api_82.xml | 11 + .../Gradle__javax_inject_javax_inject_1.xml | 13 + .idea/libraries/Gradle__junit_junit_4_12.xml | 16 + .../libraries/Gradle__junit_junit_4_13_2.xml | 16 + ...Gradle__org_hamcrest_hamcrest_core_1_3.xml | 13 + ..._org_hamcrest_hamcrest_integration_1_3.xml | 13 + ...dle__org_hamcrest_hamcrest_library_1_3.xml | 13 + ...Gradle__org_jetbrains_annotations_13_0.xml | 13 + ..._jetbrains_kotlin_kotlin_stdlib_1_6_21.xml | 13 + ...ins_kotlin_kotlin_stdlib_common_1_6_21.xml | 13 + ...rains_kotlin_kotlin_stdlib_jdk7_1_6_21.xml | 13 + ...rains_kotlin_kotlin_stdlib_jdk8_1_6_21.xml | 13 + .idea/misc.xml | 22 + .idea/modules.xml | 12 + .idea/vcs.xml | 6 + .idea/workspace.xml | 184 +++++++ README.md | 66 +++ app/.gitignore | 15 + app/build.gradle | 76 +++ app/proguard-rules.pro | 40 ++ .../ExampleInstrumentedTest.kt | 24 + app/src/main/AndroidManifest.xml | 25 + app/src/main/assets/xposed_init | 1 + app/src/main/assets/yukihookapi_init | 1 + app/src/main/ic_launcher-playstore.png | Bin 0 -> 8824 bytes .../apperrorstracking/hook/HookEntry.kt | 39 ++ .../hook/entity/FrameworkHooker.kt | 180 +++++++ .../drawable/drawabletoolbox/Compatible.kt | 292 +++++++++++ .../drawable/drawabletoolbox/Constants.kt | 29 ++ .../drawabletoolbox/DrawableBuilder.kt | 489 ++++++++++++++++++ .../drawabletoolbox/DrawableProperties.kt | 221 ++++++++ .../drawabletoolbox/DrawableWrapperBuilder.kt | 34 ++ .../drawable/drawabletoolbox/FlipDrawable.kt | 78 +++ .../drawabletoolbox/FlipDrawableBuilder.kt | 35 ++ .../drawabletoolbox/LayerDrawableBuilder.kt | 181 +++++++ .../PathShapeDrawableBuilder.kt | 61 +++ .../drawabletoolbox/RippleDrawableBuilder.kt | 73 +++ .../drawabletoolbox/RotateDrawableBuilder.kt | 52 ++ .../drawabletoolbox/ScaleDrawableBuilder.kt | 45 ++ .../StateListDrawableBuilder.kt | 60 +++ .../utils/factory/FunctionFactory.kt | 79 +++ .../res/drawable/ic_baseline_bug_report.xml | 10 + .../main/res/drawable/ic_baseline_close.xml | 10 + .../main/res/drawable/ic_baseline_info.xml | 10 + .../main/res/drawable/ic_baseline_refresh.xml | 10 + .../res/drawable/ic_launcher_background.xml | 170 ++++++ .../res/drawable/ic_launcher_foreground.xml | 15 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 1259 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3084 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 1067 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2066 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 1838 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 4468 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 2730 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 6998 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 3861 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 10335 bytes app/src/main/res/values/array.xml | 6 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 3 + app/src/main/res/values/themes.xml | 4 + .../apperrorstracking/ExampleUnitTest.kt | 17 + build.gradle | 11 + gradle.properties | 23 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 +++++++ gradlew.bat | 89 ++++ keystore/public | Bin 0 -> 2423 bytes local.properties | 10 + settings.gradle | 19 + 115 files changed, 4069 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/assetWizardSettings.xml create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/libraries/Gradle__androidx_activity_activity_1_2_4_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml create mode 100644 .idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_appcompat_appcompat_1_4_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_4_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml create mode 100644 .idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml create mode 100644 .idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_fragment_fragment_1_3_6_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_4_0.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_3_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_3_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_savedstate_2_3_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_savedstate_savedstate_1_1_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_espresso_espresso_core_3_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_espresso_espresso_idling_resource_3_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_ext_junit_1_1_3_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_runner_1_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_test_services_storage_1_4_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml create mode 100644 .idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml create mode 100644 .idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1.xml create mode 100644 .idea/libraries/Gradle__com_highcapable_yukihookapi_api_1_0_86.xml create mode 100644 .idea/libraries/Gradle__com_squareup_javawriter_2_1_1.xml create mode 100644 .idea/libraries/Gradle__de_robv_android_xposed_api_82.xml create mode 100644 .idea/libraries/Gradle__javax_inject_javax_inject_1.xml create mode 100644 .idea/libraries/Gradle__junit_junit_4_12.xml create mode 100644 .idea/libraries/Gradle__junit_junit_4_13_2.xml create mode 100644 .idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml create mode 100644 .idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3.xml create mode 100644 .idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3.xml create mode 100644 .idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml create mode 100644 .idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_21.xml create mode 100644 .idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_21.xml create mode 100644 .idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_21.xml create mode 100644 .idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_6_21.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/workspace.xml create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/fankes/apperrorstracking/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/xposed_init create mode 100644 app/src/main/assets/yukihookapi_init create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/fankes/apperrorstracking/hook/HookEntry.kt create mode 100644 app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Compatible.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Constants.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableProperties.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawable.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt create mode 100755 app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt create mode 100644 app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt create mode 100644 app/src/main/res/drawable/ic_baseline_bug_report.xml create mode 100644 app/src/main/res/drawable/ic_baseline_close.xml create mode 100644 app/src/main/res/drawable/ic_baseline_info.xml create mode 100644 app/src/main/res/drawable/ic_baseline_refresh.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/array.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/com/fankes/apperrorstracking/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 keystore/public create mode 100644 local.properties create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2a0e097 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +# Project exclude paths +/.gradle/ +.DS_Store diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.idea/.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/assetWizardSettings.xml b/.idea/assetWizardSettings.xml new file mode 100644 index 0000000..c52241f --- /dev/null +++ b/.idea/assetWizardSettings.xml @@ -0,0 +1,346 @@ + + + + + + \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..a0de2a1 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..146ab09 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_activity_activity_1_2_4_aar.xml b/.idea/libraries/Gradle__androidx_activity_activity_1_2_4_aar.xml new file mode 100644 index 0000000..dd6fab5 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_activity_activity_1_2_4_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml b/.idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml new file mode 100644 index 0000000..7aace70 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_annotation_annotation_1_3_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml new file mode 100644 index 0000000..2eeee32 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_annotation_annotation_experimental_1_1_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_4_1_aar.xml b/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_4_1_aar.xml new file mode 100644 index 0000000..9977ca2 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_appcompat_appcompat_1_4_1_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_4_1_aar.xml b/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_4_1_aar.xml new file mode 100644 index 0000000..6c72c2d --- /dev/null +++ b/.idea/libraries/Gradle__androidx_appcompat_appcompat_resources_1_4_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml b/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml new file mode 100644 index 0000000..2208415 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_arch_core_core_common_2_1_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml b/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml new file mode 100644 index 0000000..dc9f55e --- /dev/null +++ b/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml b/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml new file mode 100644 index 0000000..7145764 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_arch_core_core_runtime_2_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml b/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml new file mode 100644 index 0000000..eafc05e --- /dev/null +++ b/.idea/libraries/Gradle__androidx_collection_collection_1_1_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml b/.idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml new file mode 100644 index 0000000..451d07c --- /dev/null +++ b/.idea/libraries/Gradle__androidx_core_core_1_7_0_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml new file mode 100644 index 0000000..301d07c --- /dev/null +++ b/.idea/libraries/Gradle__androidx_cursoradapter_cursoradapter_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml new file mode 100644 index 0000000..2e7e15c --- /dev/null +++ b/.idea/libraries/Gradle__androidx_customview_customview_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml new file mode 100644 index 0000000..bf00045 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_drawerlayout_drawerlayout_1_0_0_aar.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_fragment_fragment_1_3_6_aar.xml b/.idea/libraries/Gradle__androidx_fragment_fragment_1_3_6_aar.xml new file mode 100644 index 0000000..8770f74 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_fragment_fragment_1_3_6_aar.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml new file mode 100644 index 0000000..9a81347 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_interpolator_interpolator_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_4_0.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_4_0.xml new file mode 100644 index 0000000..9914e49 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_common_2_4_0.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml new file mode 100644 index 0000000..0306345 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_2_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_3_1_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_3_1_aar.xml new file mode 100644 index 0000000..1f06ffc --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_livedata_core_2_3_1_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml new file mode 100644 index 0000000..13c58d8 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_3_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_4_0_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_4_0_aar.xml new file mode 100644 index 0000000..dbbd948 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_runtime_2_4_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_3_1_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_3_1_aar.xml new file mode 100644 index 0000000..8acfe17 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_2_3_1_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_savedstate_2_3_1_aar.xml b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_savedstate_2_3_1_aar.xml new file mode 100644 index 0000000..76b14a0 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_lifecycle_lifecycle_viewmodel_savedstate_2_3_1_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml new file mode 100644 index 0000000..09ddd80 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_loader_loader_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_1_0_aar.xml new file mode 100644 index 0000000..9c69143 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_savedstate_savedstate_1_1_0_aar.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml b/.idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml new file mode 100644 index 0000000..e1e3545 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_core_1_4_0_aar.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_espresso_espresso_core_3_4_0_aar.xml b/.idea/libraries/Gradle__androidx_test_espresso_espresso_core_3_4_0_aar.xml new file mode 100644 index 0000000..c4edfcc --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_espresso_espresso_core_3_4_0_aar.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_espresso_espresso_idling_resource_3_4_0_aar.xml b/.idea/libraries/Gradle__androidx_test_espresso_espresso_idling_resource_3_4_0_aar.xml new file mode 100644 index 0000000..4457b39 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_espresso_espresso_idling_resource_3_4_0_aar.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_ext_junit_1_1_3_aar.xml b/.idea/libraries/Gradle__androidx_test_ext_junit_1_1_3_aar.xml new file mode 100644 index 0000000..86d4370 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_ext_junit_1_1_3_aar.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml b/.idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml new file mode 100644 index 0000000..5116d47 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_monitor_1_4_0_aar.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_runner_1_4_0_aar.xml b/.idea/libraries/Gradle__androidx_test_runner_1_4_0_aar.xml new file mode 100644 index 0000000..9392cdd --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_runner_1_4_0_aar.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_test_services_storage_1_4_0_aar.xml b/.idea/libraries/Gradle__androidx_test_services_storage_1_4_0_aar.xml new file mode 100644 index 0000000..fc71226 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_test_services_storage_1_4_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml new file mode 100644 index 0000000..4060993 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_1_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml new file mode 100644 index 0000000..d1799c6 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_vectordrawable_vectordrawable_animated_1_1_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml b/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml new file mode 100644 index 0000000..a385d90 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_versionedparcelable_versionedparcelable_1_1_1_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml b/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml new file mode 100644 index 0000000..cfae8f9 --- /dev/null +++ b/.idea/libraries/Gradle__androidx_viewpager_viewpager_1_0_0_aar.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1.xml b/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1.xml new file mode 100644 index 0000000..2b834ea --- /dev/null +++ b/.idea/libraries/Gradle__com_google_code_findbugs_jsr305_2_0_1.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__com_highcapable_yukihookapi_api_1_0_86.xml b/.idea/libraries/Gradle__com_highcapable_yukihookapi_api_1_0_86.xml new file mode 100644 index 0000000..e924d59 --- /dev/null +++ b/.idea/libraries/Gradle__com_highcapable_yukihookapi_api_1_0_86.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1.xml b/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1.xml new file mode 100644 index 0000000..662b001 --- /dev/null +++ b/.idea/libraries/Gradle__com_squareup_javawriter_2_1_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__de_robv_android_xposed_api_82.xml b/.idea/libraries/Gradle__de_robv_android_xposed_api_82.xml new file mode 100644 index 0000000..632e5ff --- /dev/null +++ b/.idea/libraries/Gradle__de_robv_android_xposed_api_82.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__javax_inject_javax_inject_1.xml b/.idea/libraries/Gradle__javax_inject_javax_inject_1.xml new file mode 100644 index 0000000..62012ea --- /dev/null +++ b/.idea/libraries/Gradle__javax_inject_javax_inject_1.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__junit_junit_4_12.xml b/.idea/libraries/Gradle__junit_junit_4_12.xml new file mode 100644 index 0000000..f7d27c4 --- /dev/null +++ b/.idea/libraries/Gradle__junit_junit_4_12.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__junit_junit_4_13_2.xml b/.idea/libraries/Gradle__junit_junit_4_13_2.xml new file mode 100644 index 0000000..198592d --- /dev/null +++ b/.idea/libraries/Gradle__junit_junit_4_13_2.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml b/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml new file mode 100644 index 0000000..09cf23d --- /dev/null +++ b/.idea/libraries/Gradle__org_hamcrest_hamcrest_core_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3.xml b/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3.xml new file mode 100644 index 0000000..1a77dd8 --- /dev/null +++ b/.idea/libraries/Gradle__org_hamcrest_hamcrest_integration_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3.xml b/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3.xml new file mode 100644 index 0000000..3d45e8e --- /dev/null +++ b/.idea/libraries/Gradle__org_hamcrest_hamcrest_library_1_3.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml b/.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml new file mode 100644 index 0000000..1fa0fa9 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_annotations_13_0.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_21.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_21.xml new file mode 100644 index 0000000..e9f77dc --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_6_21.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_21.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_21.xml new file mode 100644 index 0000000..5ea0bbf --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_common_1_6_21.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_21.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_21.xml new file mode 100644 index 0000000..c390012 --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk7_1_6_21.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_6_21.xml b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_6_21.xml new file mode 100644 index 0000000..60f842b --- /dev/null +++ b/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_jdk8_1_6_21.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..0f64d77 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,22 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f022303 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ 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/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 0000000..4181ec3 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1651624512624 + + + 1651859135240 + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..a78b971 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# AppErrorsTracking + +**应用异常跟踪** + +Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + +为原生 FC 对话框增加更多功能并修复国内定制 ROM 删除 FC 对话框的问题,给 Android 开发者带来更好的体验。 + +## Project Reason + +我实在是不能理解,国内 ROM 除了 MIUI(稳定版除外) 都选择了删除应用程序崩溃的对话框(FC 对话框),我曾以为这一直是一个特性,直到我去反编译了系统框架,才确认确实是被删掉了。 + +难道产品经理认为,让用户看不到错误,应用直接闪退,逃避就是最好的解决方案吗,还是说另有隐情呢? + +## Feature + +此项目为 Xposed 模块,可用在任何 Android 系统中,目前仅在 **LSPosed** 中测试通过。 + +- 重新定制应用崩溃错误对话框 + +- “错误详情”按钮功能,可查看具体的异常堆栈 + +- “应用信息”按钮功能(原生功能),点击可打开当前崩溃的应用详情页面 + +- “重新启动”按钮功能(原生功能),在首次崩溃可点击按钮重新启动应用 + +- “屡次停止运行”显示(原生功能) + +- 对话框支持 Android 10 及以上系统的深色模式 + +## Future + +此项目依然在开发中,现在未解决的问题和包含的问题如下 + +- “错误详情”按钮现在是无效的,还在开发 + +- 后台进程可能依然会弹出崩溃对话框且开发者选项里的设置无效,还在排查 + +- 无法启动后台进程和服务,是否在一定条件隐藏“重新打开”按钮的问题 + +- 暂不支持国际化语言,Chinese only + +## License + +- [AGPL-3.0](https://www.gnu.org/licenses/agpl-3.0.html) + +``` +Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as +published by the Free Software Foundation, either version 3 of the +License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +``` + +Powered by [YukiHookAPI](https://github.com/fankes/YukiHookAPI) + +版权所有 © 2019-2022 Fankes Studio(qzmmcn@163.com) \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/app/.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/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..78c1766 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,76 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' + id 'com.google.devtools.ksp' version '1.6.21-1.0.5' +} + +android { + namespace 'com.fankes.apperrorstracking' + compileSdk 31 + + signingConfigs { + debug { + storeFile file('../keystore/public') + storePassword '123456' + keyAlias 'public' + keyPassword '123456' + v1SigningEnabled true + v2SigningEnabled true + } + } + + defaultConfig { + applicationId "com.fankes.apperrorstracking" + minSdk 21 + targetSdk 31 + versionCode rootProject.ext.appVersionCode + versionName rootProject.ext.appVersionName + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled rootProject.ext.enableR8 + signingConfig signingConfigs.debug + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_11 + targetCompatibility JavaVersion.VERSION_11 + } + kotlinOptions { + jvmTarget = '11' + freeCompilerArgs = [ + '-Xno-param-assertions', + '-Xno-call-assertions', + '-Xno-receiver-assertions' + ] + } +} + +/** 移除无效耗时 lint Task */ +tasks.whenTaskAdded { + task -> if (task.name == "lintVitalRelease") task.enabled = false +} + +/** 移除无效耗时 lint Task */ +tasks.whenTaskAdded { + task -> if (task.name == "lintVitalAnalyzeRelease") task.enabled = false +} + +/** 移除无效耗时 lint Task */ +tasks.whenTaskAdded { + task -> if (task.name == "lintVitalReportRelease") task.enabled = false +} + +dependencies { + compileOnly 'de.robv.android.xposed:api:82' + implementation 'com.highcapable.yukihookapi:api:1.0.86' + ksp 'com.highcapable.yukihookapi:ksp-xposed:1.0.86' + implementation 'androidx.appcompat:appcompat:1.4.1' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..4d43f71 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,40 @@ +# 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 + +-ignorewarnings +-optimizationpasses 10 +-dontusemixedcaseclassnames +-dontoptimize +-verbose +-overloadaggressively +-allowaccessmodification +-adaptclassstrings +-adaptresourcefilenames +-adaptresourcefilecontents + +-renamesourcefileattribute P +-keepattributes SourceFile,LineNumberTable + +-assumenosideeffects class kotlin.jvm.internal.Intrinsics { + public static *** throwUninitializedProperty(...); + public static *** throwUninitializedPropertyAccessException(...); +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/fankes/apperrorstracking/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/fankes/apperrorstracking/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..7e6f3aa --- /dev/null +++ b/app/src/androidTest/java/com/fankes/apperrorstracking/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.fankes.apperrorstracking + +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.fankes.apperrorstracking", 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..1befeaa --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/xposed_init b/app/src/main/assets/xposed_init new file mode 100644 index 0000000..a151fe4 --- /dev/null +++ b/app/src/main/assets/xposed_init @@ -0,0 +1 @@ +com.fankes.apperrorstracking.hook.AppErrorsTracking \ No newline at end of file diff --git a/app/src/main/assets/yukihookapi_init b/app/src/main/assets/yukihookapi_init new file mode 100644 index 0000000..f9e2cf5 --- /dev/null +++ b/app/src/main/assets/yukihookapi_init @@ -0,0 +1 @@ +com.fankes.apperrorstracking.hook.HookEntry \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..0ab5601a6a83696470cc3bf9dcf0496824dbdb7e GIT binary patch literal 8824 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 + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.hook + +import com.fankes.apperrorstracking.hook.entity.FrameworkHooker +import com.highcapable.yukihookapi.annotation.xposed.InjectYukiHookWithXposed +import com.highcapable.yukihookapi.hook.factory.configs +import com.highcapable.yukihookapi.hook.factory.encase +import com.highcapable.yukihookapi.hook.xposed.proxy.IYukiHookXposedInit + +@InjectYukiHookWithXposed(entryClassName = "AppErrorsTracking") +class HookEntry : IYukiHookXposedInit { + + override fun onInit() = configs { + debugTag = "AppErrorsTracking" + isDebug = false + } + + override fun onHook() = encase { loadSystem(FrameworkHooker()) } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt b/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt new file mode 100644 index 0000000..7fc3c7e --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/hook/entity/FrameworkHooker.kt @@ -0,0 +1,180 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +@file:Suppress("DEPRECATION", "UseCompatLoadingForDrawables") + +package com.fankes.apperrorstracking.hook.entity + +import android.app.AlertDialog +import android.content.Context +import android.content.pm.ApplicationInfo +import android.graphics.Color +import android.os.Message +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowManager +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import com.fankes.apperrorstracking.R +import com.fankes.apperrorstracking.utils.drawable.drawabletoolbox.DrawableBuilder +import com.fankes.apperrorstracking.utils.factory.dp +import com.fankes.apperrorstracking.utils.factory.isSystemInDarkMode +import com.fankes.apperrorstracking.utils.factory.openApp +import com.fankes.apperrorstracking.utils.factory.openSelfSetting +import com.highcapable.yukihookapi.hook.bean.VariousClass +import com.highcapable.yukihookapi.hook.entity.YukiBaseHooker +import com.highcapable.yukihookapi.hook.factory.field +import com.highcapable.yukihookapi.hook.factory.method +import com.highcapable.yukihookapi.hook.log.loggerE +import com.highcapable.yukihookapi.hook.type.android.MessageClass + +class FrameworkHooker : YukiBaseHooker() { + + companion object { + + private const val AppErrorsClass = "com.android.server.am.AppErrors" + + private const val AppErrorResultClass = "com.android.server.am.AppErrorResult" + + private const val AppErrorDialog_DataClass = "com.android.server.am.AppErrorDialog\$Data" + + private const val ProcessRecordClass = "com.android.server.am.ProcessRecord" + + private val ErrorDialogControllerClass = VariousClass( + "com.android.server.am.ProcessRecord\$ErrorDialogController", + "com.android.server.am.ErrorDialogController" + ) + } + + /** + * 创建对话框按钮 + * @param context 实例 + * @param drawableId 按钮图标 + * @param content 按钮文本 + * @param it 点击事件回调 + * @return [LinearLayout] + */ + private fun createButtonItem(context: Context, drawableId: Int, content: String, it: () -> Unit) = + LinearLayout(context).apply { + background = DrawableBuilder().rounded().cornerRadius(15.dp(context)).ripple().rippleColor(0xFFAAAAAA.toInt()).build() + gravity = Gravity.CENTER or Gravity.START + layoutParams = + ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) + addView(ImageView(context).apply { + setImageDrawable(moduleAppResources.getDrawable(drawableId)) + layoutParams = ViewGroup.LayoutParams(25.dp(context), 25.dp(context)) + setColorFilter(if (context.isSystemInDarkMode) Color.WHITE else Color.BLACK) + }) + addView(View(context).apply { layoutParams = ViewGroup.LayoutParams(15.dp(context), 0) }) + addView(TextView(context).apply { + text = content + textSize = 16f + setTextColor(if (context.isSystemInDarkMode) 0xFFDDDDDD.toInt() else 0xFF777777.toInt()) + }) + setPadding(19.dp(context), 16.dp(context), 19.dp(context), 16.dp(context)) + setOnClickListener { it() } + } + + override fun onHook() { + /** 干掉原生错误对话框 - 如果有 */ + ErrorDialogControllerClass.hook { + injectMember { + method { + name = "hasCrashDialogs" + emptyParam() + } + replaceToTrue() + } + injectMember { + method { + name = "showCrashDialogs" + paramCount = 1 + } + intercept() + } + } + /** 注入自定义错误对话框 */ + AppErrorsClass.hook { + injectMember { + method { + name = "handleShowAppErrorUi" + param(MessageClass) + } + afterHook { + /** 当前实例 */ + val context = field { name = "mContext" }.get(instance).cast() ?: return@afterHook + + /** 错误数据 */ + val errData = args().first().cast()?.obj + + /** 错误结果 */ + val errResult = AppErrorResultClass.clazz.method { + name = "get" + emptyParam() + }.get(AppErrorDialog_DataClass.clazz.field { + name = "result" + }.get(errData).any()).int() + + /** 当前 APP 信息 */ + val appInfo = ProcessRecordClass.clazz.field { name = "info" } + .get(AppErrorDialog_DataClass.clazz.field { name = "proc" } + .get(errData).any()).cast() ?: ApplicationInfo() + + /** 是否短时内重复错误 */ + val isRepeating = AppErrorDialog_DataClass.clazz.field { name = "repeating" }.get(errData).boolean() + /** 判断在后台就不显示对话框 */ + if (errResult == -2) return@afterHook + /** 创建自定义对话框 */ + AlertDialog.Builder( + context, if (context.isSystemInDarkMode) + android.R.style.Theme_Material_Dialog + else android.R.style.Theme_Material_Light_Dialog + ).create().apply { + setTitle("${appInfo.loadLabel(context.packageManager)} ${if (isRepeating) "屡次停止运行" else "已停止运行"}") + setView(LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + addView(createButtonItem(context, R.drawable.ic_baseline_info, content = "应用信息") { + cancel() + context.openSelfSetting(packageName = appInfo.packageName) + }) + if (isRepeating) + addView(createButtonItem(context, R.drawable.ic_baseline_close, content = "关闭应用") { cancel() }) + else addView(createButtonItem(context, R.drawable.ic_baseline_refresh, content = "重新打开") { + cancel() + context.openApp(appInfo.packageName) + }) + addView(createButtonItem(context, R.drawable.ic_baseline_bug_report, content = "错误详情") { + // TODO 待开发 + }) + setPadding(6.dp(context), 15.dp(context), 6.dp(context), 6.dp(context)) + }) + /** 只有 SystemUid 才能响应系统级别的对话框 */ + window?.setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT) + }.show() + /** 打印错误日志 */ + loggerE(msg = "Process \"${appInfo.packageName}\" has crashed, isRepeating --> $isRepeating") + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Compatible.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Compatible.kt new file mode 100755 index 0000000..65971c3 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Compatible.kt @@ -0,0 +1,292 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +@file:Suppress("SameParameterValue") + +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.annotation.SuppressLint +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable +import android.graphics.drawable.RotateDrawable +import android.os.Build +import java.lang.reflect.Field +import java.lang.reflect.Method + +private val gradientState = resolveGradientState() + +private fun resolveGradientState(): Class<*> { + val classes = GradientDrawable::class.java.declaredClasses + for (singleClass in classes) { + if (singleClass.simpleName == "GradientState") return singleClass + } + throw RuntimeException("GradientState could not be found in thisAny GradientDrawable implementation") +} + +private val rotateState = resolveRotateState() + +private fun resolveRotateState(): Class<*> { + val classes = RotateDrawable::class.java.declaredClasses + for (singleClass in classes) { + if (singleClass.simpleName == "RotateState") return singleClass + } + throw RuntimeException("RotateState could not be found in thisAny RotateDrawable implementation") +} + +@Throws(SecurityException::class, NoSuchFieldException::class) +private fun resolveField(source: Class<*>, fieldName: String): Field { + val field = source.getDeclaredField(fieldName) + field.isAccessible = true + return field +} + +@Throws(SecurityException::class, NoSuchMethodException::class) +private fun resolveMethod( + source: Class<*>, + methodName: String, + vararg parameterTypes: Class<*> +): Method { + val method = source.getDeclaredMethod(methodName, *parameterTypes) + method.isAccessible = true + return method +} + +fun setInnerRadius(drawable: GradientDrawable, value: Int) { + try { + val innerRadius = resolveField(gradientState, "mInnerRadius") + innerRadius.setInt(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setInnerRadiusRatio(drawable: GradientDrawable, value: Float) { + try { + val innerRadius = resolveField(gradientState, "mInnerRadiusRatio") + innerRadius.setFloat(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setThickness(drawable: GradientDrawable, value: Int) { + try { + val innerRadius = resolveField(gradientState, "mThickness") + innerRadius.setInt(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setThicknessRatio(drawable: GradientDrawable, value: Float) { + try { + val innerRadius = resolveField(gradientState, "mThicknessRatio") + innerRadius.setFloat(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setUseLevelForShape(drawable: GradientDrawable, value: Boolean) { + try { + val useLevelForShape = resolveField(gradientState, "mUseLevelForShape") + useLevelForShape.setBoolean(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +@SuppressLint("ObsoleteSdkInt") +fun setOrientation(drawable: GradientDrawable, value: GradientDrawable.Orientation) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + drawable.orientation = value + } else { + try { + val orientation = resolveField(gradientState, "mOrientation") + orientation.set(drawable.constantState, value) + val rectIdDirty = resolveField(GradientDrawable::class.java, "mRectIsDirty") + rectIdDirty.setBoolean(drawable, true) + drawable.invalidateSelf() + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +@SuppressLint("ObsoleteSdkInt") +fun setColors(drawable: GradientDrawable, value: IntArray) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + drawable.colors = value + } else { + try { + val colors = resolveField(gradientState, "mColors") + colors.set(drawable.constantState, value) + drawable.invalidateSelf() + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +fun setGradientRadiusType(drawable: GradientDrawable, value: Int) { + try { + val type = resolveField(gradientState, "mGradientRadiusType") + type.setInt(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setGradientRadius(drawable: GradientDrawable, value: Float) { + try { + val gradientRadius = resolveField(gradientState, "mGradientRadius") + gradientRadius.setFloat(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setStrokeColor(drawable: GradientDrawable, value: Int) { + try { + val type = resolveField(gradientState, "mStrokeColor") + type.setInt(drawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } +} + +fun setDrawable(rotateDrawable: RotateDrawable, drawable: Drawable) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + rotateDrawable.drawable = drawable + } else { + try { + val drawableField = resolveField(rotateState, "mDrawable") + val stateField = resolveField(RotateDrawable::class.java, "mState") + drawableField.set(stateField.get(rotateDrawable), drawable) + drawable.callback = rotateDrawable + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +fun setPivotX(rotateDrawable: RotateDrawable, value: Float) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + rotateDrawable.pivotX = value + } else { + try { + val pivotXField = resolveField(rotateState, "mPivotX") + pivotXField.setFloat(rotateDrawable.constantState, value) + val pivotXRelField = resolveField(rotateState, "mPivotXRel") + pivotXRelField.setBoolean(rotateDrawable.constantState, true) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +fun setPivotY(rotateDrawable: RotateDrawable, value: Float) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + rotateDrawable.pivotY = value + } else { + try { + val pivotYField = resolveField(rotateState, "mPivotY") + pivotYField.setFloat(rotateDrawable.constantState, value) + val pivotYRelField = resolveField(rotateState, "mPivotYRel") + pivotYRelField.setBoolean(rotateDrawable.constantState, true) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +fun setFromDegrees(rotateDrawable: RotateDrawable, value: Float) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + rotateDrawable.fromDegrees = value + } else { + try { + val fromDegreesField = resolveField(rotateState, "mFromDegrees") + fromDegreesField.setFloat(rotateDrawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +fun setToDegrees(rotateDrawable: RotateDrawable, value: Float) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + rotateDrawable.toDegrees = value + } else { + try { + val toDegreesField = resolveField(rotateState, "mToDegrees") + toDegreesField.setFloat(rotateDrawable.constantState, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} + +fun setRadius(rippleDrawable: RippleDrawable, value: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + rippleDrawable.radius = value + } else { + try { + val setRadiusMethod = + resolveMethod(RippleDrawable::class.java, "setMaxRadius", Int::class.java) + setRadiusMethod.invoke(rippleDrawable, value) + } catch (e: NoSuchFieldException) { + e.printStackTrace() + } catch (e: IllegalAccessException) { + e.printStackTrace() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Constants.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Constants.kt new file mode 100755 index 0000000..53d0bad --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/Constants.kt @@ -0,0 +1,29 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +class Constants { + + companion object { + const val DEFAULT_COLOR = 0xFFBA68C8.toInt() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableBuilder.kt new file mode 100755 index 0000000..e792d5d --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableBuilder.kt @@ -0,0 +1,489 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +@file:Suppress("unused", "MemberVisibilityCanBePrivate") + +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.content.res.ColorStateList +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.util.StateSet +import java.util.* +import java.util.concurrent.atomic.AtomicInteger + +class DrawableBuilder { + + private var properties = DrawableProperties() + private var order: AtomicInteger = AtomicInteger(1) + private var transformsMap = TreeMap Drawable>() + private var baseDrawable: Drawable? = null + + fun batch(properties: DrawableProperties) = apply { this.properties = properties.copy() } + fun baseDrawable(drawable: Drawable) = apply { baseDrawable = drawable } + + // + fun shape(shape: Int) = apply { properties.shape = shape } + + fun rectangle() = apply { shape(GradientDrawable.RECTANGLE) } + fun oval() = apply { shape(GradientDrawable.OVAL) } + fun line() = apply { shape(GradientDrawable.LINE) } + fun ring() = apply { shape(GradientDrawable.RING) } + fun innerRadius(innerRadius: Int) = apply { properties.innerRadius = innerRadius } + fun innerRadiusRatio(innerRadiusRatio: Float) = + apply { properties.innerRadiusRatio = innerRadiusRatio } + + fun thickness(thickness: Int) = apply { properties.thickness = thickness } + fun thicknessRatio(thicknessRatio: Float) = apply { properties.thicknessRatio = thicknessRatio } + + fun useLevelForRing(use: Boolean = true) = apply { properties.useLevelForRing = use } + + // + fun cornerRadius(cornerRadius: Int) = apply { properties.cornerRadius = cornerRadius } + + fun topLeftRadius(topLeftRadius: Int) = apply { properties.topLeftRadius = topLeftRadius } + fun topRightRadius(topRightRadius: Int) = apply { properties.topRightRadius = topRightRadius } + fun bottomRightRadius(bottomRightRadius: Int) = + apply { properties.bottomRightRadius = bottomRightRadius } + + fun bottomLeftRadius(bottomLeftRadius: Int) = + apply { properties.bottomLeftRadius = bottomLeftRadius } + + fun rounded() = apply { cornerRadius(Int.MAX_VALUE) } + fun cornerRadii( + topLeftRadius: Int, + topRightRadius: Int, + bottomRightRadius: Int, + bottomLeftRadius: Int + ) = apply { + topLeftRadius(topLeftRadius); topRightRadius(topRightRadius); bottomRightRadius( + bottomRightRadius + ); bottomLeftRadius(bottomLeftRadius) + } + + // + + fun gradient(useGradient: Boolean = true) = apply { properties.useGradient = useGradient } + + fun gradientType(type: Int) = apply { properties.type = type } + fun linearGradient() = apply { gradientType(GradientDrawable.LINEAR_GRADIENT) } + fun radialGradient() = apply { gradientType(GradientDrawable.RADIAL_GRADIENT) } + fun sweepGradient() = apply { gradientType(GradientDrawable.SWEEP_GRADIENT) } + fun angle(angle: Int) = apply { properties.angle = angle } + fun centerX(centerX: Float) = apply { properties.centerX = centerX } + fun centerY(centerY: Float) = apply { properties.centerY = centerY } + fun center(centerX: Float, centerY: Float) = apply { centerX(centerX); centerY(centerY) } + + fun useCenterColor(useCenterColor: Boolean = true) = + apply { properties.useCenterColor = useCenterColor } + + fun startColor(startColor: Int) = apply { properties.startColor = startColor } + fun centerColor(centerColor: Int) = apply { + properties.centerColor = centerColor + useCenterColor(true) + } + + fun endColor(endColor: Int) = apply { properties.endColor = endColor } + fun gradientColors(startColor: Int, endColor: Int, centerColor: Int?) = apply { + startColor(startColor); endColor(endColor) + useCenterColor(centerColor != null) + centerColor?.let { + centerColor(it) + } + } + + fun gradientRadiusType(gradientRadiusType: Int) = + apply { properties.gradientRadiusType = gradientRadiusType } + + fun gradientRadius(gradientRadius: Float) = apply { properties.gradientRadius = gradientRadius } + fun gradientRadius(radius: Float, type: Int) = + apply { gradientRadius(radius); gradientRadiusType(type) } + + fun gradientRadiusInPixel(radius: Float) = + apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_PIXELS) } + + fun gradientRadiusInFraction(radius: Float) = + apply { gradientRadius(radius); gradientRadiusType(DrawableProperties.RADIUS_TYPE_FRACTION) } + + fun useLevelForGradient(use: Boolean) = apply { properties.useLevelForGradient = use } + fun useLevelForGradient() = apply { useLevelForGradient(true) } + + // + fun width(width: Int) = apply { properties.width = width } + + fun height(height: Int) = apply { properties.height = height } + fun size(width: Int, height: Int) = apply { width(width); height(height) } + fun size(size: Int) = apply { width(size).height(size) } + + // + fun solidColor(solidColor: Int) = apply { properties.solidColor = solidColor } + + private var solidColorPressed: Int? = null + fun solidColorPressed(color: Int?) = apply { solidColorPressed = color } + private var solidColorPressedWhenRippleUnsupported: Int? = null + fun solidColorPressedWhenRippleUnsupported(color: Int?) = + apply { solidColorPressedWhenRippleUnsupported = color } + + private var solidColorDisabled: Int? = null + fun solidColorDisabled(color: Int?) = apply { solidColorDisabled = color } + private var solidColorSelected: Int? = null + fun solidColorSelected(color: Int?) = apply { solidColorSelected = color } + fun solidColorStateList(colorStateList: ColorStateList) = + apply { properties.solidColorStateList = colorStateList } + + // + fun strokeWidth(strokeWidth: Int) = apply { properties.strokeWidth = strokeWidth } + + fun strokeColor(strokeColor: Int) = apply { properties.strokeColor = strokeColor } + private var strokeColorPressed: Int? = null + fun strokeColorPressed(color: Int?) = apply { strokeColorPressed = color } + private var strokeColorDisabled: Int? = null + fun strokeColorDisabled(color: Int?) = apply { strokeColorDisabled = color } + private var strokeColorSelected: Int? = null + fun strokeColorSelected(color: Int?) = apply { strokeColorSelected = color } + fun strokeColorStateList(colorStateList: ColorStateList) = + apply { properties.strokeColorStateList = colorStateList } + + fun dashWidth(dashWidth: Int) = apply { properties.dashWidth = dashWidth } + fun dashGap(dashGap: Int) = apply { properties.dashGap = dashGap } + fun hairlineBordered() = apply { strokeWidth(1) } + fun shortDashed() = apply { dashWidth(12).dashGap(12) } + fun mediumDashed() = apply { dashWidth(24).dashGap(24) } + fun longDashed() = apply { dashWidth(36).dashGap(36) } + fun dashed() = apply { mediumDashed() } + + // + private var rotateOrder = 0 + + + fun rotate(boolean: Boolean = true) = apply { + properties.useRotate = boolean + rotateOrder = if (boolean) { + order.getAndIncrement() + } else { + 0 + } + } + + fun pivotX(pivotX: Float) = apply { properties.pivotX = pivotX } + fun pivotY(pivotY: Float) = apply { properties.pivotY = pivotY } + fun pivot(pivotX: Float, pivotY: Float) = apply { pivotX(pivotX).pivotY(pivotY) } + fun fromDegrees(degrees: Float) = apply { properties.fromDegrees = degrees } + fun toDegrees(degrees: Float) = apply { properties.toDegrees = degrees } + fun degrees(fromDegrees: Float, toDegrees: Float) = + apply { fromDegrees(fromDegrees).toDegrees(toDegrees) } + + fun degrees(degrees: Float) = apply { fromDegrees(degrees).toDegrees(degrees) } + fun rotate(fromDegrees: Float, toDegrees: Float) = + apply { rotate().fromDegrees(fromDegrees).toDegrees(toDegrees) } + + fun rotate(degrees: Float) = apply { rotate().degrees(degrees) } + + // + private var scaleOrder = 0 + + + fun scale(boolean: Boolean = true) = apply { + properties.useScale = boolean + scaleOrder = if (boolean) { + order.getAndIncrement() + } else { + 0 + } + } + + fun scaleLevel(level: Int) = apply { properties.scaleLevel = level } + fun scaleGravity(gravity: Int) = apply { properties.scaleGravity = gravity } + fun scaleWidth(scale: Float) = apply { properties.scaleWidth = scale } + fun scaleHeight(scale: Float) = apply { properties.scaleHeight = scale } + fun scale(scale: Float) = apply { scale().scaleWidth(scale).scaleHeight(scale) } + fun scale(scaleWidth: Float, scaleHeight: Float) = + apply { scale().scaleWidth(scaleWidth).scaleHeight(scaleHeight) } + + // flip + + fun flip(boolean: Boolean = true) = apply { properties.useFlip = boolean } + + fun orientation(orientation: Int) = apply { properties.orientation = orientation } + fun flipVertical() = apply { flip().orientation(FlipDrawable.ORIENTATION_VERTICAL) } + + // + + fun ripple(boolean: Boolean = true) = apply { properties.useRipple = boolean } + + fun rippleColor(color: Int) = apply { properties.rippleColor = color } + fun rippleColorStateList(colorStateList: ColorStateList) = + apply { properties.rippleColorStateList = colorStateList } + + fun rippleRadius(radius: Int) = apply { properties.rippleRadius = radius } + + fun build(): Drawable { + if (baseDrawable != null) { + return wrap(baseDrawable!!) + } + + var drawable: Drawable + + // fall back when ripple is unavailable on devices with API < 21 + if (shouldFallbackRipple()) { + if (solidColorPressedWhenRippleUnsupported != null) { + solidColorPressed(solidColorPressedWhenRippleUnsupported) + } else { + solidColorPressed(properties.rippleColor) + } + } + + if (needStateListDrawable()) { + drawable = StateListDrawableBuilder() + .pressed(buildPressedDrawable()) + .disabled(buildDisabledDrawable()) + .selected(buildSelectedDrawable()) + .normal(buildNormalDrawable()) + .build() + } else { + drawable = GradientDrawable() + setupGradientDrawable(drawable) + } + drawable = wrap(drawable) + return drawable + } + + private fun getSolidColorStateList(): ColorStateList { + if (properties.solidColorStateList != null) { + return properties.solidColorStateList!! + } + + val states = mutableListOf() + val colors = mutableListOf() + + solidColorPressed?.let { + states.add(intArrayOf(android.R.attr.state_pressed)) + colors.add(it) + } + solidColorDisabled?.let { + states.add(intArrayOf(-android.R.attr.state_enabled)) + colors.add(it) + } + solidColorSelected?.let { + states.add(intArrayOf(android.R.attr.state_selected)) + colors.add(it) + } + states.add(StateSet.WILD_CARD) + colors.add(properties.solidColor) + + return ColorStateList(states.toTypedArray(), colors.toIntArray()) + } + + private fun getStrokeColorStateList(): ColorStateList { + if (properties.strokeColorStateList != null) { + return properties.strokeColorStateList!! + } + + val states = mutableListOf() + val colors = mutableListOf() + + strokeColorPressed?.let { + states.add(intArrayOf(android.R.attr.state_pressed)) + colors.add(it) + } + strokeColorDisabled?.let { + states.add(intArrayOf(-android.R.attr.state_enabled)) + colors.add(it) + } + strokeColorSelected?.let { + states.add(intArrayOf(android.R.attr.state_selected)) + colors.add(it) + } + states.add(StateSet.WILD_CARD) + colors.add(properties.strokeColor) + + return ColorStateList(states.toTypedArray(), colors.toIntArray()) + } + + private fun buildPressedDrawable(): Drawable? { + if (solidColorPressed == null && strokeColorPressed == null) return null + + val pressedDrawable = GradientDrawable() + setupGradientDrawable(pressedDrawable) + solidColorPressed?.let { + pressedDrawable.setColor(it) + } + strokeColorPressed?.let { + setStrokeColor(pressedDrawable, it) + } + return pressedDrawable + } + + private fun buildDisabledDrawable(): Drawable? { + if (solidColorDisabled == null && strokeColorDisabled == null) return null + + val disabledDrawable = GradientDrawable() + setupGradientDrawable(disabledDrawable) + solidColorDisabled?.let { + disabledDrawable.setColor(it) + } + strokeColorDisabled?.let { + setStrokeColor(disabledDrawable, it) + } + return disabledDrawable + } + + private fun buildSelectedDrawable(): Drawable? { + if (solidColorSelected == null && strokeColorSelected == null) return null + + val selectedDrawable = GradientDrawable() + setupGradientDrawable(selectedDrawable) + solidColorSelected?.let { + selectedDrawable.setColor(it) + } + strokeColorSelected?.let { + setStrokeColor(selectedDrawable, it) + } + return selectedDrawable + } + + private fun buildNormalDrawable(): Drawable { + val pressedDrawable = GradientDrawable() + setupGradientDrawable(pressedDrawable) + return pressedDrawable + } + + private fun setupGradientDrawable(drawable: GradientDrawable) { + properties.apply { + drawable.shape = shape + if (shape == GradientDrawable.RING) { + setInnerRadius(drawable, innerRadius) + setInnerRadiusRatio(drawable, innerRadiusRatio) + setThickness(drawable, thickness) + setThicknessRatio(drawable, thicknessRatio) + setUseLevelForShape(drawable, useLevelForRing) + } + drawable.cornerRadii = getCornerRadii() + if (useGradient) { + drawable.gradientType = type + setGradientRadiusType(drawable, gradientRadiusType) + setGradientRadius(drawable, gradientRadius) + drawable.setGradientCenter(centerX, centerY) + setOrientation(drawable, getOrientation()) + setColors(drawable, getColors()) + drawable.useLevel = useLevelForGradient + } else { + drawable.color = getSolidColorStateList() + } + drawable.setSize(width, height) + drawable.setStroke( + strokeWidth, + getStrokeColorStateList(), + dashWidth.toFloat(), + dashGap.toFloat() + ) + } + } + + private fun needStateListDrawable(): Boolean { + return (hasStrokeColorStateList() || (!properties.useGradient && hasSolidColorStateList())) + } + + private fun needRotateDrawable(): Boolean { + return properties.useRotate && + !(properties.pivotX == 0.5f && properties.pivotY == 0.5f + && properties.fromDegrees == 0f && properties.toDegrees == 0f) + } + + private fun needScaleDrawable(): Boolean { + return properties.useScale + } + + private fun wrap(drawable: Drawable): Drawable { + var wrappedDrawable = drawable + + if (rotateOrder > 0) { + transformsMap[rotateOrder] = ::wrapRotateIfNeeded + } + if (scaleOrder > 0) { + transformsMap[scaleOrder] = ::wrapScaleIfNeeded + } + + for (action in transformsMap.values) { + wrappedDrawable = action.invoke(wrappedDrawable) + } + + if (properties.useFlip) { + wrappedDrawable = FlipDrawableBuilder() + .drawable(wrappedDrawable) + .orientation(properties.orientation) + .build() + } + + if (isRippleSupported() && properties.useRipple) { + wrappedDrawable = RippleDrawableBuilder() + .drawable(wrappedDrawable) + .color(properties.rippleColor) + .colorStateList(properties.rippleColorStateList) + .radius(properties.rippleRadius) + .build() + } + + return wrappedDrawable + } + + private fun shouldFallbackRipple(): Boolean { + return properties.useRipple && !isRippleSupported() + } + + private fun isRippleSupported() = true + + private fun wrapRotateIfNeeded(drawable: Drawable): Drawable { + if (!needRotateDrawable()) return drawable + + with(properties) { + return RotateDrawableBuilder() + .drawable(drawable) + .pivotX(pivotX) + .pivotY(pivotY) + .fromDegrees(fromDegrees) + .toDegrees(toDegrees) + .build() + } + } + + private fun wrapScaleIfNeeded(drawable: Drawable): Drawable { + if (!needScaleDrawable()) return drawable + + with(properties) { + return ScaleDrawableBuilder() + .drawable(drawable) + .level(scaleLevel) + .scaleGravity(scaleGravity) + .scaleWidth(scaleWidth) + .scaleHeight(scaleHeight) + .build() + } + } + + private fun hasSolidColorStateList(): Boolean { + return solidColorPressed != null || solidColorDisabled != null || solidColorSelected != null + } + + private fun hasStrokeColorStateList(): Boolean { + return strokeColorPressed != null || strokeColorDisabled != null || strokeColorSelected != null + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableProperties.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableProperties.kt new file mode 100755 index 0000000..daef2b6 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableProperties.kt @@ -0,0 +1,221 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +@file:Suppress("SetterBackingFieldAssignment", "unused") + +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.graphics.drawable.GradientDrawable +import android.os.Parcel +import android.os.Parcelable +import android.view.Gravity +import java.io.Serializable + +data class DrawableProperties( + + // + @JvmField var shape: Int = GradientDrawable.RECTANGLE, + @JvmField var innerRadius: Int = -1, + @JvmField var innerRadiusRatio: Float = 9f, + @JvmField var thickness: Int = -1, + @JvmField var thicknessRatio: Float = 3f, + @JvmField var useLevelForRing: Boolean = false, + + // + private var _cornerRadius: Int = 0, + @JvmField var topLeftRadius: Int = 0, + @JvmField var topRightRadius: Int = 0, + @JvmField var bottomRightRadius: Int = 0, + @JvmField var bottomLeftRadius: Int = 0, + + // + @JvmField var useGradient: Boolean = false, + @JvmField var type: Int = GradientDrawable.RADIAL_GRADIENT, + @JvmField var angle: Int = 0, + @JvmField var centerX: Float = 0.5f, + @JvmField var centerY: Float = 0.5f, + @JvmField var useCenterColor: Boolean = false, + @JvmField var startColor: Int = Constants.DEFAULT_COLOR, + @JvmField var centerColor: Int? = null, + @JvmField var endColor: Int = 0x7FFFFFFF, + @JvmField var gradientRadiusType: Int = RADIUS_TYPE_FRACTION, + @JvmField var gradientRadius: Float = 0.5f, + @JvmField var useLevelForGradient: Boolean = false, + + // + @JvmField var width: Int = -1, + @JvmField var height: Int = -1, + + // + @JvmField var solidColor: Int = Color.TRANSPARENT, + @JvmField var solidColorStateList: ColorStateList? = null, + + // + @JvmField var strokeWidth: Int = 0, + @JvmField var strokeColor: Int = Color.DKGRAY, + @JvmField var strokeColorStateList: ColorStateList? = null, + @JvmField var dashWidth: Int = 0, + @JvmField var dashGap: Int = 0, + + // + @JvmField var useRotate: Boolean = false, + @JvmField var pivotX: Float = 0.5f, + @JvmField var pivotY: Float = 0.5f, + @JvmField var fromDegrees: Float = 0f, + @JvmField var toDegrees: Float = 0f, + + // + @JvmField var useScale: Boolean = false, + @JvmField var scaleLevel: Int = 10000, + @JvmField var scaleGravity: Int = Gravity.CENTER, + @JvmField var scaleWidth: Float = 0f, + @JvmField var scaleHeight: Float = 0f, + + // flip + @JvmField var useFlip: Boolean = false, + @JvmField var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL, + + // ripple + @JvmField var useRipple: Boolean = false, + @JvmField var rippleColor: Int = Constants.DEFAULT_COLOR, + @JvmField var rippleColorStateList: ColorStateList? = null, + @JvmField var rippleRadius: Int = -1 +) : Serializable { + + companion object { + const val RADIUS_TYPE_PIXELS = 0 + const val RADIUS_TYPE_FRACTION = 1 + + @JvmField + val CREATOR = object : Parcelable.Creator { + override fun createFromParcel(parcel: Parcel): DrawableProperties { + return DrawableProperties(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + + var cornerRadius: Int = _cornerRadius + set(value) { + _cornerRadius = value + topLeftRadius = value + topRightRadius = value + bottomRightRadius = value + bottomLeftRadius = value + } + + constructor(parcel: Parcel) : this( + parcel.readInt(), + parcel.readInt(), + parcel.readFloat(), + parcel.readInt(), + parcel.readFloat(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readInt(), + parcel.readFloat(), + parcel.readFloat(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readValue(Int::class.java.classLoader) as? Int, + parcel.readInt(), + parcel.readInt(), + parcel.readFloat(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readInt(), + parcel.readInt(), + parcel.readParcelable(ColorStateList::class.java.classLoader), + parcel.readInt(), + parcel.readInt(), + parcel.readParcelable(ColorStateList::class.java.classLoader), + parcel.readInt(), + parcel.readInt(), + parcel.readByte() != 0.toByte(), + parcel.readFloat(), + parcel.readFloat(), + parcel.readFloat(), + parcel.readFloat(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readInt(), + parcel.readFloat(), + parcel.readFloat(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readByte() != 0.toByte(), + parcel.readInt(), + parcel.readParcelable(ColorStateList::class.java.classLoader), + parcel.readInt() + ) + + fun copy(): DrawableProperties { + val parcel = Parcel.obtain() + parcel.setDataPosition(0) + val properties = CREATOR.createFromParcel(parcel) + parcel.recycle() + return properties + } + + fun getCornerRadii(): FloatArray { + return floatArrayOf( + topLeftRadius.toFloat(), topLeftRadius.toFloat(), + topRightRadius.toFloat(), topRightRadius.toFloat(), + bottomRightRadius.toFloat(), bottomRightRadius.toFloat(), + bottomLeftRadius.toFloat(), bottomLeftRadius.toFloat() + ) + } + + fun getOrientation(): GradientDrawable.Orientation { + val orientation: GradientDrawable.Orientation = when (val angle = this.angle % 360) { + 0 -> GradientDrawable.Orientation.LEFT_RIGHT + 45 -> GradientDrawable.Orientation.BL_TR + 90 -> GradientDrawable.Orientation.BOTTOM_TOP + 135 -> GradientDrawable.Orientation.BR_TL + 180 -> GradientDrawable.Orientation.RIGHT_LEFT + 225 -> GradientDrawable.Orientation.TR_BL + 270 -> GradientDrawable.Orientation.TOP_BOTTOM + 315 -> GradientDrawable.Orientation.TL_BR + else -> throw IllegalArgumentException("Unsupported angle: $angle") + } + return orientation + } + + fun getColors(): IntArray { + return if (useCenterColor && centerColor != null) { + intArrayOf(startColor, centerColor!!, endColor) + } else intArrayOf(startColor, endColor) + } + + fun materialization(): Drawable = DrawableBuilder().batch(this).build() +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt new file mode 100755 index 0000000..6fce541 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/DrawableWrapperBuilder.kt @@ -0,0 +1,34 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable + +abstract class DrawableWrapperBuilder> { + + protected var drawable: Drawable? = null + + @Suppress("UNCHECKED_CAST") + fun drawable(drawable: Drawable): T = apply { this.drawable = drawable } as T + + abstract fun build(): Drawable +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawable.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawable.kt new file mode 100755 index 0000000..aee1536 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawable.kt @@ -0,0 +1,78 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +@file:Suppress("DEPRECATION", "CanvasSize") + +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.Canvas +import android.graphics.ColorFilter +import android.graphics.Rect +import android.graphics.drawable.Drawable + +class FlipDrawable( + private var drawable: Drawable, + private var orientation: Int = ORIENTATION_HORIZONTAL +) : Drawable() { + + companion object { + const val ORIENTATION_HORIZONTAL = 0 + const val ORIENTATION_VERTICAL = 1 + } + + override fun draw(canvas: Canvas) { + val saveCount = canvas.save() + if (orientation == ORIENTATION_VERTICAL) { + canvas.scale(1f, -1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat()) + } else { + canvas.scale(-1f, 1f, (canvas.width / 2).toFloat(), (canvas.height / 2).toFloat()) + } + drawable.bounds = Rect(0, 0, canvas.width, canvas.height) + drawable.draw(canvas) + canvas.restoreToCount(saveCount) + } + + override fun onLevelChange(level: Int): Boolean { + drawable.level = level + invalidateSelf() + return true + } + + override fun getIntrinsicWidth(): Int { + return drawable.intrinsicWidth + } + + override fun getIntrinsicHeight(): Int { + return drawable.intrinsicHeight + } + + override fun setAlpha(alpha: Int) { + drawable.alpha = alpha + } + + override fun getOpacity(): Int { + return drawable.opacity + } + + override fun setColorFilter(colorFilter: ColorFilter?) { + drawable.colorFilter = colorFilter + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt new file mode 100755 index 0000000..cbf2fab --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/FlipDrawableBuilder.kt @@ -0,0 +1,35 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable + +class FlipDrawableBuilder : DrawableWrapperBuilder() { + + private var orientation: Int = FlipDrawable.ORIENTATION_HORIZONTAL + + fun orientation(orientation: Int) = apply { this.orientation = orientation } + + override fun build(): Drawable { + return FlipDrawable(drawable!!, orientation) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt new file mode 100755 index 0000000..ee553a9 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/LayerDrawableBuilder.kt @@ -0,0 +1,181 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable +import android.os.Build +import android.view.Gravity +import androidx.annotation.RequiresApi + +class LayerDrawableBuilder { + + companion object { + const val DIMEN_UNDEFINED = Int.MIN_VALUE + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private var paddingMode = LayerDrawable.PADDING_MODE_NEST + private var paddingLeft = 0 + private var paddingTop = 0 + private var paddingRight = 0 + private var paddingBottom = 0 + private var paddingStart = 0 + private var paddingEnd = 0 + private val layers = ArrayList() + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun paddingMode(mode: Int) = apply { paddingMode = mode } + fun paddingLeft(padding: Int) = apply { paddingLeft = padding } + fun paddingTop(padding: Int) = apply { paddingTop = padding } + fun paddingRight(padding: Int) = apply { paddingRight = padding } + fun paddingBottom(padding: Int) = apply { paddingBottom = padding } + + @RequiresApi(Build.VERSION_CODES.M) + fun paddingStart(padding: Int) = apply { paddingStart = padding } + + @RequiresApi(Build.VERSION_CODES.M) + fun paddingEnd(padding: Int) = apply { paddingEnd = padding } + fun padding(padding: Int) = apply { + paddingLeft(padding).paddingTop(padding).paddingRight(padding).paddingBottom(padding) + } + + @RequiresApi(Build.VERSION_CODES.M) + fun paddingRelative(padding: Int) = apply { + paddingStart(padding).paddingTop(padding).paddingEnd(padding).paddingBottom(padding) + } + + fun add(drawable: Drawable) = apply { layers.add(Layer(drawable)) } + + fun width(width: Int) = apply { layers.last().width = width } + fun height(height: Int) = apply { layers.last().height = height } + + fun insetLeft(inset: Int) = apply { layers.last().insetLeft = inset } + fun insetTop(inset: Int) = apply { layers.last().insetTop = inset } + fun insetRight(inset: Int) = apply { layers.last().insetRight = inset } + fun insetBottom(inset: Int) = apply { layers.last().insetBottom = inset } + + @RequiresApi(Build.VERSION_CODES.M) + fun insetStart(inset: Int) = apply { layers.last().insetStart = inset } + + @RequiresApi(Build.VERSION_CODES.M) + fun insetEnd(inset: Int) = apply { layers.last().insetEnd = inset } + fun inset(inset: Int) = + apply { insetLeft(inset).insetTop(inset).insetRight(inset).insetBottom(inset) } + + fun inset(insetLeft: Int, insetTop: Int, insetRight: Int, insetBottom: Int) = apply { + insetLeft(insetLeft).insetTop(insetTop).insetRight(insetRight).insetBottom(insetBottom) + } + + @RequiresApi(Build.VERSION_CODES.M) + fun insetRelative(inset: Int) = + apply { insetStart(inset).insetTop(inset).insetEnd(inset).insetBottom(inset) } + + @RequiresApi(Build.VERSION_CODES.M) + fun insetRelative(insetStart: Int, insetTop: Int, insetEnd: Int, insetBottom: Int) = apply { + insetStart(insetStart).insetTop(insetTop).insetEnd(insetEnd).insetBottom(insetBottom) + } + + @RequiresApi(Build.VERSION_CODES.M) + fun gravity(gravity: Int) = apply { layers.last().gravity = gravity } + + fun modify( + index: Int, drawable: Drawable, + width: Int = DIMEN_UNDEFINED, + height: Int = DIMEN_UNDEFINED, + insetLeft: Int = DIMEN_UNDEFINED, + insetTop: Int = DIMEN_UNDEFINED, + insetRight: Int = DIMEN_UNDEFINED, + insetBottom: Int = DIMEN_UNDEFINED, + insetStart: Int = DIMEN_UNDEFINED, + insetEnd: Int = DIMEN_UNDEFINED + ) = + apply { + val layer = layers[index] + layer.drawable = drawable + if (width != DIMEN_UNDEFINED) layer.width = width + if (height != DIMEN_UNDEFINED) layer.height = height + if (insetLeft != DIMEN_UNDEFINED) layer.insetLeft = insetLeft + if (insetTop != DIMEN_UNDEFINED) layer.insetTop = insetTop + if (insetRight != DIMEN_UNDEFINED) layer.insetRight = insetRight + if (insetBottom != DIMEN_UNDEFINED) layer.insetBottom = insetBottom + if (insetStart != DIMEN_UNDEFINED) layer.insetStart = insetStart + if (insetEnd != DIMEN_UNDEFINED) layer.insetEnd = insetEnd + } + + fun build(): LayerDrawable { + val layerDrawable = LayerDrawable(layers.map { it -> it.drawable }.toTypedArray()) + for (i in 0 until layers.size) { + val layer = layers[i] + layerDrawable.setLayerInset( + i, + layer.insetLeft, + layer.insetTop, + layer.insetRight, + layer.insetBottom + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (layer.insetStart != DIMEN_UNDEFINED || layer.insetEnd != DIMEN_UNDEFINED) { + layerDrawable.setLayerInsetRelative( + i, + layer.insetStart, + layer.insetTop, + layer.insetEnd, + layer.insetBottom + ) + } + } + layerDrawable.setId(i, i) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + layerDrawable.setLayerGravity(i, layer.gravity) + layerDrawable.setLayerInsetStart(i, layer.insetStart) + layerDrawable.setLayerInsetEnd(i, layer.insetEnd) + } + } + layerDrawable.paddingMode = paddingMode + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + layerDrawable.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom) + if (paddingStart != DIMEN_UNDEFINED || paddingEnd != DIMEN_UNDEFINED) { + layerDrawable.setPaddingRelative( + paddingStart, + paddingTop, + paddingEnd, + paddingBottom + ) + } + } + + return layerDrawable + } + + internal class Layer(var drawable: Drawable) { + var gravity: Int = Gravity.NO_GRAVITY + var width: Int = -1 + var height: Int = -1 + var insetLeft: Int = 0 + var insetTop: Int = 0 + var insetRight: Int = 0 + var insetBottom: Int = 0 + var insetStart: Int = DIMEN_UNDEFINED + var insetEnd: Int = DIMEN_UNDEFINED + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt new file mode 100755 index 0000000..29660ee --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/PathShapeDrawableBuilder.kt @@ -0,0 +1,61 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.Path +import android.graphics.drawable.ShapeDrawable +import android.graphics.drawable.shapes.PathShape + +class PathShapeDrawableBuilder { + + private var path: Path? = null + private var pathStandardWidth: Float = 100f + private var pathStandardHeight: Float = 100f + private var width: Int = -1 + private var height: Int = -1 + + fun path(path: Path, pathStandardWidth: Float, pathStandardHeight: Float) = apply { + this.path = path + this.pathStandardWidth = pathStandardWidth + this.pathStandardHeight = pathStandardHeight + } + + fun width(width: Int) = apply { this.width = width } + fun height(height: Int) = apply { this.height = height } + fun size(size: Int) = apply { width(size).height(size) } + + fun build(custom: ((shapeDrawable: ShapeDrawable) -> Unit)? = null): ShapeDrawable { + val shapeDrawable = ShapeDrawable() + if (path == null || width <= 0 || height <= 0) { + return shapeDrawable + } + val pathShape = PathShape(path!!, pathStandardWidth, pathStandardHeight) + + shapeDrawable.shape = pathShape + shapeDrawable.intrinsicWidth = width + shapeDrawable.intrinsicHeight = height + if (custom != null) { + custom(shapeDrawable) + } + return shapeDrawable + } +} diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt new file mode 100755 index 0000000..b5913fd --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RippleDrawableBuilder.kt @@ -0,0 +1,73 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.drawable.* +import android.util.StateSet + +class RippleDrawableBuilder : DrawableWrapperBuilder() { + + private var color: Int = Constants.DEFAULT_COLOR + private var colorStateList: ColorStateList? = null + private var radius: Int = -1 + + fun color(color: Int) = apply { this.color = color } + fun colorStateList(colorStateList: ColorStateList?) = + apply { this.colorStateList = colorStateList } + + fun radius(radius: Int) = apply { this.radius = radius } + + override fun build(): Drawable { + var drawable = this.drawable!! + val colorStateList = this.colorStateList ?: ColorStateList( + arrayOf(StateSet.WILD_CARD), + intArrayOf(color) + ) + + var mask = if (drawable is DrawableContainer) drawable.getCurrent() else drawable + if (mask is ShapeDrawable) { + val state = mask.getConstantState() + if (state != null) { + val temp = state.newDrawable().mutate() as ShapeDrawable + temp.paint.color = Color.BLACK + mask = temp + } + } else if (mask is GradientDrawable) { + val state = mask.getConstantState() + if (state != null) { + val temp = state.newDrawable().mutate() as GradientDrawable + temp.setColor(Color.BLACK) + mask = temp + } + } else { + mask = ColorDrawable(Color.BLACK) + } + + val rippleDrawable = RippleDrawable(colorStateList, drawable, mask) + setRadius(rippleDrawable, radius) + rippleDrawable.invalidateSelf() + drawable = rippleDrawable + return drawable + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt new file mode 100755 index 0000000..30c9d47 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/RotateDrawableBuilder.kt @@ -0,0 +1,52 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable +import android.graphics.drawable.RotateDrawable + +class RotateDrawableBuilder : DrawableWrapperBuilder() { + + private var pivotX: Float = 0.5f + private var pivotY: Float = 0.5f + private var fromDegrees: Float = 0f + private var toDegrees: Float = 360f + + fun pivotX(x: Float) = apply { pivotX = x } + fun pivotY(y: Float) = apply { pivotY = y } + fun fromDegrees(degree: Float) = apply { fromDegrees = degree } + fun toDegrees(degree: Float) = apply { toDegrees = degree } + + override fun build(): Drawable { + val rotateDrawable = RotateDrawable() + drawable?.let { + setDrawable(rotateDrawable, it) + apply { + setPivotX(rotateDrawable, pivotX) + setPivotY(rotateDrawable, pivotY) + setFromDegrees(rotateDrawable, fromDegrees) + setToDegrees(rotateDrawable, toDegrees) + } + } + return rotateDrawable + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt new file mode 100755 index 0000000..b413778 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/ScaleDrawableBuilder.kt @@ -0,0 +1,45 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.drawable.Drawable +import android.graphics.drawable.ScaleDrawable +import android.view.Gravity + +class ScaleDrawableBuilder : DrawableWrapperBuilder() { + + private var level: Int = 10000 + private var scaleGravity = Gravity.CENTER + private var scaleWidth: Float = 0f + private var scaleHeight: Float = 0f + + fun level(level: Int) = apply { this.level = level } + fun scaleGravity(gravity: Int) = apply { this.scaleGravity = gravity } + fun scaleWidth(scale: Float) = apply { this.scaleWidth = scale } + fun scaleHeight(scale: Float) = apply { this.scaleHeight = scale } + + override fun build(): Drawable { + val scaleDrawable = ScaleDrawable(drawable, scaleGravity, scaleWidth, scaleHeight) + scaleDrawable.level = level + return scaleDrawable + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt new file mode 100755 index 0000000..33254c2 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/drawable/drawabletoolbox/StateListDrawableBuilder.kt @@ -0,0 +1,60 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +package com.fankes.apperrorstracking.utils.drawable.drawabletoolbox + +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.graphics.drawable.StateListDrawable +import android.util.StateSet + +class StateListDrawableBuilder { + + private var pressed: Drawable? = null + private var disabled: Drawable? = null + private var selected: Drawable? = null + private var normal: Drawable = ColorDrawable(Color.TRANSPARENT) + + fun pressed(pressed: Drawable?) = apply { this.pressed = pressed } + fun disabled(disabled: Drawable?) = apply { this.disabled = disabled } + fun selected(selected: Drawable?) = apply { this.selected = selected } + fun normal(normal: Drawable) = apply { this.normal = normal } + + fun build(): StateListDrawable { + val stateListDrawable = StateListDrawable() + setupStateListDrawable(stateListDrawable) + return stateListDrawable + } + + private fun setupStateListDrawable(stateListDrawable: StateListDrawable) { + pressed?.let { + stateListDrawable.addState(intArrayOf(android.R.attr.state_pressed), it) + } + disabled?.let { + stateListDrawable.addState(intArrayOf(-android.R.attr.state_enabled), it) + } + selected?.let { + stateListDrawable.addState(intArrayOf(android.R.attr.state_selected), it) + } + stateListDrawable.addState(StateSet.WILD_CARD, normal) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt b/app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt new file mode 100644 index 0000000..85600bf --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/utils/factory/FunctionFactory.kt @@ -0,0 +1,79 @@ +/* + * AppErrorsTracking - Added more features to app's crash dialog, fixed custom rom deleted dialog, the best experience to Android developer. + * Copyright (C) 2019-2022 Fankes Studio(qzmmcn@163.com) + * https://github.com/KitsunePie/AppErrorsTracking + * + * This software is non-free but opensource software: you can redistribute it + * and/or modify it under the terms of the GNU Affero General Public License + * as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * and eula along with this software. If not, see + * + * + * This file is Created by fankes on 2022/5/7. + */ +@file:Suppress("DEPRECATION", "PrivateApi", "unused", "ObsoleteSdkInt") + +package com.fankes.apperrorstracking.utils.factory + +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.net.Uri +import android.provider.Settings +import android.widget.Toast + +/** + * 系统深色模式是否开启 + * @return [Boolean] 是否开启 + */ +val Context.isSystemInDarkMode get() = (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES + +/** + * 系统深色模式是否没开启 + * @return [Boolean] 是否开启 + */ +inline val Context.isNotSystemInDarkMode get() = !isSystemInDarkMode + +/** + * dp 转换为 pxInt + * @param context 使用的实例 + * @return [Int] + */ +fun Number.dp(context: Context) = dpFloat(context).toInt() + +/** + * dp 转换为 pxFloat + * @param context 使用的实例 + * @return [Float] + */ +fun Number.dpFloat(context: Context) = toFloat() * context.resources.displayMetrics.density + +/** + * 跳转 APP 自身设置界面 + * @param packageName 包名 + */ +fun Context.openSelfSetting(packageName: String = this.packageName) = runCatching { + startActivity(Intent().apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + action = Settings.ACTION_APPLICATION_DETAILS_SETTINGS + data = Uri.fromParts("package", packageName, null) + }) +}.onFailure { Toast.makeText(this, "无法打开 $packageName 的设置界面", Toast.LENGTH_SHORT).show() } + +/** + * 启动指定 APP + * @param packageName 包名 + */ +fun Context.openApp(packageName: String = this.packageName) = runCatching { + startActivity(packageManager.getLaunchIntentForPackage(packageName)?.apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + }) +}.onFailure { Toast.makeText(this, "无法启动 $packageName", Toast.LENGTH_SHORT).show() } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_bug_report.xml b/app/src/main/res/drawable/ic_baseline_bug_report.xml new file mode 100644 index 0000000..04a0fa8 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_bug_report.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_close.xml b/app/src/main/res/drawable/ic_baseline_close.xml new file mode 100644 index 0000000..47222ae --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_close.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_info.xml b/app/src/main/res/drawable/ic_baseline_info.xml new file mode 100644 index 0000000..cd7c081 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_info.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_refresh.xml b/app/src/main/res/drawable/ic_baseline_refresh.xml new file mode 100644 index 0000000..1465cb6 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_refresh.xml @@ -0,0 +1,10 @@ + + + \ 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/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..4e096a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + \ 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 0000000000000000000000000000000000000000..8870dd239a3ec691167963b8f90dcd32d3787966 GIT binary patch literal 1259 zcmViSWpPXu-?(8(e}E{BGDjAqm&AY!2hJ6LW&GYi(1$}IVJi5 z*$=CKA`DVD>5r(ktSxCKg<%rLDwhjWx6{-4?(VevKIgUb-uo)|2j_tg_rCW$=X>7o zclVyb#wGIQ%a<=-*)Ark;Udk*721>Z8LN0b)yivmsj9t&1$Z$n;V#;sd67Hm1^u}@ zQ{X$}yTgO_QqE(fG86UfSg%F<{^=qHsx$jcdFeZ@y@kD2T;b0 zeX1=6H$s#>37Z&>Cp&4W2}k}EvX1sOexieT|3bPm?75QgT1CscF9Qc47_<$UwI-qq+Sl!iVUq2J*#TgR)j+5bY?#p=GRgD~!&7l3nI!5Lna9>hSla zAss%z>X#o`{c$t^=eQOQ2Xio2>cu6s0fEPE4)zpd-UWIeU2N-ALuvzB*}!UFZ|U9P z0=)yfg1J&JE~yM?=`2>mU)ljw3g$vX>c$~;0d>`~+WCq$8sW~d&~rc98fZ!Vs3acH z7ay}4Ia12$lVwat$~s)IY>O9m)Ek|@q9qvyS6C< zHeS;w`M(Xl_nqlJtR>fXk$6C1HmtM_8emZN8!#sO4Nu=;_eUXB0X=iKZLr#apeg&K z-~)1yctC!WcHjeY-~)27X%(xUhXZ!MX`3>9-8f-o$Eb)HYoym*<_%j(EMRwu^nU~mm%=s~ZX zUoEK*$gupm}3ykL#m6Ug0XvrStWnK{J2zyxMg;ucPgIo9Tl+ z1$wWqncff6dwuD-{9I|zxzLhFy24iE4GPw=*&kuE589j$5#4RoPCUaq-HbB*TO zi~|qwGP`LuT|`kIt#VZ$npv!)y28a&-!zLZ%QEmV>4hR}$`J}_=K){7eEG^m{R^Ql Vbh7 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 JveT~j{sA5RHpBn` literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..8c0275c9dce51cd565baaed39a786c533d970b83 GIT binary patch literal 6998 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 literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..07849073f3cf0b463d65b023bb6cfbd770fe4567 GIT binary patch literal 3861 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml new file mode 100644 index 0000000..d978039 --- /dev/null +++ b/app/src/main/res/values/array.xml @@ -0,0 +1,6 @@ + + + + android + + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..a35b997 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FF4400 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..a768263 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + 异常跟踪 + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..b1f8a06 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,4 @@ + + +