commit 26c3aabf4df7d41c4380d89776ad5b70e820a3f8 Author: Fankesyooni Date: Sat May 7 01:48:53 2022 +0800 first commit 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 0000000..0ab5601 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/fankes/apperrorstracking/hook/HookEntry.kt b/app/src/main/java/com/fankes/apperrorstracking/hook/HookEntry.kt new file mode 100644 index 0000000..876f284 --- /dev/null +++ b/app/src/main/java/com/fankes/apperrorstracking/hook/HookEntry.kt @@ -0,0 +1,39 @@ +/* + * 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.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 0000000..8870dd2 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..9dc6e19 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..0378ce1 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..a45ba9d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..f5ed84d Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..293fb6f Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..457cdf2 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..8c0275c Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..0784907 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d112706 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ 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 @@ + + +