From 10f7fbbd66a586532d039a37a75ad5da0b3e089a Mon Sep 17 00:00:00 2001 From: fyf Date: Mon, 19 Jun 2023 15:38:09 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=80=E6=AC=A1=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 13 + .idea/.gitignore | 3 + .idea/compiler.xml | 6 + .idea/gradle.xml | 19 + .idea/jarRepositories.xml | 30 + .idea/misc.xml | 10 + README.md | 31 + app/.gitignore | 1 + app/build.gradle | 90 ++ app/libs/commons-codec-1.6.jar | Bin 0 -> 232771 bytes app/proguard-rules.pro | 21 + app/src/main/AndroidManifest.xml | 64 + app/src/main/assets/litepal.xml | 49 + .../bonait/bnframework/MainApplication.java | 151 ++ .../bnframework/common/base/BaseActivity.java | 86 ++ .../bnframework/common/base/BaseFragment.java | 87 ++ .../common/base/BaseFragmentActivity.java | 9 + .../common/constant/Constants.java | 31 + .../common/constant/SPConstants.java | 56 + .../bnframework/common/http/GsonConvert.java | 52 + .../callback/bitmap/BitmapDialogCallback.java | 44 + .../files/FileProgressDialogCallBack.java | 145 ++ .../http/callback/files2/FileCallback2.java | 40 + .../http/callback/files2/FileConvert2.java | 117 ++ .../files2/FileProgressDialogCallBack2.java | 145 ++ .../common/http/callback/files2/ReadMe.txt | 21 + .../http/callback/json/JsonCallback.java | 117 ++ .../http/callback/json/JsonConvert.java | 279 ++++ .../callback/json/JsonDialogCallback.java | 57 + .../strings/StringDialogCallback.java | 52 + .../common/http/exception/TokenException.java | 18 + .../common/interfaces/OnDoIntListener.java | 9 + .../common/model/BaseCodeJson.java | 56 + .../bnframework/common/model/BaseJson.java | 57 + .../common/model/SimpleBaseJson.java | 39 + .../common/model/SimpleCodeJson.java | 38 + .../notification/DownloadNotification.java | 98 ++ .../common/notification/MainNotification.java | 85 ++ .../common/utils/AlertDialogUtils.java | 104 ++ .../common/utils/AnimationToolUtils.java | 226 +++ .../common/utils/AppFileUtils.java | 217 +++ .../bnframework/common/utils/AppUtils.java | 108 ++ .../bnframework/common/utils/Des3Utils.java | 98 ++ .../bnframework/common/utils/FileUtils.java | 1300 +++++++++++++++++ .../common/utils/KeyboardToolUtils.java | 157 ++ .../common/utils/MediaFileUtils.java | 353 +++++ .../common/utils/NetworkUtils.java | 109 ++ .../common/utils/PreferenceUtils.java | 139 ++ .../bnframework/common/utils/ToastUtils.java | 278 ++++ .../common/utils/UpdateAppUtils.java | 148 ++ .../bnframework/common/utils/UriUtils.java | 195 +++ .../common/view/ClearEditTextView.java | 163 +++ .../common/view/QMAutoDialogBuilderView.java | 60 + .../manager/ActivityLifecycleManager.java | 232 +++ .../activity/BottomNavigation2Activity.java | 129 ++ .../activity/BottomNavigationActivity.java | 243 +++ .../modules/home/adapter/FragmentAdapter.java | 42 + .../modules/home/fragment/Home1Fragment.java | 83 ++ .../modules/home/fragment/Home2Fragment.java | 78 + .../modules/home/fragment/Home3Fragment.java | 68 + .../modules/mine/fragment/MyFragment.java | 220 +++ .../modules/mine/model/UpdateAppPo.java | 112 ++ .../welcome/activity/LoginActivity.java | 477 ++++++ .../welcome/activity/WelcomeActivity.java | 196 +++ .../modules/welcome/model/AppLoginPo.java | 82 ++ .../com/bonait/bnframework/test/Test.java | 7 + .../bonait/bnframework/test/TestActivity.java | 189 +++ app/src/main/res/color/s_app_color_blue_2.xml | 6 + app/src/main/res/color/s_app_color_gray.xml | 6 + app/src/main/res/color/s_btn_blue_bg.xml | 7 + app/src/main/res/color/s_btn_blue_border.xml | 7 + app/src/main/res/color/s_btn_blue_text.xml | 7 + app/src/main/res/color/s_topbar_btn_color.xml | 7 + .../main/res/drawable-hdpi/icon_pass_gone.png | Bin 0 -> 505 bytes .../res/drawable-hdpi/icon_pass_visuable.png | Bin 0 -> 463 bytes .../icon_personal_announcement.png | Bin 0 -> 1348 bytes .../res/drawable-hdpi/icon_personal_pwd.png | Bin 0 -> 1174 bytes .../res/drawable-hdpi/icon_personal_sex.png | Bin 0 -> 1154 bytes .../res/drawable-hdpi/icon_personal_sign.png | Bin 0 -> 1115 bytes .../drawable-hdpi/icon_personal_update.png | Bin 0 -> 658 bytes .../res/drawable-hdpi/icon_personal_user.png | Bin 0 -> 1070 bytes .../drawable-v24/ic_launcher_foreground.xml | 34 + .../res/drawable-xhdpi/icon_login_pwd.png | Bin 0 -> 1186 bytes .../res/drawable-xhdpi/icon_login_user.png | Bin 0 -> 1597 bytes .../res/drawable-xxhdpi/icon_delete_fill.png | Bin 0 -> 1198 bytes .../icon_delete_fill_select.png | Bin 0 -> 1127 bytes .../icon_personal_announcement.png | Bin 0 -> 1967 bytes .../icon_personal_download_files.png | Bin 0 -> 1707 bytes .../res/drawable-xxhdpi/icon_personal_pwd.png | Bin 0 -> 1970 bytes .../res/drawable-xxhdpi/icon_personal_sex.png | Bin 0 -> 2162 bytes .../drawable-xxhdpi/icon_personal_sign.png | Bin 0 -> 1632 bytes .../drawable-xxhdpi/icon_personal_update.png | Bin 0 -> 1266 bytes .../drawable-xxhdpi/icon_personal_user.png | Bin 0 -> 1793 bytes .../main/res/drawable-xxhdpi/icon_right.png | Bin 0 -> 2883 bytes .../res/drawable-xxhdpi/icon_user_pic.png | Bin 0 -> 4189 bytes .../res/drawable/bg_btn_login_selected.xml | 27 + .../common_bg_with_radius_and_border.xml | 17 + app/src/main/res/drawable/delete_selector.xml | 7 + .../res/drawable/ic_dashboard_black_24dp.xml | 9 + .../main/res/drawable/ic_home_black_24dp.xml | 9 + .../res/drawable/ic_launcher_background.xml | 170 +++ .../drawable/ic_notifications_black_24dp.xml | 9 + .../main/res/drawable/icon_home1_selector.xml | 7 + .../main/res/drawable/icon_home2_selector.xml | 7 + .../res/drawable/icon_home_my_selector.xml | 7 + .../main/res/drawable/radius_button_bg.xml | 18 + .../res/drawable/radius_button_bg_pressed.xml | 18 + .../main/res/drawable/s_radius_button_bg.xml | 6 + app/src/main/res/drawable/tab_panel_bg.xml | 15 + app/src/main/res/drawable/welcome.xml | 6 + .../res/layout/activity_bottom_navigation.xml | 25 + .../layout/activity_bottom_navigation2.xml | 28 + app/src/main/res/layout/activity_login.xml | 214 +++ app/src/main/res/layout/activity_test.xml | 67 + app/src/main/res/layout/activity_welcome.xml | 9 + app/src/main/res/layout/fragment_home1.xml | 39 + app/src/main/res/layout/fragment_home2.xml | 39 + app/src/main/res/layout/fragment_home3.xml | 39 + app/src/main/res/layout/fragment_my.xml | 117 ++ app/src/main/res/layout/toast_layout.xml | 20 + app/src/main/res/menu/navigation.xml | 19 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../res/mipmap-hdpi/ic_check_white_48dp.png | Bin 0 -> 276 bytes .../res/mipmap-hdpi/ic_clear_white_48dp.png | Bin 0 -> 347 bytes .../ic_error_outline_white_48dp.png | Bin 0 -> 2156 bytes .../ic_info_outline_white_48dp.png | Bin 0 -> 2311 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4905 bytes app/src/main/res/mipmap-hdpi/ico.png | Bin 0 -> 10017 bytes .../main/res/mipmap-hdpi/toast_frame.9.png | Bin 0 -> 1573 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2060 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2783 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4490 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 6895 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 6387 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 10413 bytes .../mipmap-xxhdpi/icon_tabbar_component.png | Bin 0 -> 848 bytes .../icon_tabbar_component_selected.png | Bin 0 -> 738 bytes .../res/mipmap-xxhdpi/icon_tabbar_lab.png | Bin 0 -> 1749 bytes .../icon_tabbar_lab_selected.png | Bin 0 -> 1241 bytes .../res/mipmap-xxhdpi/icon_tabbar_util.png | Bin 0 -> 1000 bytes .../icon_tabbar_util_selected.png | Bin 0 -> 911 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 9128 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 15132 bytes app/src/main/res/values/attr.xml | 6 + app/src/main/res/values/colors.xml | 110 ++ app/src/main/res/values/dimens.xml | 42 + app/src/main/res/values/ids.xml | 4 + app/src/main/res/values/strings.xml | 11 + app/src/main/res/values/styles.xml | 180 +++ app/src/main/res/xml/app_provider_paths.xml | 31 + .../main/res/xml/network_security_config.xml | 4 + build.gradle | 45 + gradle.properties | 15 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 +++ gradlew.bat | 84 ++ settings.gradle | 1 + 159 files changed, 9782 insertions(+) create mode 100644 .gitignore create mode 100644 .idea/.gitignore create mode 100644 .idea/compiler.xml create mode 100644 .idea/gradle.xml create mode 100644 .idea/jarRepositories.xml create mode 100644 .idea/misc.xml create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/libs/commons-codec-1.6.jar create mode 100644 app/proguard-rules.pro create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/assets/litepal.xml create mode 100644 app/src/main/java/com/bonait/bnframework/MainApplication.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/base/BaseActivity.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/base/BaseFragment.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/base/BaseFragmentActivity.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/constant/Constants.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/constant/SPConstants.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/GsonConvert.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/bitmap/BitmapDialogCallback.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/files/FileProgressDialogCallBack.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileCallback2.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileConvert2.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileProgressDialogCallBack2.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/files2/ReadMe.txt create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonCallback.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonConvert.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonDialogCallback.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/callback/strings/StringDialogCallback.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/http/exception/TokenException.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/interfaces/OnDoIntListener.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/model/BaseCodeJson.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/model/BaseJson.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/model/SimpleBaseJson.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/model/SimpleCodeJson.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/notification/DownloadNotification.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/notification/MainNotification.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/AlertDialogUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/AnimationToolUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/AppFileUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/AppUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/Des3Utils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/FileUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/KeyboardToolUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/MediaFileUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/NetworkUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/PreferenceUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/ToastUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/UpdateAppUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/utils/UriUtils.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/view/ClearEditTextView.java create mode 100644 app/src/main/java/com/bonait/bnframework/common/view/QMAutoDialogBuilderView.java create mode 100644 app/src/main/java/com/bonait/bnframework/manager/ActivityLifecycleManager.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigationActivity.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/home/adapter/FragmentAdapter.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home2Fragment.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home3Fragment.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/mine/fragment/MyFragment.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/mine/model/UpdateAppPo.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/welcome/activity/LoginActivity.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/welcome/activity/WelcomeActivity.java create mode 100644 app/src/main/java/com/bonait/bnframework/modules/welcome/model/AppLoginPo.java create mode 100644 app/src/main/java/com/bonait/bnframework/test/Test.java create mode 100644 app/src/main/java/com/bonait/bnframework/test/TestActivity.java create mode 100644 app/src/main/res/color/s_app_color_blue_2.xml create mode 100644 app/src/main/res/color/s_app_color_gray.xml create mode 100644 app/src/main/res/color/s_btn_blue_bg.xml create mode 100644 app/src/main/res/color/s_btn_blue_border.xml create mode 100644 app/src/main/res/color/s_btn_blue_text.xml create mode 100644 app/src/main/res/color/s_topbar_btn_color.xml create mode 100644 app/src/main/res/drawable-hdpi/icon_pass_gone.png create mode 100644 app/src/main/res/drawable-hdpi/icon_pass_visuable.png create mode 100644 app/src/main/res/drawable-hdpi/icon_personal_announcement.png create mode 100644 app/src/main/res/drawable-hdpi/icon_personal_pwd.png create mode 100644 app/src/main/res/drawable-hdpi/icon_personal_sex.png create mode 100644 app/src/main/res/drawable-hdpi/icon_personal_sign.png create mode 100644 app/src/main/res/drawable-hdpi/icon_personal_update.png create mode 100644 app/src/main/res/drawable-hdpi/icon_personal_user.png create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable-xhdpi/icon_login_pwd.png create mode 100644 app/src/main/res/drawable-xhdpi/icon_login_user.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_delete_fill.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_delete_fill_select.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_announcement.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_download_files.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_pwd.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_sex.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_sign.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_update.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_personal_user.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_right.png create mode 100644 app/src/main/res/drawable-xxhdpi/icon_user_pic.png create mode 100644 app/src/main/res/drawable/bg_btn_login_selected.xml create mode 100644 app/src/main/res/drawable/common_bg_with_radius_and_border.xml create mode 100644 app/src/main/res/drawable/delete_selector.xml create mode 100644 app/src/main/res/drawable/ic_dashboard_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_home_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_notifications_black_24dp.xml create mode 100644 app/src/main/res/drawable/icon_home1_selector.xml create mode 100644 app/src/main/res/drawable/icon_home2_selector.xml create mode 100644 app/src/main/res/drawable/icon_home_my_selector.xml create mode 100644 app/src/main/res/drawable/radius_button_bg.xml create mode 100644 app/src/main/res/drawable/radius_button_bg_pressed.xml create mode 100644 app/src/main/res/drawable/s_radius_button_bg.xml create mode 100644 app/src/main/res/drawable/tab_panel_bg.xml create mode 100644 app/src/main/res/drawable/welcome.xml create mode 100644 app/src/main/res/layout/activity_bottom_navigation.xml create mode 100644 app/src/main/res/layout/activity_bottom_navigation2.xml create mode 100644 app/src/main/res/layout/activity_login.xml create mode 100644 app/src/main/res/layout/activity_test.xml create mode 100644 app/src/main/res/layout/activity_welcome.xml create mode 100644 app/src/main/res/layout/fragment_home1.xml create mode 100644 app/src/main/res/layout/fragment_home2.xml create mode 100644 app/src/main/res/layout/fragment_home3.xml create mode 100644 app/src/main/res/layout/fragment_my.xml create mode 100644 app/src/main/res/layout/toast_layout.xml create mode 100644 app/src/main/res/menu/navigation.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_check_white_48dp.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_clear_white_48dp.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_error_outline_white_48dp.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_info_outline_white_48dp.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-hdpi/ico.png create mode 100644 app/src/main/res/mipmap-hdpi/toast_frame.9.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_tabbar_component.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_tabbar_component_selected.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_tabbar_lab.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_tabbar_lab_selected.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_tabbar_util.png create mode 100644 app/src/main/res/mipmap-xxhdpi/icon_tabbar_util_selected.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/attr.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/ids.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/xml/app_provider_paths.xml create mode 100644 app/src/main/res/xml/network_security_config.xml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..2b75303a --- /dev/null +++ b/.gitignore @@ -0,0 +1,13 @@ +*.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 diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 00000000..26d33521 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..fb7f4a8a --- /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 00000000..a2d7c213 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 00000000..eb2873e7 --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..31622f1e --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..e262835a --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ + +# Framework_Android +Android架构搭建,根据腾讯QMUI_Android框架搭建,适用于快速运用到项目中去。 +由于本项目与自己的后台服务器搭配使用,在使用时请改成自己的服务器IP地址。 + +感谢QMUI_Android团队为广大开发者提供的 UI 库 ,官网:[http://qmuiteam.com/android](http://qmuiteam.com/android) + + +### (随缘更新) + +### 开发日志 —— 2 + - 添加并重写OkGo中的JsonCallback,添加JsonConvert类,对后台baseJson进行统一管理。 + 每个项目请求后台返回的json格式都有所不同,**请根据实际项目需求修改JsonConvert类**。 本项目同时提供了两种json格式进行解析操作: + 1. json格式为`{code:0,msg:"成功",result:{…}}`。 + 根据后台返回的`code`数值来判断返回的数据是否成功。`code:0`表示成功返回,`code:101` 等其他标识表示返回失败,并返回失败的`msg`。 + 2. json格式为`{success:true,msg:"成功",result:{…}}`。 + 根据后台返回的`success`数值来判断返回的数据是否成功。`success:true`表示成功返回,`success:false` 表示返回失败,并返回失败的`msg`。 + +- 实现启动和登录功能,在app启动时通过后台服务器判断token是否失效,如果没有token或者失效,则跳转到登录页面,反之直接跳转到主界面。 +- 添加测试用户信息开关,并且在gradle配置只能在debug版本使用,当编译成release正式版时,关闭测试用户信息开关。 +- 添加SPConstants,统一管理SharedPreferences的key。 + +### 开发日志 —— 1 + +- 根据QMUI搭建viewPager+fragment+Navigation主页,添加Android7.0 provider +- 添加登录界面,界面根据软键盘弹出自适应上下移动,添加文件相关工具类 +- 添加OkGo并配置框架,添加Logger框架,添加内存泄漏检测框架,base类添加EasyPermission权限管理框架。 + +### 项目截图(持续更新) +![登录界面](https://raw.githubusercontent.com/FadedYu/Framework_Android/master/img_folder/im_login.png) +![主页](https://raw.githubusercontent.com/FadedYu/Framework_Android/master/img_folder/im_pager1.png) diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..becfa464 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,90 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion rootProject.ext.compileSdkVersion + buildToolsVersion rootProject.ext.buildToolsVersion + + defaultConfig { + applicationId "com.bonait.bnframework" + minSdkVersion rootProject.ext.minSdkVersion + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode rootProject.ext.versionCode + versionName rootProject.ext.versionName + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + + resValue("bool","superAdminTest","false") + } + + debug { + resValue("string", "PORT_NUMBER", "8081") + resValue("bool","superAdminTest","true") + } + } +} + +dependencies { + implementation fileTree(include: ['*.jar'], dir: 'libs') + + //测试相关 + testImplementation 'junit:junit:4.13-beta-2' + androidTestImplementation 'com.android.support.test:runner:1.0.2' + androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + implementation 'com.android.support.constraint:constraint-layout:1.1.3' + + //支持包 + implementation 'com.android.support:design:28.0.0' + implementation 'com.android.support:appcompat-v7:28.0.0' + implementation "com.android.support:recyclerview-v7:$rootProject.supportVersion" + implementation "com.android.support:design:$rootProject.supportVersion" + implementation "com.android.support:cardview-v7:$rootProject.supportVersion" + implementation "com.android.support:support-vector-drawable:$rootProject.supportVersion" + + // QMUI框架 link: http://qmuiteam.com/android + // 本App 搭建的基础框架,基本使用控件功能请看官网的功能列表 + implementation 'com.qmuiteam:qmui:1.2.0' + implementation 'com.qmuiteam:arch:0.3.1' + + // RecyclerAdapter框架 + implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.46' + + // OKGO网络协议封装框架 + implementation 'com.lzy.net:okgo:3.0.4' + implementation 'com.google.code.gson:gson:2.8.0' + + // easyPermissions权限管理 + implementation 'pub.devrel:easypermissions:2.0.1' + + // butterKnife黄油刀 + implementation "com.jakewharton:butterknife:$rootProject.butterknife" + annotationProcessor "com.jakewharton:butterknife-compiler:$rootProject.butterknife" + + // SuperTextView + implementation 'com.github.lygttpod:SuperTextView:2.1.8' + + // android-saripaar 基于规则的Android表单验证库 + implementation 'com.mobsandgeeks:android-saripaar:2.0.3' + + // litePal 数据库操作框架 + implementation 'org.litepal.android:java:3.0.0' + + // SmartShow Toast框架,解决不同机型弹出方式缺陷问题 + implementation 'com.github.the-pig-of-jungle.smart-show:toast:2.6.7' + + // debug调试app本地数据库 + debugImplementation 'com.amitshekhar.android:debug-db:1.0.6' + + // log日志框架 + implementation 'com.orhanobut:logger:2.2.0' + + // leak 内存泄漏检测 + debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.6.3' + releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.6.3' + // Optional, if you use support library fragments: + debugImplementation 'com.squareup.leakcanary:leakcanary-support-fragment:1.6.3' + implementation files('libs/commons-codec-1.6.jar') +} diff --git a/app/libs/commons-codec-1.6.jar b/app/libs/commons-codec-1.6.jar new file mode 100644 index 0000000000000000000000000000000000000000..ee1bc49acae11cc79eceec51f7be785590e99fd8 GIT binary patch literal 232771 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%0D!i*HX8O4MB=oJfx_b0#x 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⪚|U3VY}d-^qs5aFtVx+RK?q( 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

(TLX+)v?_>QQPREAAiWGY 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 Xlz{<4C9qfv_|u{aqy=0+^o00-JA(}_ literal 0 HcmV?d00001 diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..f1b42451 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..95bb45a2 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/assets/litepal.xml b/app/src/main/assets/litepal.xml new file mode 100644 index 00000000..e416801b --- /dev/null +++ b/app/src/main/assets/litepal.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/bonait/bnframework/MainApplication.java b/app/src/main/java/com/bonait/bnframework/MainApplication.java new file mode 100644 index 00000000..653049cb --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/MainApplication.java @@ -0,0 +1,151 @@ +package com.bonait.bnframework; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.app.Application; +import android.content.Context; + +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.notification.MainNotification; +import com.bonait.bnframework.common.utils.AppUtils; +import com.bonait.bnframework.common.utils.PreferenceUtils; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.cache.CacheEntity; +import com.lzy.okgo.cache.CacheMode; +import com.lzy.okgo.interceptor.HttpLoggingInterceptor; +import com.orhanobut.logger.AndroidLogAdapter; +import com.orhanobut.logger.FormatStrategy; +import com.orhanobut.logger.Logger; +import com.orhanobut.logger.PrettyFormatStrategy; +import com.qmuiteam.qmui.arch.QMUISwipeBackActivityManager; +import com.squareup.leakcanary.LeakCanary; + +import org.litepal.LitePal; + +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +import okhttp3.OkHttpClient; + +/** + * Created by LY on 2019/3/19. + */ +public class MainApplication extends Application { + + @SuppressLint("StaticFieldLeak") + private static Context context; + + public static Context getContext() { + return context; + } + + @Override + public void onCreate() { + super.onCreate(); + context = getApplicationContext(); + + // activity生命周期管理 + ActivityLifecycleManager.get().init(this); + + // QMUI 框架初始化 + QMUISwipeBackActivityManager.init(this); + + // LitePal 数据库初始化 + LitePal.initialize(this); + + // 全局配置OkGo + initOkGo(); + + // 配置sharedPreferences + PreferenceUtils.initPreference(this, AppUtils.getAppName(this), Activity.MODE_PRIVATE); + + // 初始化通知栏消息渠道 + MainNotification.initNotificationChannel(this); + + // 内存泄漏检测 + initLeakCanary(false); + + // Log日志打印框架 + initLogCat(); + + // SmartShow Toast框架,暂时不用,先使用ToastUtils工具类的 + //SmartShow.init(this); + } + + //========================================================================// + + /** + * 内存泄漏检测,根据flag来判断要不要初始化 + */ + private void initLeakCanary(boolean flag) { + if (flag) { + // leak 内存检测注册 + if (LeakCanary.isInAnalyzerProcess(this)) { + return; + } + LeakCanary.install(this); + } + } + + /** + * 初始化log日志框架 + */ + private void initLogCat() { + FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder() + .showThreadInfo(false) // (可选)是否显示线程信息。 默认值为true + .methodCount(2) // (可选)要显示的方法行数。 默认2 + .build(); + Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy) { + @Override + public boolean isLoggable(int priority, String tag) { + return BuildConfig.DEBUG; + } + }); + } + + /** + * 配置OkGo + */ + private void initOkGo() { + + //---------这里给出的是示例代码,告诉你可以这么传,实际使用的时候,根据需要传,不需要就不传-------------// + /*HttpHeaders headers = new HttpHeaders(); + headers.put("commonHeaderKey1", "commonHeaderValue1"); //header不支持中文,不允许有特殊字符 + headers.put("commonHeaderKey2", "commonHeaderValue2"); + HttpParams params = new HttpParams(); + params.put("commonParamsKey1", "commonParamsValue1"); //param支持中文,直接传,不要自己编码 + params.put("commonParamsKey2", "这里支持中文参数");*/ + //----------------------------------------------------------------------------------------// + + OkHttpClient.Builder builder = new OkHttpClient.Builder(); + + //log相关 + HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor("OkGo"); + //log打印级别,决定了log显示的详细程度 + loggingInterceptor.setPrintLevel(HttpLoggingInterceptor.Level.BODY); + //log颜色级别,决定了log在控制台显示的颜色 + loggingInterceptor.setColorLevel(Level.INFO); + //添加OkGo默认debug日志 + builder.addInterceptor(loggingInterceptor); + + //-------------------------配置超时时间,默认60000ms,60s------------------------------// + //OkGo.DEFAULT_MILLISECONDS + //全局的连接超时时间 + builder.connectTimeout(Constants.CONNECT_TIME_OUT, TimeUnit.MILLISECONDS); + //全局的读取超时时间 + builder.readTimeout(Constants.CONNECT_TIME_OUT, TimeUnit.MILLISECONDS); + //全局的写入超时时间 + builder.writeTimeout(OkGo.DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS); + + OkGo.getInstance().init(this) //必须调用初始化 + .setOkHttpClient(builder.build()) //建议设置OkHttpClient,不设置将使用默认的 + .setCacheMode(CacheMode.NO_CACHE) //全局统一缓存模式,默认不使用缓存,可以不传 + .setCacheTime(CacheEntity.CACHE_NEVER_EXPIRE) //全局统一缓存时间,默认永不过期,可以不传 + .setRetryCount(3); //全局统一超时重连次数,默认为三次,那么最差的情况会请求4次(一次原始请求,三次重连请求),不需要可以设置为0 + //.addCommonParams(params); //全局公共参数 + + } + + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/base/BaseActivity.java b/app/src/main/java/com/bonait/bnframework/common/base/BaseActivity.java new file mode 100644 index 00000000..dd18c11b --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/base/BaseActivity.java @@ -0,0 +1,86 @@ +package com.bonait.bnframework.common.base; + +import android.annotation.SuppressLint; +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bonait.bnframework.MainApplication; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.utils.AlertDialogUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.qmuiteam.qmui.arch.QMUIActivity; +import com.qmuiteam.qmui.util.QMUIDisplayHelper; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +import java.util.List; + +import pub.devrel.easypermissions.EasyPermissions; + + +/** + * Created by LY on 2019/3/21. + */ +@SuppressLint("Registered") +public class BaseActivity extends QMUIActivity implements EasyPermissions.PermissionCallbacks { + + @Override + protected int backViewInitOffset() { + return QMUIDisplayHelper.dp2px(MainApplication.getContext(), 100); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == Constants.APP_SETTING_DIALOG_REQUEST_CODE) { + //在这儿,你可以再对权限进行检查,从而给出提示,或进行下一步操作 + checkPermission(); + ToastUtils.info("从设置中返回"); + } + } + + //----------------以下是请求权限base,子类activity只需要重写checkPermission()方法即可----------------// + + /** + * 检查权限,子类要申请权限,需要重写该方法 + * */ + public void checkPermission() { + } + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + // 将结果转发给EasyPermissions + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + public void onPermissionsGranted(int requestCode, @NonNull List perms) { + } + + @Override + public void onPermissionsDenied(int requestCode, @NonNull List perms) { + + //若是在权限弹窗中,用户勾选了'NEVER ASK AGAIN.'或者'不再提示',且拒绝权限。 + //跳转到设置界面去,让用户手动开启。 + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + + String title = "需要权限,才能正常使用:"; + String message = "如果没有请求的权限,此应用可能无法正常工作。请打开应用设置以修改应用权限。"; + AlertDialogUtils.showDialog(this, title, message, "去设置", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", MainApplication.getContext().getPackageName(), null); + intent.setData(uri); + startActivityForResult(intent, Constants.APP_SETTING_DIALOG_REQUEST_CODE); + dialog.dismiss(); + } + }); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/base/BaseFragment.java b/app/src/main/java/com/bonait/bnframework/common/base/BaseFragment.java new file mode 100644 index 00000000..4ed51f0e --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/base/BaseFragment.java @@ -0,0 +1,87 @@ +package com.bonait.bnframework.common.base; + +import android.content.Intent; +import android.net.Uri; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import com.bonait.bnframework.MainApplication; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.utils.AlertDialogUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.qmuiteam.qmui.arch.QMUIFragment; +import com.qmuiteam.qmui.util.QMUIDisplayHelper; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +import java.util.List; + +import pub.devrel.easypermissions.EasyPermissions; + +/** + * Created by LY on 2019/3/25. + */ +public abstract class BaseFragment extends QMUIFragment implements EasyPermissions.PermissionCallbacks { + + public BaseFragment() { + } + + @Override + protected int backViewInitOffset() { + return QMUIDisplayHelper.dp2px(requireContext(), 100); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == Constants.APP_SETTING_DIALOG_REQUEST_CODE) { + //在这儿,你可以再对权限进行检查,从而给出提示,或进行下一步操作 + checkPermission(); + ToastUtils.info("从设置中返回"); + } + } + + //----------------以下是请求权限base,子类activity只需要重写checkPermission()方法即可----------------// + + /** + * 检查权限,子类要申请权限,需要重写该方法 + * */ + public void checkPermission() { + } + + + @Override + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + // 将结果转发给EasyPermissions + EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this); + } + + @Override + public void onPermissionsGranted(int requestCode, @NonNull List perms) { + } + + @Override + public void onPermissionsDenied(int requestCode, @NonNull List perms) { + + //若是在权限弹窗中,用户勾选了'NEVER ASK AGAIN.'或者'不再提示',且拒绝权限。 + //跳转到设置界面去,让用户手动开启。 + if (EasyPermissions.somePermissionPermanentlyDenied(this, perms)) { + + String title = "需要权限,才能正常使用:"; + String message = "如果没有请求的权限,此应用可能无法正常工作。请打开应用设置以修改应用权限。"; + AlertDialogUtils.showDialog(getContext(), title, message, "去设置", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + Uri uri = Uri.fromParts("package", MainApplication.getContext().getPackageName(), null); + intent.setData(uri); + startActivityForResult(intent, Constants.APP_SETTING_DIALOG_REQUEST_CODE); + dialog.dismiss(); + } + }); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/base/BaseFragmentActivity.java b/app/src/main/java/com/bonait/bnframework/common/base/BaseFragmentActivity.java new file mode 100644 index 00000000..09387161 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/base/BaseFragmentActivity.java @@ -0,0 +1,9 @@ +package com.bonait.bnframework.common.base; + +import com.qmuiteam.qmui.arch.QMUIFragmentActivity; + +/** + * Created by LY on 2019/3/27. + */ +public abstract class BaseFragmentActivity extends QMUIFragmentActivity { +} diff --git a/app/src/main/java/com/bonait/bnframework/common/constant/Constants.java b/app/src/main/java/com/bonait/bnframework/common/constant/Constants.java new file mode 100644 index 00000000..93c84309 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/constant/Constants.java @@ -0,0 +1,31 @@ +package com.bonait.bnframework.common.constant; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.MainApplication; + +/** + * Created by LY on 2019/3/21. + */ +public interface Constants { + + String SERVICE_IP = "http://192.168.22.98:23104/GKIA"; + String APP_TOKEN = "appToken"; + + // admin测试数据使用 + boolean SKIP_TO_TEST_ACTIVITY = false; // 是否启用TestActivity + boolean superAdminTest = MainApplication.getContext().getResources().getBoolean(R.bool.superAdminTest); + + // OkGo 连接超时时间,毫秒ms + long CONNECT_TIME_OUT = 6000; + + // 申请权限跳转到设置界面code + int APP_SETTING_DIALOG_REQUEST_CODE = 1; + + // 申请权限code + int ALL_PERMISSION = 100; + + // 更新apk + int UPDATE_APP = 102; + int INSTALL_PERMISSION_CODE = 103; + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/constant/SPConstants.java b/app/src/main/java/com/bonait/bnframework/common/constant/SPConstants.java new file mode 100644 index 00000000..f83bdc34 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/constant/SPConstants.java @@ -0,0 +1,56 @@ +package com.bonait.bnframework.common.constant; + +/** + * Created by LY on 2019/4/4. + * 保存到SharedPreferences的常量名字 + * 将SharedPreferences的key统一管理 + */ +public interface SPConstants { + + /* + * 将SharedPreferences的key统一管理 + * + * */ + + /** + * App token + * */ + String TOKEN = "token"; + + /** + * 是否修改密码,Boolean + * */ + String CHANGE_PWD = "changePwd"; + + /** + * 用户账号 + * */ + String USER_NAME = "username"; + + /** + * 用户密码 + * */ + String PASSWORD = "password"; + + /** + * 用户名字 + * */ + String USER = "user"; + + /** + * 用户ID + * */ + String USER_ID = "userId"; + + /** + * 角色 + * */ + String ROLE_NAMES = "roleNames"; + + /** + * 部门 + * */ + String FIRST_DEP_ID = "firstDepId"; + + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/GsonConvert.java b/app/src/main/java/com/bonait/bnframework/common/http/GsonConvert.java new file mode 100644 index 00000000..9665ba8c --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/GsonConvert.java @@ -0,0 +1,52 @@ +package com.bonait.bnframework.common.http; + +import com.google.gson.Gson; +import com.google.gson.JsonIOException; +import com.google.gson.JsonSyntaxException; +import com.google.gson.stream.JsonReader; + +import java.io.Reader; +import java.lang.reflect.Type; + +/** + * Created by LY on 2019/4/1. + * Gson 转换 + */ +public class GsonConvert { + + private static Gson create() { + return GsonConvert.GsonHolder.gson; + } + + private static class GsonHolder { + private static Gson gson = new Gson(); + } + + public static T fromJson(String json, Class type) throws JsonIOException, JsonSyntaxException { + return create().fromJson(json, type); + } + + public static T fromJson(String json, Type type) { + return create().fromJson(json, type); + } + + public static T fromJson(JsonReader reader, Type typeOfT) throws JsonIOException, JsonSyntaxException { + return create().fromJson(reader, typeOfT); + } + + public static T fromJson(Reader json, Class classOfT) throws JsonSyntaxException, JsonIOException { + return create().fromJson(json, classOfT); + } + + public static T fromJson(Reader json, Type typeOfT) throws JsonIOException, JsonSyntaxException { + return create().fromJson(json, typeOfT); + } + + public static String toJson(Object src) { + return create().toJson(src); + } + + public static String toJson(Object src, Type typeOfSrc) { + return create().toJson(src, typeOfSrc); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/bitmap/BitmapDialogCallback.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/bitmap/BitmapDialogCallback.java new file mode 100644 index 00000000..deddfe6c --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/bitmap/BitmapDialogCallback.java @@ -0,0 +1,44 @@ +package com.bonait.bnframework.common.http.callback.bitmap; + +import android.content.Context; +import android.graphics.Bitmap; + +import com.lzy.okgo.callback.BitmapCallback; +import com.lzy.okgo.request.base.Request; +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog; + +/** + * Created by LY on 2019/4/2. + * + * 如果请求的数据是图片,则可以使用该回调,回调的图片进行了压缩处理,确保不发生OOM + * + * 有加载框的网络图片请求回调 + * + */ +public abstract class BitmapDialogCallback extends BitmapCallback { + + private QMUITipDialog tipDialog; + + + public BitmapDialogCallback(Context context) { + super(1000,1000); + tipDialog = new QMUITipDialog.Builder(context) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord("正在加载") + .create(); + } + + @Override + public void onStart(Request request) { + if (tipDialog != null && !tipDialog.isShowing()) { + tipDialog.show(); + } + } + + @Override + public void onFinish() { + if (tipDialog != null && tipDialog.isShowing()) { + tipDialog.dismiss(); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/files/FileProgressDialogCallBack.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/files/FileProgressDialogCallBack.java new file mode 100644 index 00000000..51fa957a --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/files/FileProgressDialogCallBack.java @@ -0,0 +1,145 @@ +package com.bonait.bnframework.common.http.callback.files; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.text.format.Formatter; +import android.util.Log; + +import com.bonait.bnframework.MainApplication; +import com.bonait.bnframework.common.notification.DownloadNotification; +import com.bonait.bnframework.common.utils.AlertDialogUtils; +import com.bonait.bnframework.common.utils.NetworkUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.callback.FileCallback; +import com.lzy.okgo.model.Progress; +import com.lzy.okgo.model.Response; +import com.lzy.okgo.request.base.Request; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +import java.io.File; +import java.util.Locale; + +/** + * Created by LY on 2019/4/15. + * 带加载进度条的下载回调接口,默认OkGo文件回调 + */ +public abstract class FileProgressDialogCallBack extends FileCallback { + + private ProgressDialog progressDialog; + private DownloadNotification downloadNotification; + + public FileProgressDialogCallBack(Context context) { + super(null); + initDialog(context); + } + + public FileProgressDialogCallBack(Context context, String destFileName) { + super(destFileName); + initDialog(context); + } + + public FileProgressDialogCallBack(Context context, String destFileDir, String destFileName) { + super(destFileDir, destFileName); + initDialog(context); + } + + + @Override + public void onStart(Request request) { + if (progressDialog != null && !progressDialog.isShowing()) { + progressDialog.show(); + } + } + + @Override + public void onFinish() { + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } + + if (downloadNotification != null) { + downloadNotification.downloadComplete(); + } + } + + @Override + public void onError(Response response) { + super.onError(response); + if (downloadNotification != null) { + downloadNotification.downloadError(); + } + + ToastUtils.error(response.body().getName() + "下载失败,请重新下载!"); + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } + } + + @Override + public void downloadProgress(Progress progress) { + String downloadLength = Formatter.formatFileSize(MainApplication.getContext(), progress.currentSize); + String totalLength = Formatter.formatFileSize(MainApplication.getContext(), progress.totalSize); + + if (progress.totalSize > 0) { + progressDialog.setMax((int) progress.totalSize); + } + progressDialog.setProgress((int) (progress.fraction * progress.totalSize)); + progressDialog.setProgressNumberFormat(String.format(Locale.CHINA, "%s / %s", downloadLength, totalLength)); + Log.d("downloadProgress", progress.fileName + " 下载中……" + String.format(Locale.CHINA, "%s / %s", downloadLength, totalLength)); + + if (downloadNotification != null) { + downloadNotification.showProgress(progress); + } + } + + /** + * 判断网络状态。移动/WiFi,弹出警告框 + */ + private void initDialog(final Context context) { + if (NetworkUtils.isActiveNetworkMobile(context)) { + String title = "温馨提示!"; + String message = "当前网络为移动网络,可能会消耗大量移动流量数据,确定要下载吗?"; + AlertDialogUtils.showDialog(context, title, message, new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + downloadingDialog(context); + dialog.dismiss(); + } + }); + } else { + downloadingDialog(context); + } + } + + /** + * 显示进度条对话框 + * */ + private void downloadingDialog(final Context context) { + progressDialog = new ProgressDialog(context); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setCancelable(false); + progressDialog.setCanceledOnTouchOutside(false); + progressDialog.setTitle("正在下载"); + + progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "停止下载", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + OkGo.getInstance().cancelTag(context); + ToastUtils.info("已停止下载!"); + dialog.dismiss(); + } + }); + + progressDialog.setButton(DialogInterface.BUTTON_POSITIVE, "后台下载", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + downloadNotification = new DownloadNotification(context); + dialog.dismiss(); + } + }); + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileCallback2.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileCallback2.java new file mode 100644 index 00000000..a0e30d3f --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileCallback2.java @@ -0,0 +1,40 @@ +package com.bonait.bnframework.common.http.callback.files2; + +import com.lzy.okgo.callback.AbsCallback; + +import java.io.File; + +import okhttp3.Response; + +/** + * Created by LY on 2019/4/10. + *

与OkGo的FileCallback改动不大,如果不想使用该类,可直接使用OkGo默认的FileCallback

+ *

将文件转换类改成FileConvert2

+ * + */ +public abstract class FileCallback2 extends AbsCallback { + + //文件转换类FileConvert2 + private FileConvert2 convert; + + public FileCallback2(long totalSize) { + this(null,totalSize); + } + + public FileCallback2(String destFileName,long totalSize) { + this(null, destFileName,totalSize); + } + + public FileCallback2(String destFileDir, String destFileName,long totalSize) { + // FileConvert2 + convert = new FileConvert2(destFileDir, destFileName,totalSize); + convert.setCallback(this); + } + + @Override + public File convertResponse(Response response) throws Throwable { + File file = convert.convertResponse(response); + response.close(); + return file; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileConvert2.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileConvert2.java new file mode 100644 index 00000000..4b4210c8 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileConvert2.java @@ -0,0 +1,117 @@ +package com.bonait.bnframework.common.http.callback.files2; + +import android.os.Environment; +import android.text.TextUtils; + +import com.lzy.okgo.callback.Callback; +import com.lzy.okgo.convert.Converter; +import com.lzy.okgo.model.Progress; +import com.lzy.okgo.utils.HttpUtils; +import com.lzy.okgo.utils.IOUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +import okhttp3.Response; +import okhttp3.ResponseBody; + +/** + * Created by LY on 2019/4/10. + * 与OkGo的FileConvert改动不大,如果不想使用该类,可直接使用OkGo默认的FileConvert + *

与OkGo的FileConvert改动不大

+ *

只是将代码的 progress.totalSize = body.contentLength();

+ *

替换成progress.totalSize = totalSize; 其中totalSize为形参,需要自己传参

+ * + */ +public class FileConvert2 implements Converter { + + public static final String DM_TARGET_FOLDER = File.separator + "download" + File.separator; //下载目标文件夹 + + private String folder; //目标文件存储的文件夹路径 + private String fileName; //目标文件存储的文件名 + private Callback callback; //下载回调 + + /** + * 文件总大小 + * */ + private long totalSize; + + public FileConvert2(long totalSize) { + this(null,totalSize); + this.totalSize = totalSize; + } + + public FileConvert2(String fileName, long totalSize) { + this(Environment.getExternalStorageDirectory() + DM_TARGET_FOLDER, fileName,totalSize); + this.totalSize = totalSize; + } + + public FileConvert2(String folder, String fileName,long totalSize) { + this.folder = folder; + this.fileName = fileName; + this.totalSize = totalSize; + } + + public void setCallback(Callback callback) { + this.callback = callback; + } + + @Override + public File convertResponse(Response response) throws Throwable { + String url = response.request().url().toString(); + if (TextUtils.isEmpty(folder)) folder = Environment.getExternalStorageDirectory() + DM_TARGET_FOLDER; + if (TextUtils.isEmpty(fileName)) fileName = HttpUtils.getNetFileName(response, url); + + File dir = new File(folder); + IOUtils.createFolder(dir); + File file = new File(dir, fileName); + IOUtils.delFileOrFolder(file); + + InputStream bodyStream = null; + byte[] buffer = new byte[8192]; + FileOutputStream fileOutputStream = null; + try { + ResponseBody body = response.body(); + if (body == null) return null; + + bodyStream = body.byteStream(); + Progress progress = new Progress(); + progress.totalSize = totalSize; + progress.fileName = fileName; + progress.filePath = file.getAbsolutePath(); + progress.status = Progress.LOADING; + progress.url = url; + progress.tag = url; + + int len; + fileOutputStream = new FileOutputStream(file); + while ((len = bodyStream.read(buffer)) != -1) { + fileOutputStream.write(buffer, 0, len); + + if (callback == null) continue; + Progress.changeProgress(progress, len, new Progress.Action() { + @Override + public void call(Progress progress) { + onProgress(progress); + } + }); + } + fileOutputStream.flush(); + return file; + } finally { + IOUtils.closeQuietly(bodyStream); + IOUtils.closeQuietly(fileOutputStream); + } + } + + private void onProgress(final Progress progress) { + HttpUtils.runOnUiThread(new Runnable() { + @Override + public void run() { + callback.downloadProgress(progress); //进度回调的方法 + } + }); + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileProgressDialogCallBack2.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileProgressDialogCallBack2.java new file mode 100644 index 00000000..1418c3e8 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/FileProgressDialogCallBack2.java @@ -0,0 +1,145 @@ +package com.bonait.bnframework.common.http.callback.files2; + +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.text.format.Formatter; +import android.util.Log; + +import com.bonait.bnframework.MainApplication; +import com.bonait.bnframework.common.notification.DownloadNotification; +import com.bonait.bnframework.common.utils.AlertDialogUtils; +import com.bonait.bnframework.common.utils.NetworkUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.model.Progress; +import com.lzy.okgo.model.Response; +import com.lzy.okgo.request.base.Request; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +import java.io.File; +import java.util.Locale; + +/** + * Created by LY on 2019/4/10. + * 带加载进度条的下载回调接口,自定义的文件回调 + */ +public abstract class FileProgressDialogCallBack2 extends FileCallback2 { + + private ProgressDialog progressDialog; + private DownloadNotification downloadNotification; + + + public FileProgressDialogCallBack2(Context context, long totalSize) { + super(totalSize); + initDialog(context, totalSize); + } + + public FileProgressDialogCallBack2(Context context, String destFileName, long totalSize) { + super(destFileName, totalSize); + initDialog(context, totalSize); + } + + public FileProgressDialogCallBack2(Context context, String destFileDir, String destFileName, long totalSize) { + super(destFileDir, destFileName, totalSize); + initDialog(context, totalSize); + } + + @Override + public void onStart(Request request) { + if (progressDialog != null && !progressDialog.isShowing()) { + progressDialog.show(); + } + } + + @Override + public void onFinish() { + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } + + if (downloadNotification != null) { + downloadNotification.downloadComplete(); + } + } + + @Override + public void onError(Response response) { + super.onError(response); + if (downloadNotification != null) { + downloadNotification.downloadError(); + } + + ToastUtils.error(response.body().getName() + "下载失败,请重新下载!"); + if (progressDialog != null && progressDialog.isShowing()) { + progressDialog.dismiss(); + } + } + + @Override + public void downloadProgress(Progress progress) { + String downloadLength = Formatter.formatFileSize(MainApplication.getContext(), progress.currentSize); + String totalLength = Formatter.formatFileSize(MainApplication.getContext(), progress.totalSize); + + progressDialog.setProgress((int) (progress.fraction * progress.totalSize)); + progressDialog.setProgressNumberFormat(String.format(Locale.CHINA, "%sMb / %sMb", downloadLength, totalLength)); + Log.d("downloadProgress", progress.fileName + " 下载中……" + String.format(Locale.CHINA, "%s / %s", downloadLength, totalLength)); + + if (downloadNotification != null) { + downloadNotification.showProgress(progress); + } + } + + /** + * 判断网络状态。移动/WiFi,弹出警告框 + */ + private void initDialog(final Context context, final long totalSize) { + if (NetworkUtils.isActiveNetworkMobile(context)) { + String title = "温馨提示!"; + String message = "当前网络为移动网络,可能会消耗大量移动流量数据,确定要下载吗?"; + AlertDialogUtils.showDialog(context, title, message, new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + downloadingDialog(context, totalSize); + dialog.dismiss(); + } + }); + } else { + downloadingDialog(context, totalSize); + } + } + + /** + * 显示进度条对话框 + * */ + private void downloadingDialog(final Context context, long totalSize) { + progressDialog = new ProgressDialog(context); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setCancelable(false); + progressDialog.setCanceledOnTouchOutside(false); + progressDialog.setTitle("正在下载"); + + if (totalSize > 0) { + progressDialog.setMax((int) totalSize); + } + + progressDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "停止下载", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + OkGo.getInstance().cancelTag(context); + ToastUtils.info("已停止下载!"); + dialog.dismiss(); + } + }); + + progressDialog.setButton(DialogInterface.BUTTON_POSITIVE, "后台下载", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + downloadNotification = new DownloadNotification(context); + dialog.dismiss(); + } + }); + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/ReadMe.txt b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/ReadMe.txt new file mode 100644 index 00000000..addf0821 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/files2/ReadMe.txt @@ -0,0 +1,21 @@ +由于一些服务器http返回头的原因:在文件下载中, +响应头为:Transfer-Encoding: chunked; +请求头为:Accept-Encoding: gzip, deflate; +且没有返回Content-Length这个响应头,或者Content-Length = -1。 +这样的http请求是以压缩gzip形式动态下载,文件下载总大小是无法预知的。所以使用一般的默认的fileCallback回调接口,进度条是呈现不出来进度的。 + +小提示:Transfer-Encoding: chunked;与 Content-Length 这两个响应头二者只能存在一个,即使两者都有,Content-Length也会默认失效。 + +//---------------------------------------------------------------------------------------------// + +由于OkGo里的OkDownload主要功能必须要添加Content-Length这个响应头,才能获取文件大小。 +(OkGo原文说明:服务端一定要返回Content-Length,注意,是一定要返回Content-Length这个响应头,如果没有,该值默认是-1, +这个值表示当前要下载的文件有多大,如果服务端不给的话,客户端在下载过程中是不可能知道我要下载的文件有多大的,所以常见的问题就是进度是负数。) + +为了应对这种情况,在这个包中重写了fileCallback和fileConvert这两个类,开放文件总大小totalSize这个参数。 +因此,需要自己传参文件总大小。(文件大小必须为实际大小byte,不然进度条会出现奇怪的问题) + +//---------------------------------------------------------------------------------------------// + +如果后台服务器有Content-Length,并且不是gzip压缩,则可以使用file包下的FileProgressDialogCallBack或者FileCallback回调。 +如果后台服务器没有Content-Length或者Content-Length = -1,且是gzip压缩,则可以使用file2包下的FileProgressDialogCallBack2或者FileCallback2回调,并传入totalSize参数。 \ No newline at end of file diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonCallback.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonCallback.java new file mode 100644 index 00000000..69631f2b --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonCallback.java @@ -0,0 +1,117 @@ +package com.bonait.bnframework.common.http.callback.json; + +import android.app.Activity; +import android.content.Intent; + +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.common.constant.SPConstants; +import com.bonait.bnframework.common.http.exception.TokenException; +import com.bonait.bnframework.common.utils.PreferenceUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.bonait.bnframework.modules.welcome.activity.WelcomeActivity; +import com.lzy.okgo.callback.AbsCallback; +import com.lzy.okgo.exception.HttpException; +import com.lzy.okgo.exception.StorageException; +import com.lzy.okgo.model.Response; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.net.ConnectException; +import java.net.SocketTimeoutException; +import java.net.UnknownHostException; + +/** + * Created by LY on 2019/4/2. + */ +public abstract class JsonCallback extends AbsCallback { + + private Type type; + private Class clazz; + + public JsonCallback() { + } + + public JsonCallback(Type type) { + this.type = type; + } + + public JsonCallback(Class clazz) { + this.clazz = clazz; + } + + /** + * 该方法是子线程处理,不能做ui相关的工作 + * 主要作用是解析网络返回的 response 对象,生产onSuccess回调中需要的数据对象 + * 这里的解析工作不同的业务逻辑基本都不一样,所以需要自己实现,实际使用根据需要修改 + */ + @Override + public T convertResponse(okhttp3.Response response) throws Throwable { + + // 不同的业务,这里的代码逻辑都不一样,如果你不修改,那么基本不可用 + + //详细自定义的原理和文档,看这里: https://github.com/jeasonlzy/okhttp-OkGo/wiki/JsonCallback + + if (type == null) { + if (clazz == null) { + Type genType = getClass().getGenericSuperclass(); + type = ((ParameterizedType) genType).getActualTypeArguments()[0]; + } else { + JsonConvert convert = new JsonConvert<>(clazz); + return convert.convertResponse(response); + } + } + + JsonConvert convert = new JsonConvert<>(type); + return convert.convertResponse(response); + } + + + @Override + public void onError(Response response) { + super.onError(response); + + Throwable exception = response.getException(); + //如果OkGo配置中Logger日志已打开,super.onError(response);就已经将printStackTrace打印出来了 + /*if (exception != null) { + exception.printStackTrace(); + }*/ + + if (exception instanceof UnknownHostException || exception instanceof ConnectException) { + ToastUtils.error("网络连接失败,请连接网络!"); + } else if (exception instanceof SocketTimeoutException) { + ToastUtils.error("网络请求超时,请稍后重试!"); + } else if (exception instanceof HttpException) { + ToastUtils.error("暂时无法连接服务器,请稍后重试!"); + } else if (exception instanceof StorageException) { + ToastUtils.error("SD卡不存在或者没有获取SD卡访问权限!"); + } else if (exception instanceof TokenException) { + // 删除token,跳转到登录页面 + skipLoginActivityAndFinish(exception); + } else if (exception instanceof IllegalStateException) { + ToastUtils.error(exception.getMessage()); + } + } + + /** + * 删除token,跳转到登录页面 + * */ + private void skipLoginActivityAndFinish(Throwable exception) { + //删除本地过期的token + PreferenceUtils.remove(SPConstants.TOKEN); + PreferenceUtils.remove(SPConstants.USER_ID); + Intent intent = new Intent("com.bonait.bnframework.modules.welcome.activity.LoginActivity.ACTION_START"); + // 获取当前Activity(栈中最后一个压入的) + Activity activity = ActivityLifecycleManager.get().currentActivity(); + + // 如果不是WelcomeActivity,显示Toast告知token已过期 + if (activity.getClass() != WelcomeActivity.class) { + ToastUtils.info(exception.getMessage()); + } + + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + // 跳转到登录页面 + activity.startActivity(intent); + activity.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonConvert.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonConvert.java new file mode 100644 index 00000000..f9a5e0fe --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonConvert.java @@ -0,0 +1,279 @@ +package com.bonait.bnframework.common.http.callback.json; + +import android.text.TextUtils; + +import com.bonait.bnframework.common.http.GsonConvert; +import com.bonait.bnframework.common.http.exception.TokenException; +import com.bonait.bnframework.common.model.BaseCodeJson; +import com.bonait.bnframework.common.model.BaseJson; +import com.bonait.bnframework.common.model.SimpleBaseJson; +import com.bonait.bnframework.common.model.SimpleCodeJson; +import com.google.gson.stream.JsonReader; +import com.lzy.okgo.convert.Converter; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.nio.charset.Charset; + +import okhttp3.Response; +import okhttp3.ResponseBody; +import okio.Buffer; +import okio.BufferedSource; + +/** + * Created by LY on 2019/4/2. + */ +public class JsonConvert implements Converter { + + private Type type; + private Class clazz; + + public JsonConvert() { + } + + public JsonConvert(Type type) { + this.type = type; + } + + public JsonConvert(Class clazz) { + this.clazz = clazz; + } + + + /** + * 该方法是子线程处理,不能做ui相关的工作 + * 主要作用是解析网络返回的 response 对象,生成onSuccess回调中需要的数据对象 + * 这里的解析工作不同的业务逻辑基本都不一样,所以需要自己实现。 + */ + @Override + public T convertResponse(Response response) throws Throwable { + + // 不同的业务,这里的代码逻辑都不一样,要按需修改 + + // 如果你对这里的代码原理不清楚,可以看这里的详细原理说明: https://github.com/jeasonlzy/okhttp-OkGo/wiki/JsonCallback + // 如果你对这里的代码原理不清楚,可以看这里的详细原理说明: https://github.com/jeasonlzy/okhttp-OkGo/wiki/JsonCallback + // 如果你对这里的代码原理不清楚,可以看这里的详细原理说明: https://github.com/jeasonlzy/okhttp-OkGo/wiki/JsonCallback + + + /* + * 由于项目需要,App端请求网络必需添加一个Token验证,所以登录成功之后会在OkGo全局添加一个Token的param + * 每次请求网络后台都会验证这个token是否过期,如果过期,则返回{"code":102,"msg":"token过期"} + * */ + checkIfTheTokenHasExpired(response); + + if (type == null) { + if (clazz == null) { + // 如果没有通过构造函数传进来,就自动解析父类泛型的真实类型(有局限性,继承后就无法解析到) + Type genType = getClass().getGenericSuperclass(); + type = ((ParameterizedType) genType).getActualTypeArguments()[0]; + } else { + return parseClass(response, clazz); + } + } + + if (type instanceof ParameterizedType) { + return parseParameterizedType(response, (ParameterizedType) type); + } else if (type instanceof Class) { + return parseClass(response, (Class) type); + } else { + return parseType(response, type); + } + } + + /** + * 检查Token是否过期,如果过期抛出TokenException异常并返回登录界面。 + * + * 由于写这个方法时,出现了response.body()只调用一次就会close掉,导致后面方法无法继续获取response而发生异常 + * 下面是一种解决方案,在读取buffer之前,先对buffer进行clone一下。这时候就可以拿到返回的数据,然后就可以继续调用response.body()。 + * */ + private void checkIfTheTokenHasExpired(Response response) throws Throwable { + ResponseBody body = response.body(); + if (body == null) return ; + + BufferedSource source = body.source(); + source.request(Long.MAX_VALUE); + Buffer buffer = source.buffer(); + String responseBodyString = buffer.clone().readString(Charset.forName("UTF-8")); + buffer.close(); + + JSONObject jsonObject = new JSONObject(responseBodyString); + if (jsonObject.optInt("code") == 102) { + throw new TokenException("token已过期"); + } + } + + + /** + * 根据class解析json数据 + * */ + private T parseClass(Response response, Class rawType) throws Exception { + if (rawType == null) return null; + ResponseBody body = response.body(); + if (body == null) return null; + JsonReader jsonReader = new JsonReader(body.charStream()); + + if (rawType == String.class) { + //noinspection unchecked + return (T) body.string(); + } else if (rawType == JSONObject.class) { + //noinspection unchecked + return (T) new JSONObject(body.string()); + } else if (rawType == JSONArray.class) { + //noinspection unchecked + return (T) new JSONArray(body.string()); + } else { + // 泛型格式如下: new JsonCallback<任意JavaBean>(this) + T t = GsonConvert.fromJson(jsonReader, rawType); + response.close(); + return t; + } + } + + /** + * 根据Type,解析json数据 + * */ + private T parseType(Response response, Type type) throws Exception { + if (type == null) return null; + ResponseBody body = response.body(); + if (body == null) return null; + JsonReader jsonReader = new JsonReader(body.charStream()); + + // 泛型格式如下: new JsonCallback<任意JavaBean>(this) + T t = GsonConvert.fromJson(jsonReader, type); + response.close(); + return t; + } + + private T parseParameterizedType(Response response, ParameterizedType type) throws Exception { + if (type == null) return null; + ResponseBody body = response.body(); + if (body == null) return null; + JsonReader jsonReader = new JsonReader(body.charStream()); + + // 泛型的实际类型 + Type rawType = type.getRawType(); + // 泛型的参数 + Type typeArgument = type.getActualTypeArguments()[0]; + + if (rawType == BaseJson.class) { + // success json格式 + return convertBaseJson(response,type,jsonReader,typeArgument); + } else if (rawType == BaseCodeJson.class) { + // code json格式 + return convertBaseCodeJson(response,type,jsonReader,typeArgument); + } else { + // 其他json格式 + // 泛型格式如下: new JsonCallback<外层BaseBean<内层JavaBean>>(this) + T t = GsonConvert.fromJson(jsonReader, type); + response.close(); + return t; + } + } + + /** + * 解析BaseLoginJson与其属性为泛型的参数 + * 根据BaseLoginJson解析出来的数据,实现不同的业务逻辑 + * 要点:code : 0/101/104 根据code实现不同的业务逻辑 + * + * */ + private T convertBaseCodeJson(Response response,ParameterizedType type,JsonReader jsonReader,Type typeArgument) { + + //code返回的数据,0:表示成功,其他失败的有:101,102,900 + int code; + // msg信息 + String msg; + + if (typeArgument == Void.class) { + // 泛型格式如下: new JsonCallback>(this) + SimpleCodeJson simpleCodeJson = GsonConvert.fromJson(jsonReader,SimpleCodeJson.class); + response.close(); + + code = simpleCodeJson.getCode(); + msg = simpleCodeJson.getMsg(); + + // code为0: 表示成功 + if (code == 0) { + //noinspection unchecked + return (T) simpleCodeJson.toBaseCodeJson(); + } + } else { + // 泛型格式如下: new JsonCallback>(this) + BaseCodeJson baseCodeJson = GsonConvert.fromJson(jsonReader,type); + response.close(); + + code = baseCodeJson.getCode(); + msg = baseCodeJson.getMsg(); + + // code为0: 表示成功 + if (code == 0) { + //noinspection unchecked + return (T) baseCodeJson; + } + } + + if (TextUtils.isEmpty(msg)) { + msg = "未知错误,请稍后重试!"; + } + + // 当code返回不是0时,表示错误数据,根据数据返回不同的错误信息 + if (code == 101) { + throw new IllegalStateException(msg+""); + } else if (code == 102) { + throw new TokenException(msg+""); + } else if (code == 900) { + throw new IllegalStateException(msg+""); + } else { + throw new IllegalStateException(msg+""); + } + } + + /** + * 解析BaseJson与其属性为泛型的参数 + * 根据BaseJson解析出来的数据,实现不同的业务逻辑 + * 要点:success : true/false 根据success实现不同的业务逻辑 + * + * */ + private T convertBaseJson(Response response,ParameterizedType type,JsonReader jsonReader,Type typeArgument) { + + // 一般来说服务器会和客户端约定一个数表示成功,其余的表示失败,这里根据实际情况修改 + // success为true则表示成功,false表示失败 + boolean success; + // msg信息 + String msg; + + if (typeArgument == Void.class) { + // 泛型格式如下: new JsonCallback>(this) + SimpleBaseJson simpleBaseJson = GsonConvert.fromJson(jsonReader, SimpleBaseJson.class); + response.close(); + success = simpleBaseJson.isSuccess(); + msg = simpleBaseJson.getMsg(); + // success为true时,表示成功 + if (success) { + //noinspection unchecked + return (T) simpleBaseJson.toBaseJson(); + } + + } else { + // 泛型格式如下: new JsonCallback>(this) + BaseJson baseJson = GsonConvert.fromJson(jsonReader, type); + response.close(); + success = baseJson.isSuccess(); + msg = baseJson.getMsg(); + // success为true时,表示成功 + if (success) { + //noinspection unchecked + return (T) baseJson; + } + } + + // 当success为false时,会走以下方法,显示不同的错误信息 + if (msg != null) { + throw new IllegalStateException(msg+""); + } else { + throw new IllegalStateException("请求服务器失败,请稍后重试!"); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonDialogCallback.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonDialogCallback.java new file mode 100644 index 00000000..851ce807 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/json/JsonDialogCallback.java @@ -0,0 +1,57 @@ +package com.bonait.bnframework.common.http.callback.json; + +import android.content.Context; + +import com.lzy.okgo.request.base.Request; +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog; + +/** + * Created by LY on 2019/4/1. + * + * 在网络请求前显示一个loading,请求结束后取消loading + * + * 有加载框的网络请求回调 + * + */ +public abstract class JsonDialogCallback extends JsonCallback { + + private QMUITipDialog tipDialog; + + /** + * 弹出加载框入口 + * */ + public JsonDialogCallback(Context context) { + super(); + initDialog(context); + } + + /** + * 初始化加载框 + * */ + private void initDialog(Context context) { + tipDialog = new QMUITipDialog.Builder(context) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord("正在加载") + .create(); + } + + /** + * 请求网络开始前弹出加载框 + * */ + @Override + public void onStart(Request request) { + if (tipDialog != null && !tipDialog.isShowing()) { + tipDialog.show(); + } + } + + /** + * 请求网络结束后关闭加载框 + * */ + @Override + public void onFinish() { + if (tipDialog != null && tipDialog.isShowing()) { + tipDialog.dismiss(); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/callback/strings/StringDialogCallback.java b/app/src/main/java/com/bonait/bnframework/common/http/callback/strings/StringDialogCallback.java new file mode 100644 index 00000000..fb3bf016 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/callback/strings/StringDialogCallback.java @@ -0,0 +1,52 @@ +package com.bonait.bnframework.common.http.callback.strings; + +import android.content.Context; + +import com.lzy.okgo.callback.StringCallback; +import com.lzy.okgo.request.base.Request; +import com.qmuiteam.qmui.widget.dialog.QMUITipDialog; + +/** + * Created by LY on 2019/4/2. + * + * 对convertResponse()按文本解析,解析的编码依据服务端响应头中的Content-Type中的编码格式,自动进行编码转换,确保不出现乱码 + * + * 有加载框的文本解析回调 + * + */ +public abstract class StringDialogCallback extends StringCallback { + + private QMUITipDialog tipDialog; + + /** + * 展示加载框 + * */ + public StringDialogCallback(Context context) { + tipDialog = new QMUITipDialog.Builder(context) + .setIconType(QMUITipDialog.Builder.ICON_TYPE_LOADING) + .setTipWord("正在加载") + .create(); + } + + + /** + * 请求网络开始前弹出加载框 + * */ + @Override + public void onStart(Request request) { + if (tipDialog != null && !tipDialog.isShowing()) { + tipDialog.show(); + } + } + + /** + * 请求网络结束后关闭加载框 + * */ + @Override + public void onFinish() { + if (tipDialog != null && tipDialog.isShowing()) { + tipDialog.dismiss(); + } + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/http/exception/TokenException.java b/app/src/main/java/com/bonait/bnframework/common/http/exception/TokenException.java new file mode 100644 index 00000000..31c42a49 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/http/exception/TokenException.java @@ -0,0 +1,18 @@ +package com.bonait.bnframework.common.http.exception; + +/** + * Created by LY on 2019/4/3. + */ +public class TokenException extends RuntimeException { + + private static final long serialVersionUID = 8394950517718638426L; + + + public TokenException() { + super(); + } + + public TokenException(String message) { + super(message); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/interfaces/OnDoIntListener.java b/app/src/main/java/com/bonait/bnframework/common/interfaces/OnDoIntListener.java new file mode 100644 index 00000000..cb3c0142 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/interfaces/OnDoIntListener.java @@ -0,0 +1,9 @@ +package com.bonait.bnframework.common.interfaces; + +/** + * Created by LY on 2019/3/28. + * 动画工具类接口监听 + */ +public interface OnDoIntListener { + void doSomething(int intValue); +} diff --git a/app/src/main/java/com/bonait/bnframework/common/model/BaseCodeJson.java b/app/src/main/java/com/bonait/bnframework/common/model/BaseCodeJson.java new file mode 100644 index 00000000..f4b4c0c3 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/model/BaseCodeJson.java @@ -0,0 +1,56 @@ +package com.bonait.bnframework.common.model; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by LY on 2019/4/2. + */ +public class BaseCodeJson implements Serializable { + + private static final long serialVersionUID = -2849146113542167339L; + + @SerializedName("code") + private int code; + @SerializedName("msg") + private String msg; + @SerializedName(value = "result" , alternate = {"data"}) + private T result; + + + @SerializedName("userId") + private int userId; + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public int getUserId() { + return userId; + } + + public void setUserId(int userId) { + this.userId = userId; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/model/BaseJson.java b/app/src/main/java/com/bonait/bnframework/common/model/BaseJson.java new file mode 100644 index 00000000..6a31b954 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/model/BaseJson.java @@ -0,0 +1,57 @@ +package com.bonait.bnframework.common.model; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by LY on 2019/4/2. + */ +public class BaseJson implements Serializable { + + private static final long serialVersionUID = 477369592714415773L; + + @SerializedName("success") + private boolean success; + + @SerializedName("totalCounts") + private String totalCounts; + + @SerializedName(value = "result" , alternate = {"data"}) + private T result; + + private String msg; + + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getTotalCounts() { + return totalCounts; + } + + public void setTotalCounts(String totalCounts) { + this.totalCounts = totalCounts; + } + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/model/SimpleBaseJson.java b/app/src/main/java/com/bonait/bnframework/common/model/SimpleBaseJson.java new file mode 100644 index 00000000..af19b5ff --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/model/SimpleBaseJson.java @@ -0,0 +1,39 @@ +package com.bonait.bnframework.common.model; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/** + * Created by LY on 2019/4/2. + */ +public class SimpleBaseJson implements Serializable { + + @SerializedName("success") + private boolean success; + + private String msg; + + public BaseJson toBaseJson() { + BaseJson baseJson = new BaseJson(); + baseJson.setSuccess(success); + baseJson.setMsg(msg); + return baseJson; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/model/SimpleCodeJson.java b/app/src/main/java/com/bonait/bnframework/common/model/SimpleCodeJson.java new file mode 100644 index 00000000..624b96c4 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/model/SimpleCodeJson.java @@ -0,0 +1,38 @@ +package com.bonait.bnframework.common.model; + +import com.google.gson.annotations.SerializedName; + +/** + * Created by LY on 2019/4/3. + */ +public class SimpleCodeJson { + + @SerializedName("code") + private int code; + @SerializedName("msg") + private String msg; + + + public BaseCodeJson toBaseCodeJson() { + BaseCodeJson baseCodeJson = new BaseCodeJson(); + baseCodeJson.setCode(code); + baseCodeJson.setMsg(msg); + return baseCodeJson; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMsg() { + return msg; + } + + public void setMsg(String msg) { + this.msg = msg; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/notification/DownloadNotification.java b/app/src/main/java/com/bonait/bnframework/common/notification/DownloadNotification.java new file mode 100644 index 00000000..06b4c543 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/notification/DownloadNotification.java @@ -0,0 +1,98 @@ +package com.bonait.bnframework.common.notification; + +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.BitmapFactory; +import android.support.v4.app.NotificationCompat; +import android.text.format.Formatter; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.MainApplication; +import com.lzy.okgo.model.Progress; + +import java.util.Date; +import java.util.Locale; + + +/** + * Created by LY on 2019/4/18. + * 下载通知栏notification + */ +public class DownloadNotification { + + private Context context; + private int notificationId; + private String Tag = "downloading"; // 下载通知栏标识 + private String downloadFileName; // 下载文件名称 + + public DownloadNotification(Context context) { + notificationId = (int) new Date().getTime(); + this.context = context; + } + + /** + * 显示notification下载进度 + * */ + public void showProgress(Progress progress) { + downloadFileName = progress.fileName; + String downloadLength = Formatter.formatFileSize(MainApplication.getContext(), progress.currentSize); + String totalLength = Formatter.formatFileSize(MainApplication.getContext(), progress.totalSize); + + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + Notification notification = new NotificationCompat.Builder(context, MainNotification.CHANNEL_DOWNLOADING_ID) + .setSmallIcon(R.drawable.icon_user_pic) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_user_pic)) + .setContentTitle(progress.fileName) + .setContentText("下载进度:" + String.format(Locale.CHINA, "%s / %s", downloadLength, totalLength)) + .setProgress(100, (int) (progress.fraction * 100.0), false) + .setDefaults(NotificationCompat.FLAG_ONLY_ALERT_ONCE) + .build(); + + if (notificationManager != null) { + notificationManager.notify(Tag,notificationId,notification); + } + } + + /** + * 下载完成,关闭进度条通知栏,显示成功通知栏 + * */ + public void downloadComplete() { + int downloadCompleteId = (int) new Date().getTime(); + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + Notification notification = new NotificationCompat.Builder(context, MainNotification.CHANNEL_DOWNLOAD_COMPLETE_ID) + .setSmallIcon(R.drawable.icon_user_pic) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_user_pic)) + .setContentTitle(downloadFileName) + .setContentText("下载完成!") + .setPriority(NotificationCompat.PRIORITY_MAX) + .setAutoCancel(true) + .build(); + if (notificationManager != null) { + notificationManager.notify(downloadCompleteId,notification); + notificationManager.cancel(Tag,notificationId); + } + } + + /** + * 下载错误,关闭进度条通知栏,显示错误通知栏 + * */ + public void downloadError() { + int downloadCompleteId = (int) new Date().getTime(); + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + + Notification notification = new NotificationCompat.Builder(context, MainNotification.CHANNEL_DOWNLOAD_COMPLETE_ID) + .setSmallIcon(R.drawable.icon_user_pic) + .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_user_pic)) + .setContentTitle(downloadFileName) + .setContentText("下载失败,请重新下载!") + .setAutoCancel(true) + .build(); + if (notificationManager != null) { + notificationManager.notify(downloadCompleteId,notification); + notificationManager.cancel(Tag,notificationId); + } + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/notification/MainNotification.java b/app/src/main/java/com/bonait/bnframework/common/notification/MainNotification.java new file mode 100644 index 00000000..033e635c --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/notification/MainNotification.java @@ -0,0 +1,85 @@ +package com.bonait.bnframework.common.notification; + +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; +import android.app.NotificationManager; +import android.content.Context; +import android.os.Build; +import android.support.annotation.RequiresApi; + + +/** + * Created by LY on 2019/4/22. + * 初始化notification,针对Android8.0以上创建消息渠道组和消息渠道 + */ +public class MainNotification { + + public static final String GROUP_DOWNLOAD_ID = "group_download_id"; + public static final String GROUP_PUSH_MESSAGE_ID = "group_push_message_id"; + + public static final String CHANNEL_DOWNLOADING_ID = "channel_downloading_id"; + public static final String CHANNEL_DOWNLOAD_COMPLETE_ID = "channel_download_complete_id"; + public static final String CHANNEL_MESSAGE_ID = "channel_message_id"; + + /** + * 初始化Notification 消息通知渠道 + */ + public static void initNotificationChannel(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // 创建渠道组 + createNotificationGroup(context, GROUP_DOWNLOAD_ID, "下载消息"); + createNotificationGroup(context, GROUP_PUSH_MESSAGE_ID, "推送消息"); + + // 创建渠道 + // 下载完成通知栏 + createNotificationChannel(context, + GROUP_DOWNLOAD_ID, + CHANNEL_DOWNLOAD_COMPLETE_ID, + "下载完成通知栏", + NotificationManager.IMPORTANCE_MAX); + + // 下载通知栏 + createNotificationChannel(context, + GROUP_DOWNLOAD_ID, + CHANNEL_DOWNLOADING_ID, + "下载通知栏", + NotificationManager.IMPORTANCE_LOW); + + // 常规通知栏 + createNotificationChannel(context, + GROUP_PUSH_MESSAGE_ID, + CHANNEL_MESSAGE_ID, + "常规通知栏", + NotificationManager.IMPORTANCE_MAX); + + } + } + + /** + * 创建消息渠道组 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static void createNotificationGroup(Context context, String groupId, String groupName) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationChannelGroup group = new NotificationChannelGroup(groupId, groupName); + if (notificationManager != null) { + notificationManager.createNotificationChannelGroup(group); + } + } + + /** + * 创建消息渠道,并分配到指定组下。 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + public static void createNotificationChannel(Context context, String groupId, String channelId, String channelName, int importance) { + NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationChannel channel = new NotificationChannel(channelId, channelName, importance); + channel.canShowBadge(); + //在创建通知渠道前,指定渠道组 id + channel.setGroup(groupId); + if (notificationManager != null) { + notificationManager.createNotificationChannel(channel); + } + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/AlertDialogUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/AlertDialogUtils.java new file mode 100644 index 00000000..1ed9d6b1 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/AlertDialogUtils.java @@ -0,0 +1,104 @@ +package com.bonait.bnframework.common.utils; + +import android.content.Context; + +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +/** + * Created by LY on 2019/3/25. + */ +public class AlertDialogUtils { + + // 定义对话框主题样式 + public static final int mCurrentDialogStyle = com.qmuiteam.qmui.R.style.QMUI_Dialog; + + /** + * 对话框,只有确定按钮 + * */ + public static void showDialog(Context context,String title,String message) { + new QMUIDialog.MessageDialogBuilder(context) + .setCancelable(false) + .setTitle(title) + .setMessage(message) + .addAction("确定", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + dialog.dismiss(); + } + }) + .create(mCurrentDialogStyle).show(); + } + + /** + * 对话框,只有确定按钮,自定义确定按钮文本 + * */ + public static void showDialog(Context context, String title, String message,String ok) { + new QMUIDialog.MessageDialogBuilder(context) + .setCancelable(false) + .setTitle(title) + .setMessage(message) + .addAction(ok, new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + dialog.dismiss(); + } + }) + .create(mCurrentDialogStyle).show(); + } + + /** + * 对话框,有取消确定按钮 + * */ + public static void showDialog(Context context, String title, String message, QMUIDialogAction.ActionListener onClickListener) { + new QMUIDialog.MessageDialogBuilder(context) + .setCancelable(false) + .setTitle(title) + .setMessage(message) + .addAction("取消", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + dialog.dismiss(); + } + }) + .addAction("确定", onClickListener) + .create(mCurrentDialogStyle).show(); + } + + /** + * 对话框,自定义确定按钮 + * */ + public static void showDialog(Context context, String title, String message,String ok, QMUIDialogAction.ActionListener onClickListener) { + new QMUIDialog.MessageDialogBuilder(context) + .setCancelable(false) + .setTitle(title) + .setMessage(message) + .addAction("取消", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + dialog.dismiss(); + } + }) + .addAction(ok, onClickListener) + .create(mCurrentDialogStyle).show(); + } + + /** + * 对话框,带红色按钮 + * */ + public static void showRedButtonDialog(Context context, String title, String message,String ok, QMUIDialogAction.ActionListener onClickListener) { + new QMUIDialog.MessageDialogBuilder(context) + .setCancelable(false) + .setTitle(title) + .setMessage(message) + .addAction("取消", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + dialog.dismiss(); + } + }) + .addAction(0, ok, QMUIDialogAction.ACTION_PROP_NEGATIVE, onClickListener) + .create(mCurrentDialogStyle).show(); + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/AnimationToolUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/AnimationToolUtils.java new file mode 100644 index 00000000..b2c61941 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/AnimationToolUtils.java @@ -0,0 +1,226 @@ +package com.bonait.bnframework.common.utils; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.AnimatorSet; +import android.animation.ArgbEvaluator; +import android.animation.ObjectAnimator; +import android.animation.PropertyValuesHolder; +import android.animation.ValueAnimator; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateInterpolator; +import android.view.animation.Animation; +import android.view.animation.AnticipateOvershootInterpolator; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.view.animation.LinearInterpolator; +import android.view.animation.OvershootInterpolator; +import android.view.animation.ScaleAnimation; + +import com.bonait.bnframework.common.interfaces.OnDoIntListener; + +/** + * Created by LY on 2019/3/28. + * 动画工具类 + */ +public class AnimationToolUtils { + public static void start(Animator animator) { + if (animator != null && !animator.isStarted()) { + animator.start(); + } + } + + public static void stop(Animator animator) { + if (animator != null && !animator.isRunning()) { + animator.end(); + } + } + + public static boolean isRunning(ValueAnimator animator) { + return animator != null && animator.isRunning(); + } + + public static boolean isStarted(ValueAnimator animator) { + return animator != null && animator.isStarted(); + } + + /** + * 颜色渐变动画 + * + * @param beforeColor 变化之前的颜色 + * @param afterColor 变化之后的颜色 + * @param listener 变化事件 + */ + public static void animationColorGradient(int beforeColor, int afterColor, final OnDoIntListener listener) { + ValueAnimator valueAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), beforeColor, afterColor).setDuration(3000); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + // textView.setTextColor((Integer) animation.getAnimatedValue()); + listener.doSomething((Integer) animation.getAnimatedValue()); + } + }); + valueAnimator.start(); + } + + /** + * 卡片翻转动画 + * + * @param beforeView + * @param afterView + */ + public static void cardFilpAnimation(final View beforeView, final View afterView) { + Interpolator accelerator = new AccelerateInterpolator(); + Interpolator decelerator = new DecelerateInterpolator(); + ObjectAnimator invisToVis = null; + ObjectAnimator visToInvis = null; + if (beforeView.getVisibility() == View.GONE) { + // 局部layout可达到字体翻转 背景不翻转 + invisToVis = ObjectAnimator.ofFloat(beforeView, + "rotationY", -90f, 0f); + visToInvis = ObjectAnimator.ofFloat(afterView, + "rotationY", 0f, 90f); + } else if (afterView.getVisibility() == View.GONE) { + invisToVis = ObjectAnimator.ofFloat(afterView, + "rotationY", -90f, 0f); + visToInvis = ObjectAnimator.ofFloat(beforeView, + "rotationY", 0f, 90f); + } + + visToInvis.setDuration(250);// 翻转速度 + visToInvis.setInterpolator(accelerator);// 在动画开始的地方速率改变比较慢,然后开始加速 + invisToVis.setDuration(250); + invisToVis.setInterpolator(decelerator); + final ObjectAnimator finalInvisToVis = invisToVis; + final ObjectAnimator finalVisToInvis = visToInvis; + visToInvis.addListener(new Animator.AnimatorListener() { + + @Override + public void onAnimationEnd(Animator arg0) { + if (beforeView.getVisibility() == View.GONE) { + afterView.setVisibility(View.GONE); + finalInvisToVis.start(); + beforeView.setVisibility(View.VISIBLE); + } else { + afterView.setVisibility(View.GONE); + finalVisToInvis.start(); + beforeView.setVisibility(View.VISIBLE); + } + } + + @Override + public void onAnimationCancel(Animator arg0) { + + } + + @Override + public void onAnimationRepeat(Animator arg0) { + + } + + @Override + public void onAnimationStart(Animator arg0) { + + } + }); + visToInvis.start(); + } + + /** + * 缩小动画 + * + * @param view + */ + public static void zoomIn(final View view, float scale, float dist) { + view.setPivotY(view.getHeight()); + view.setPivotX(view.getWidth() / 2); + AnimatorSet mAnimatorSet = new AnimatorSet(); + ObjectAnimator mAnimatorScaleX = ObjectAnimator.ofFloat(view, "scaleX", 1.0f, scale); + ObjectAnimator mAnimatorScaleY = ObjectAnimator.ofFloat(view, "scaleY", 1.0f, scale); + ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(view, "translationY", 0.0f, -dist); + + mAnimatorSet.play(mAnimatorTranslateY).with(mAnimatorScaleX); + mAnimatorSet.play(mAnimatorScaleX).with(mAnimatorScaleY); + mAnimatorSet.setDuration(300); + mAnimatorSet.start(); + } + + /** + * 放大动画 + * + * @param view + */ + public static void zoomOut(final View view, float scale) { + view.setPivotY(view.getHeight()); + view.setPivotX(view.getWidth() / 2); + AnimatorSet mAnimatorSet = new AnimatorSet(); + + ObjectAnimator mAnimatorScaleX = ObjectAnimator.ofFloat(view, "scaleX", scale, 1.0f); + ObjectAnimator mAnimatorScaleY = ObjectAnimator.ofFloat(view, "scaleY", scale, 1.0f); + ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(view, "translationY", view.getTranslationY(), 0); + + mAnimatorSet.play(mAnimatorTranslateY).with(mAnimatorScaleX); + mAnimatorSet.play(mAnimatorScaleX).with(mAnimatorScaleY); + mAnimatorSet.setDuration(300); + mAnimatorSet.start(); + } + + public static void ScaleUpDowm(View view) { + ScaleAnimation animation = new ScaleAnimation(1.0f, 1.0f, 0.0f, 1.0f); + animation.setRepeatCount(-1); + animation.setRepeatMode(Animation.RESTART); + animation.setInterpolator(new LinearInterpolator()); + animation.setDuration(1200); + view.startAnimation(animation); + } + + public static void animateHeight(int start, int end, final View view) { + ValueAnimator valueAnimator = ValueAnimator.ofInt(start, end); + valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animation) { + int value = (int) animation.getAnimatedValue();//根据时间因子的变化系数进行设置高度 + ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); + layoutParams.height = value; + view.setLayoutParams(layoutParams);//设置高度 + } + }); + valueAnimator.start(); + } + + public static ObjectAnimator popup(final View view, final long duration) { + view.setAlpha(0); + view.setVisibility(View.VISIBLE); + + ObjectAnimator popup = ObjectAnimator.ofPropertyValuesHolder(view, + PropertyValuesHolder.ofFloat("alpha", 0f, 1f), + PropertyValuesHolder.ofFloat("scaleX", 0f, 1f), + PropertyValuesHolder.ofFloat("scaleY", 0f, 1f)); + popup.setDuration(duration); + popup.setInterpolator(new OvershootInterpolator()); + + return popup; + } + + public static ObjectAnimator popout(final View view, final long duration, final AnimatorListenerAdapter animatorListenerAdapter) { + ObjectAnimator popout = ObjectAnimator.ofPropertyValuesHolder(view, + PropertyValuesHolder.ofFloat("alpha", 1f, 0f), + PropertyValuesHolder.ofFloat("scaleX", 1f, 0f), + PropertyValuesHolder.ofFloat("scaleY", 1f, 0f)); + popout.setDuration(duration); + popout.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + super.onAnimationEnd(animation); + view.setVisibility(View.GONE); + if (animatorListenerAdapter != null) { + animatorListenerAdapter.onAnimationEnd(animation); + } + } + }); + popout.setInterpolator(new AnticipateOvershootInterpolator()); + + return popout; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/AppFileUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/AppFileUtils.java new file mode 100644 index 00000000..12966c8e --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/AppFileUtils.java @@ -0,0 +1,217 @@ +package com.bonait.bnframework.common.utils; + +import android.os.Environment; + +import java.io.File; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Created by LY on 2019/1/17. + */ +public class AppFileUtils { + + private static final String APP_PROJECT_DIR = "com.bonait.bnframework"; + private static final String APP_DIR = "bnframework"; + private static final String UPLOAD_IMAGE = "UploadImage"; + private static final String UPLOAD_FILE ="UploadFile"; + private static final String DOWNLOAD_IMAGE = "DownloadImage"; + private static final String DOWNLOAD_FILE ="DownloadFile"; + private static final String USER_FILE = "UserFile"; + private static final String CACHE = "Cache"; + private static final String CAMERA_V2 = "CameraV2"; + public static final String ZIP_FILE = "ZIP"; + + + /** + * 获取data目录下的图片目录 + * */ + public static String getUploadImageDir() { + String dir = getPrivateAppDir() +"/" + UPLOAD_IMAGE+"/"; + return mkdirs(dir); + } + + /** + * 获取data目录下的文件目录 + * */ + public static String getUploadFileDir() { + String dir = getPrivateAppDir() +"/" + UPLOAD_FILE+"/"; + return mkdirs(dir); + } + + /** + * 获取data目录下的文件目录 + * */ + public static String getZipFileDir() { + String dir = getPrivateAppDir() +"/" + ZIP_FILE+"/"; + return mkdirs(dir); + } + + public static String getCacheDir() { + String dir = getPrivateAppDir() + "/"+CACHE+"/"; + return mkdirs(dir); + } + + /** + * 获取上传文件目录 + * */ + public static String getPublicUploadFileDir() { + String dir = getAppDir() + "/"+UPLOAD_FILE+"/"; + return mkdirs(dir); + } + + /** + * 获取上传文件目录 + * */ + public static String getPublicCamera2Dir() { + String dir = getAppDir() + "/"+ CAMERA_V2 +"/"; + return mkdirs(dir); + } + + + /** + * 获取图片目录 + * */ + public static String getDownloadImageDir() { + String dir = getAppDir() + "/"+DOWNLOAD_IMAGE+"/"; + return mkdirs(dir); + } + + /** + * 获取文件目录 + * */ + public static String getDownloadFileDir() { + String dir = getAppDir() + "/"+DOWNLOAD_FILE+"/"; + return mkdirs(dir); + } + + /** + * 获取图片目录 + * */ + public static String getUserFileDir() { + String dir = getAppDir() + "/"+USER_FILE+"/"; + return mkdirs(dir); + } + + /** + * 复制文件到本App目录下,并返回目标文件路径 + * + * @param srcFilePath 原文件路径 + * + * @param toPathDir 复制目标的 目录 路径 + * + * @return 目标文件路径 + * */ + public static String copyToPath(final String srcFilePath, final String toPathDir) { + + // 获取格式正确的文件名字 + String str = FileUtils.getFileName(srcFilePath); + String srcFileName = stringFilter(str); + // 目标路径 + String toPath = toPathDir + srcFileName; + // 如果用户选中的是本app目录下的文件,则返回原路径 + if (toPath.equals(srcFilePath)) { + return srcFilePath; + } + //检查目标文件路径是否重名 + String toPrivateAppPath = checkToAppPathIsExists(toPath); + boolean flag = FileUtils.copyFile(srcFilePath,toPrivateAppPath); + if (flag) { + return toPrivateAppPath; + } else { + //复制失败,返回原路径 + return srcFilePath; + } + } + + /** + * 判断目标路径是否有同名文件,若同名则修改文件名,并返回文件名 + * @param oldPath 原文件路径 + * @param toPathDir 目标路径 + * @return 目标新文件路径 + * */ + public static String checkToPathAndGetNewFileName(String oldPath, final String toPathDir) { + // 获取格式正确的文件名字 + String str = FileUtils.getFileName(oldPath); + String srcFileName = stringFilter(str); + // 目标路径 + String toPath = toPathDir + srcFileName; + //检查目标文件路径是否重名 + return checkToAppPathIsExists(toPath); + } + + /** + * 检查目标路径文件是否重名,若重名则名字后面加(i); + * */ + public static String checkToAppPathIsExists(String path) { + String toNewPath = path; + + String prefix = FileUtils.getFileNameNoExtension(path);//提取文件名字,没有后缀 + String suffix = FileUtils.getFileExtension(path);//提取文件名字后缀 + + //循环判断文件是否存在,存在就加(i),知道不存在为止 + for (int i = 1; FileUtils.isFileExists(toNewPath) && i < Integer.MAX_VALUE; i++) { + toNewPath = FileUtils.getDirName(path) + prefix + "(" + i + ")."+suffix; + } + return toNewPath; + } + + /** + * @param name 文件名字 + * @return 文件保存路径 + * */ + public static String savePath(final String name) { + //获取格式正确的文件名字 + String srcFileName = AppFileUtils.stringFilter(name); + // 获取预保存地址 + String path = AppFileUtils.getDownloadFileDir() + srcFileName; + // 检查地址是否有重名,返回不重名的地址 + return AppFileUtils.checkToAppPathIsExists(path); + } + + /** + * @param name 文件名字 + * @return 文件保存路径 + * */ + public static String saveImagePath(final String name) { + //获取格式正确的文件名字 + String srcFileName = AppFileUtils.stringFilter(name); + // 获取预保存地址 + String path = AppFileUtils.getDownloadImageDir() + srcFileName; + // 检查地址是否有重名,返回不重名的地址 + return AppFileUtils.checkToAppPathIsExists(path); + } + + + /****************************内部方法调用************************************/ + + private static String getAppDir(){ + return Environment.getExternalStorageDirectory().getPath()+"/"+APP_DIR; + } + + private static String getPrivateAppDir() { + return Environment.getExternalStorageDirectory().getPath() + "/Android/data/" + APP_PROJECT_DIR +"/"+APP_DIR; + } + + private static String mkdirs(String dir) { + File file = new File(dir); + if (!file.exists()) { + file.mkdirs(); + } + return dir; + } + + + /** + * 过滤特殊字符(\/:*?"<>|) + */ + public static String stringFilter(String str) { + if (str == null) { + return null; + } + String regEx = "[\\/:*?\"<>|]"; + Pattern p = Pattern.compile(regEx); + Matcher m = p.matcher(str); + return m.replaceAll("").trim(); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/AppUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/AppUtils.java new file mode 100644 index 00000000..d8f762c8 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/AppUtils.java @@ -0,0 +1,108 @@ +package com.bonait.bnframework.common.utils; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; + +import org.apache.commons.codec.binary.Base64; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; + +/** + * Created by LY on 2019/3/21. + * App版本信息工具类 + */ +public class AppUtils { + /** + * 获取版本名称 + * + * @param context 上下文 + * + * @return 版本名称 + */ + public static String getVersionName(Context context) { + + //获取包管理器 + PackageManager pm = context.getPackageManager(); + //获取包信息 + try { + PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + //返回版本号 + return packageInfo.versionName; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + return null; + + } + + /** + * 获取版本号 + * + * @param context 上下文 + * + * @return 版本号 + */ + public static int getVersionCode(Context context) { + + //获取包管理器 + PackageManager pm = context.getPackageManager(); + //获取包信息 + try { + PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + //返回版本号 + return packageInfo.versionCode; + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + return 0; + + } + + /** + * 获取App的名称 + * + * @param context 上下文 + * + * @return 名称 + */ + public static String getAppName(Context context) { + PackageManager pm = context.getPackageManager(); + //获取包信息 + try { + PackageInfo packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + //获取应用 信息 + ApplicationInfo applicationInfo = packageInfo.applicationInfo; + //获取albelRes + int labelRes = applicationInfo.labelRes; + //返回App的名称 + return context.getResources().getString(labelRes); + } catch (PackageManager.NameNotFoundException e) { + e.printStackTrace(); + } + + return null; + } + + /** + * 密码加密,使用SHA-256加密 + * + * @param inputStr 密码 + * + * @return 加密 + */ + public static synchronized String encryptSha256(String inputStr) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + byte digest[] = md.digest(inputStr.getBytes(StandardCharsets.UTF_8)); + return new String(Base64.encodeBase64(digest)); + } catch (Exception e) { + return null; + } + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/Des3Utils.java b/app/src/main/java/com/bonait/bnframework/common/utils/Des3Utils.java new file mode 100644 index 00000000..60511bc0 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/Des3Utils.java @@ -0,0 +1,98 @@ +package com.bonait.bnframework.common.utils; + + +import android.text.TextUtils; +import android.util.Base64; + +import java.security.Key; + +import javax.crypto.Cipher; +import javax.crypto.SecretKeyFactory; +import javax.crypto.spec.DESedeKeySpec; +import javax.crypto.spec.IvParameterSpec; + +/** + * Created by LY on 2019/4/10. + * 3DES 加密/解密 + */ +public class Des3Utils { + + // 密钥 长度等于24,且不得小于24 + private final static String secretKey = "CW0Y&S7l1BVU2zwKop@syT92"; + // 向量 可有可无 终端后台也要约定 + private final static String iv = "01234567"; + + /** + * 3DES转变 + *

法算法名称/加密模式/填充方式

+ *

加密模式有:电子密码本模式ECB、加密块链模式CBC、加密反馈模式CFB、输出反馈模式OFB

+ *

填充方式有:NoPadding、ZerosPadding、PKCS5Padding

+ */ + private static String TripleDES_Transformation = "desede/CBC/PKCS5Padding"; + + // 加密算法类型 + private static final String TripleDES_Algorithm = "desede"; + + // 加解密统一使用的编码方式 + private final static String encoding = "utf-8"; + + /** + * 3DES加密 + * + * @param plainText 普通文本 + * @return 加密Str + */ + public static String encode(String plainText) { + if (TextUtils.isEmpty(plainText)) { + return null; + } + + try { + DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(TripleDES_Algorithm); + Key desKey = keyFactory.generateSecret(spec); + + Cipher cipher = Cipher.getInstance(TripleDES_Transformation); + IvParameterSpec ips = new IvParameterSpec(iv.getBytes()); + cipher.init(Cipher.ENCRYPT_MODE, desKey, ips); + + byte[] encryptData = cipher.doFinal(plainText.getBytes(encoding)); + + return Base64.encodeToString(encryptData, Base64.DEFAULT); + + }catch (Exception e ) { + e.printStackTrace(); + return null; + } + } + + /** + * 3DES解密 + * + * @param encryptText 加密文本 + * @return 解密Str + */ + public static String decode(String encryptText) { + if (TextUtils.isEmpty(encryptText)) { + return null; + } + + try { + DESedeKeySpec spec = new DESedeKeySpec(secretKey.getBytes()); + SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(TripleDES_Algorithm); + Key desKey = keyFactory.generateSecret(spec); + + Cipher cipher = Cipher.getInstance(TripleDES_Transformation); + IvParameterSpec ips = new IvParameterSpec(iv.getBytes()); + cipher.init(Cipher.DECRYPT_MODE, desKey, ips); + + byte[] decryptData = cipher.doFinal(Base64.decode(encryptText, Base64.DEFAULT)); + + return new String(decryptData, encoding); + + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/FileUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/FileUtils.java new file mode 100644 index 00000000..9014fef2 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/FileUtils.java @@ -0,0 +1,1300 @@ +package com.bonait.bnframework.common.utils; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.File; +import java.io.FileFilter; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +/** + * Created by LY on 2019/1/17. + * getFileByPath : 根据文件路径获取文件 + * isFileExists : 判断文件是否存在 + * rename : 重命名文件 + * isDir : 判断是否是目录 + * isFile : 判断是否是文件 + * createOrExistsDir : 判断目录是否存在,不存在则判断是否创建成功 + * createOrExistsFile : 判断文件是否存在,不存在则判断是否创建成功 + * createFileByDeleteOldFile: 判断文件是否存在,存在则在创建之前删除 + * copyDir : 复制目录 + * copyFile : 复制文件 + * moveDir : 移动目录 + * moveFile : 移动文件 + * deleteDir : 删除目录 + * deleteFile : 删除文件 + * listFilesInDir : 获取目录下所有文件 + * listFilesInDir : 获取目录下所有文件包括子目录 + * listFilesInDirWithFilter : 获取目录下所有后缀名为suffix的文件 + * listFilesInDirWithFilter : 获取目录下所有后缀名为suffix的文件包括子目录 + * listFilesInDirWithFilter : 获取目录下所有符合filter的文件 + * listFilesInDirWithFilter : 获取目录下所有符合filter的文件包括子目录 + * searchFileInDir : 获取目录下指定文件名的文件包括子目录 + * writeFileFromIS : 将输入流写入文件 + * writeFileFromString : 将字符串写入文件 + * readFile2List : 指定编码按行读取文件到链表中 + * readFile2String : 指定编码按行读取文件到字符串中 + * readFile2Bytes : 读取文件到字符数组中 + * getFileLastModified : 获取文件最后修改的毫秒时间戳 + * getFileCharsetSimple : 简单获取文件编码格式 + * getFileLines : 获取文件行数 + * getDirSize : 获取目录大小 + * getFileSize : 获取文件大小 + * getDirLength : 获取目录长度 + * getFileLength : 获取文件长度 + * getFileMD5 : 获取文件的MD5校验码 + * getFileMD5ToString : 获取文件的MD5校验码 + * getDirName : 根据全路径获取最长目录 + * getFileName : 根据全路径获取文件名 + * getFileNameNoExtension : 根据全路径获取文件名不带拓展名 + * getFileExtension : 根据全路径获取文件的拓展名 + * + */ +public class FileUtils { + private static final String LINE_SEP = System.getProperty("line.separator"); + + private FileUtils() { + throw new UnsupportedOperationException("u can't instantiate me..."); + } + + /** + * Return the file by path. + * + * @param filePath The path of file. + * @return the file + */ + public static File getFileByPath(final String filePath) { + return isSpace(filePath) ? null : new File(filePath); + } + + /** + * Return whether the file exists. + * + * @param filePath The path of file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFileExists(final String filePath) { + return isFileExists(getFileByPath(filePath)); + } + + /** + * Return whether the file exists. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFileExists(final File file) { + return file != null && file.exists(); + } + + /** + * Rename the file. + * + * @param filePath The path of file. + * @param newName The new name of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean rename(final String filePath, final String newName) { + return rename(getFileByPath(filePath), newName); + } + + /** + * Rename the file. + * + * @param file The file. + * @param newName The new name of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean rename(final File file, final String newName) { + // file is null then return false + if (file == null) return false; + // file doesn't exist then return false + if (!file.exists()) return false; + // the new name is space then return false + if (isSpace(newName)) return false; + // the new name equals old name then return true + if (newName.equals(file.getName())) return true; + File newFile = new File(file.getParent() + File.separator + newName); + // the new name of file exists then return false + return !newFile.exists() + && file.renameTo(newFile); + } + + /** + * Return whether it is a directory. + * + * @param dirPath The path of directory. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isDir(final String dirPath) { + return isDir(getFileByPath(dirPath)); + } + + /** + * Return whether it is a directory. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isDir(final File file) { + return file != null && file.exists() && file.isDirectory(); + } + + /** + * Return whether it is a file. + * + * @param filePath The path of file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFile(final String filePath) { + return isFile(getFileByPath(filePath)); + } + + /** + * Return whether it is a file. + * + * @param file The file. + * @return {@code true}: yes
{@code false}: no + */ + public static boolean isFile(final File file) { + return file != null && file.exists() && file.isFile(); + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param dirPath The path of directory. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsDir(final String dirPath) { + return createOrExistsDir(getFileByPath(dirPath)); + } + + /** + * Create a directory if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsDir(final File file) { + return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param filePath The path of file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsFile(final String filePath) { + return createOrExistsFile(getFileByPath(filePath)); + } + + /** + * Create a file if it doesn't exist, otherwise do nothing. + * + * @param file The file. + * @return {@code true}: exists or creates successfully
{@code false}: otherwise + */ + public static boolean createOrExistsFile(final File file) { + if (file == null) return false; + if (file.exists()) return file.isFile(); + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Create a file if it doesn't exist, otherwise delete old file before creating. + * + * @param filePath The path of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean createFileByDeleteOldFile(final String filePath) { + return createFileByDeleteOldFile(getFileByPath(filePath)); + } + + /** + * Create a file if it doesn't exist, otherwise delete old file before creating. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean createFileByDeleteOldFile(final File file) { + if (file == null) return false; + // file exists and unsuccessfully delete then return false + if (file.exists() && !file.delete()) return false; + if (!createOrExistsDir(file.getParentFile())) return false; + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Copy the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final String srcDirPath, + final String destDirPath) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath)); + } + + /** + * Copy the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener) { + return copyDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * Copy the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final File srcDir, + final File destDir) { + return copyOrMoveDir(srcDir, destDir, false); + } + + /** + * Copy the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyDir(final File srcDir, + final File destDir, + final OnReplaceListener listener) { + return copyOrMoveDir(srcDir, destDir, listener, false); + } + + /** + * Copy the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final String srcFilePath, + final String destFilePath) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath)); + } + + /** + * Copy the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener) { + return copyFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * Copy the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final File srcFile, + final File destFile) { + return copyOrMoveFile(srcFile, destFile, false); + } + + /** + * Copy the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean copyFile(final File srcFile, + final File destFile, + final OnReplaceListener listener) { + return copyOrMoveFile(srcFile, destFile, listener, false); + } + + /** + * Move the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final String srcDirPath, + final String destDirPath) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath)); + } + + /** + * Move the directory. + * + * @param srcDirPath The path of source directory. + * @param destDirPath The path of destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final String srcDirPath, + final String destDirPath, + final OnReplaceListener listener) { + return moveDir(getFileByPath(srcDirPath), getFileByPath(destDirPath), listener); + } + + /** + * Move the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final File srcDir, + final File destDir) { + return copyOrMoveDir(srcDir, destDir, true); + } + + /** + * Move the directory. + * + * @param srcDir The source directory. + * @param destDir The destination directory. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveDir(final File srcDir, + final File destDir, + final OnReplaceListener listener) { + return copyOrMoveDir(srcDir, destDir, listener, true); + } + + /** + * Move the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final String srcFilePath, + final String destFilePath) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath)); + } + + /** + * Move the file. + * + * @param srcFilePath The path of source file. + * @param destFilePath The path of destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final String srcFilePath, + final String destFilePath, + final OnReplaceListener listener) { + return moveFile(getFileByPath(srcFilePath), getFileByPath(destFilePath), listener); + } + + /** + * Move the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final File srcFile, + final File destFile) { + return copyOrMoveFile(srcFile, destFile, true); + } + + /** + * Move the file. + * + * @param srcFile The source file. + * @param destFile The destination file. + * @param listener The replace listener. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean moveFile(final File srcFile, + final File destFile, + final OnReplaceListener listener) { + return copyOrMoveFile(srcFile, destFile, listener, true); + } + + private static boolean copyOrMoveDir(final File srcDir, + final File destDir, + final boolean isMove) { + return copyOrMoveDir(srcDir, destDir, new OnReplaceListener() { + @Override + public boolean onReplace() { + return true; + } + }, isMove); + } + + private static boolean copyOrMoveDir(final File srcDir, + final File destDir, + final OnReplaceListener listener, + final boolean isMove) { + if (srcDir == null || destDir == null) return false; + // destDir's path locate in srcDir's path then return false + String srcPath = srcDir.getPath() + File.separator; + String destPath = destDir.getPath() + File.separator; + if (destPath.contains(srcPath)) return false; + if (!srcDir.exists() || !srcDir.isDirectory()) return false; + if (destDir.exists()) { + if (listener == null || listener.onReplace()) {// require delete the old directory + if (!deleteAllInDir(destDir)) {// unsuccessfully delete then return false + return false; + } + } else { + return true; + } + } + if (!createOrExistsDir(destDir)) return false; + File[] files = srcDir.listFiles(); + for (File file : files) { + File oneDestFile = new File(destPath + file.getName()); + if (file.isFile()) { + if (!copyOrMoveFile(file, oneDestFile, listener, isMove)) return false; + } else if (file.isDirectory()) { + if (!copyOrMoveDir(file, oneDestFile, listener, isMove)) return false; + } + } + return !isMove || deleteDir(srcDir); + } + + private static boolean copyOrMoveFile(final File srcFile, + final File destFile, + final boolean isMove) { + return copyOrMoveFile(srcFile, destFile, new OnReplaceListener() { + @Override + public boolean onReplace() { + return true; + } + }, isMove); + } + + private static boolean copyOrMoveFile(final File srcFile, + final File destFile, + final OnReplaceListener listener, + final boolean isMove) { + if (srcFile == null || destFile == null) return false; + // srcFile equals destFile then return false + if (srcFile.equals(destFile)) return false; + // srcFile doesn't exist or isn't a file then return false + if (!srcFile.exists() || !srcFile.isFile()) return false; + if (destFile.exists()) { + if (listener == null || listener.onReplace()) {// require delete the old file + if (!destFile.delete()) {// unsuccessfully delete then return false + return false; + } + } else { + return true; + } + } + if (!createOrExistsDir(destFile.getParentFile())) return false; + try { + return writeFileFromIS(destFile, new FileInputStream(srcFile)) + && !(isMove && !deleteFile(srcFile)); + } catch (FileNotFoundException e) { + e.printStackTrace(); + return false; + } + } + + /** + * Delete the directory. + * + * @param filePath The path of file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean delete(final String filePath) { + return delete(getFileByPath(filePath)); + } + + /** + * Delete the directory. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean delete(final File file) { + if (file == null) return false; + if (file.isDirectory()) { + return deleteDir(file); + } + return deleteFile(file); + } + + /** + * Delete the directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteDir(final String dirPath) { + return deleteDir(getFileByPath(dirPath)); + } + + /** + * Delete the directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteDir(final File dir) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + return dir.delete(); + } + + /** + * Delete the file. + * + * @param srcFilePath The path of source file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFile(final String srcFilePath) { + return deleteFile(getFileByPath(srcFilePath)); + } + + /** + * Delete the file. + * + * @param file The file. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFile(final File file) { + return file != null && (!file.exists() || file.isFile() && file.delete()); + } + + /** + * Delete the all in directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteAllInDir(final String dirPath) { + return deleteAllInDir(getFileByPath(dirPath)); + } + + /** + * Delete the all in directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteAllInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }); + } + + /** + * Delete all files in directory. + * + * @param dirPath The path of directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDir(final String dirPath) { + return deleteFilesInDir(getFileByPath(dirPath)); + } + + /** + * Delete all files in directory. + * + * @param dir The directory. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDir(final File dir) { + return deleteFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return pathname.isFile(); + } + }); + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dirPath The path of directory. + * @param filter The filter. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDirWithFilter(final String dirPath, + final FileFilter filter) { + return deleteFilesInDirWithFilter(getFileByPath(dirPath), filter); + } + + /** + * Delete all files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @return {@code true}: success
{@code false}: fail + */ + public static boolean deleteFilesInDirWithFilter(final File dir, final FileFilter filter) { + if (dir == null) return false; + // dir doesn't exist then return true + if (!dir.exists()) return true; + // dir isn't a directory then return false + if (!dir.isDirectory()) return false; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + if (file.isFile()) { + if (!file.delete()) return false; + } else if (file.isDirectory()) { + if (!deleteDir(file)) return false; + } + } + } + } + return true; + } + + /** + * Return the files in directory. + *

Doesn't traverse subdirectories

+ * + * @param dirPath The path of directory. + * @return the files in directory + */ + public static List listFilesInDir(final String dirPath) { + return listFilesInDir(dirPath, false); + } + + /** + * Return the files in directory. + *

Doesn't traverse subdirectories

+ * + * @param dir The directory. + * @return the files in directory + */ + public static List listFilesInDir(final File dir) { + return listFilesInDir(dir, false); + } + + /** + * Return the files in directory. + * + * @param dirPath The path of directory. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files in directory + */ + public static List listFilesInDir(final String dirPath, final boolean isRecursive) { + return listFilesInDir(getFileByPath(dirPath), isRecursive); + } + + /** + * Return the files in directory. + * + * @param dir The directory. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files in directory + */ + public static List listFilesInDir(final File dir, final boolean isRecursive) { + return listFilesInDirWithFilter(dir, new FileFilter() { + @Override + public boolean accept(File pathname) { + return true; + } + }, isRecursive); + } + + /** + * Return the files that satisfy the filter in directory. + *

Doesn't traverse subdirectories

+ * + * @param dirPath The path of directory. + * @param filter The filter. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final String dirPath, + final FileFilter filter) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, false); + } + + /** + * Return the files that satisfy the filter in directory. + *

Doesn't traverse subdirectories

+ * + * @param dir The directory. + * @param filter The filter. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final File dir, + final FileFilter filter) { + return listFilesInDirWithFilter(dir, filter, false); + } + + /** + * Return the files that satisfy the filter in directory. + * + * @param dirPath The path of directory. + * @param filter The filter. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final String dirPath, + final FileFilter filter, + final boolean isRecursive) { + return listFilesInDirWithFilter(getFileByPath(dirPath), filter, isRecursive); + } + + /** + * Return the files that satisfy the filter in directory. + * + * @param dir The directory. + * @param filter The filter. + * @param isRecursive True to traverse subdirectories, false otherwise. + * @return the files that satisfy the filter in directory + */ + public static List listFilesInDirWithFilter(final File dir, + final FileFilter filter, + final boolean isRecursive) { + if (!isDir(dir)) return null; + List list = new ArrayList<>(); + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (filter.accept(file)) { + list.add(file); + } + if (isRecursive && file.isDirectory()) { + //noinspection ConstantConditions + list.addAll(listFilesInDirWithFilter(file, filter, true)); + } + } + } + return list; + } + + /** + * Return the time that the file was last modified. + * + * @param filePath The path of file. + * @return the time that the file was last modified + */ + + public static long getFileLastModified(final String filePath) { + return getFileLastModified(getFileByPath(filePath)); + } + + /** + * Return the time that the file was last modified. + * + * @param file The file. + * @return the time that the file was last modified + */ + public static long getFileLastModified(final File file) { + if (file == null) return -1; + return file.lastModified(); + } + + /** + * Return the charset of file simply. + * + * @param filePath The path of file. + * @return the charset of file simply + */ + public static String getFileCharsetSimple(final String filePath) { + return getFileCharsetSimple(getFileByPath(filePath)); + } + + /** + * Return the charset of file simply. + * + * @param file The file. + * @return the charset of file simply + */ + public static String getFileCharsetSimple(final File file) { + int p = 0; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + p = (is.read() << 8) + is.read(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + switch (p) { + case 0xefbb: + return "UTF-8"; + case 0xfffe: + return "Unicode"; + case 0xfeff: + return "UTF-16BE"; + default: + return "GBK"; + } + } + + /** + * Return the number of lines of file. + * + * @param filePath The path of file. + * @return the number of lines of file + */ + public static int getFileLines(final String filePath) { + return getFileLines(getFileByPath(filePath)); + } + + /** + * Return the number of lines of file. + * + * @param file The file. + * @return the number of lines of file + */ + public static int getFileLines(final File file) { + int count = 1; + InputStream is = null; + try { + is = new BufferedInputStream(new FileInputStream(file)); + byte[] buffer = new byte[1024]; + int readChars; + if (LINE_SEP.endsWith("\n")) { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\n') ++count; + } + } + } else { + while ((readChars = is.read(buffer, 0, 1024)) != -1) { + for (int i = 0; i < readChars; ++i) { + if (buffer[i] == '\r') ++count; + } + } + } + } catch (IOException e) { + e.printStackTrace(); + } finally { + try { + if (is != null) { + is.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return count; + } + + /** + * Return the size of directory. + * + * @param dirPath The path of directory. + * @return the size of directory + */ + public static String getDirSize(final String dirPath) { + return getDirSize(getFileByPath(dirPath)); + } + + /** + * Return the size of directory. + * + * @param dir The directory. + * @return the size of directory + */ + public static String getDirSize(final File dir) { + long len = getDirLength(dir); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of file. + * + * @param filePath The path of file. + * @return the length of file + */ + public static String getFileSize(final String filePath) { + long len = getFileLength(filePath); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of file. + * + * @param file The file. + * @return the length of file + */ + public static String getFileSize(final File file) { + long len = getFileLength(file); + return len == -1 ? "" : byte2FitMemorySize(len); + } + + /** + * Return the length of directory. + * + * @param dirPath The path of directory. + * @return the length of directory + */ + public static long getDirLength(final String dirPath) { + return getDirLength(getFileByPath(dirPath)); + } + + /** + * Return the length of directory. + * + * @param dir The directory. + * @return the length of directory + */ + public static long getDirLength(final File dir) { + if (!isDir(dir)) return -1; + long len = 0; + File[] files = dir.listFiles(); + if (files != null && files.length != 0) { + for (File file : files) { + if (file.isDirectory()) { + len += getDirLength(file); + } else { + len += file.length(); + } + } + } + return len; + } + + /** + * Return the length of file. + * + * @param filePath The path of file. + * @return the length of file + */ + public static long getFileLength(final String filePath) { + boolean isURL = filePath.matches("[a-zA-z]+://[^\\s]*"); + if (isURL) { + try { + HttpURLConnection conn = (HttpURLConnection) new URL(filePath).openConnection(); + conn.setRequestProperty("Accept-Encoding", "identity"); + conn.connect(); + if (conn.getResponseCode() == 200) { + return conn.getContentLength(); + } + return -1; + } catch (IOException e) { + e.printStackTrace(); + } + } + return getFileLength(getFileByPath(filePath)); + } + + /** + * Return the length of file. + * + * @param file The file. + * @return the length of file + */ + public static long getFileLength(final File file) { + if (!isFile(file)) return -1; + return file.length(); + } + + /** + * Return the MD5 of file. + * + * @param filePath The path of file. + * @return the md5 of file + */ + public static String getFileMD5ToString(final String filePath) { + File file = isSpace(filePath) ? null : new File(filePath); + return getFileMD5ToString(file); + } + + /** + * Return the MD5 of file. + * + * @param file The file. + * @return the md5 of file + */ + public static String getFileMD5ToString(final File file) { + return bytes2HexString(getFileMD5(file)); + } + + /** + * Return the MD5 of file. + * + * @param filePath The path of file. + * @return the md5 of file + */ + public static byte[] getFileMD5(final String filePath) { + return getFileMD5(getFileByPath(filePath)); + } + + /** + * Return the MD5 of file. + * + * @param file The file. + * @return the md5 of file + */ + public static byte[] getFileMD5(final File file) { + if (file == null) return null; + DigestInputStream dis = null; + try { + FileInputStream fis = new FileInputStream(file); + MessageDigest md = MessageDigest.getInstance("MD5"); + dis = new DigestInputStream(fis, md); + byte[] buffer = new byte[1024 * 256]; + while (true) { + if (!(dis.read(buffer) > 0)) break; + } + md = dis.getMessageDigest(); + return md.digest(); + } catch (NoSuchAlgorithmException | IOException e) { + e.printStackTrace(); + } finally { + try { + if (dis != null) { + dis.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + return null; + } + + /** + * Return the file's path of directory. + * + * @param file The file. + * @return the file's path of directory + */ + public static String getDirName(final File file) { + if (file == null) return ""; + return getDirName(file.getAbsolutePath()); + } + + /** + * Return the file's path of directory. + * + * @param filePath The path of file. + * @return the file's path of directory + */ + public static String getDirName(final String filePath) { + if (isSpace(filePath)) return ""; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? "" : filePath.substring(0, lastSep + 1); + } + + /** + * Return the name of file. + * + * @param file The file. + * @return the name of file + */ + public static String getFileName(final File file) { + if (file == null) return ""; + return getFileName(file.getAbsolutePath()); + } + + /** + * Return the name of file. + * + * @param filePath The path of file. + * @return the name of file + */ + public static String getFileName(final String filePath) { + if (isSpace(filePath)) return ""; + int lastSep = filePath.lastIndexOf(File.separator); + return lastSep == -1 ? filePath : filePath.substring(lastSep + 1); + } + + /** + * Return the name of file without extension. + * + * @param file The file. + * @return the name of file without extension + */ + public static String getFileNameNoExtension(final File file) { + if (file == null) return ""; + return getFileNameNoExtension(file.getPath()); + } + + /** + * Return the name of file without extension. + * + * @param filePath The path of file. + * @return the name of file without extension + */ + public static String getFileNameNoExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastSep == -1) { + return (lastPoi == -1 ? filePath : filePath.substring(0, lastPoi)); + } + if (lastPoi == -1 || lastSep > lastPoi) { + return filePath.substring(lastSep + 1); + } + return filePath.substring(lastSep + 1, lastPoi); + } + + /** + * Return the extension of file. + * + * @param file The file. + * @return the extension of file + */ + public static String getFileExtension(final File file) { + if (file == null) return ""; + return getFileExtension(file.getPath()); + } + + /** + * Return the extension of file. + * + * @param filePath The path of file. + * @return the extension of file + */ + public static String getFileExtension(final String filePath) { + if (isSpace(filePath)) return ""; + int lastPoi = filePath.lastIndexOf('.'); + int lastSep = filePath.lastIndexOf(File.separator); + if (lastPoi == -1 || lastSep >= lastPoi) return ""; + return filePath.substring(lastPoi + 1); + } + + /////////////////////////////////////////////////////////////////////////// + // interface + /////////////////////////////////////////////////////////////////////////// + + public interface OnReplaceListener { + boolean onReplace(); + } + + /////////////////////////////////////////////////////////////////////////// + // other utils methods + /////////////////////////////////////////////////////////////////////////// + + private static final char HEX_DIGITS[] = + {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static String bytes2HexString(final byte[] bytes) { + if (bytes == null) return ""; + int len = bytes.length; + if (len <= 0) return ""; + char[] ret = new char[len << 1]; + for (int i = 0, j = 0; i < len; i++) { + ret[j++] = HEX_DIGITS[bytes[i] >> 4 & 0x0f]; + ret[j++] = HEX_DIGITS[bytes[i] & 0x0f]; + } + return new String(ret); + } + + private static String byte2FitMemorySize(final long byteNum) { + if (byteNum < 0) { + return "shouldn't be less than zero!"; + } else if (byteNum < 1024) { + return String.format(Locale.getDefault(), "%.3fB", (double) byteNum); + } else if (byteNum < 1048576) { + return String.format(Locale.getDefault(), "%.3fKB", (double) byteNum / 1024); + } else if (byteNum < 1073741824) { + return String.format(Locale.getDefault(), "%.3fMB", (double) byteNum / 1048576); + } else { + return String.format(Locale.getDefault(), "%.3fGB", (double) byteNum / 1073741824); + } + } + + private static boolean isSpace(final String s) { + if (s == null) return true; + for (int i = 0, len = s.length(); i < len; ++i) { + if (!Character.isWhitespace(s.charAt(i))) { + return false; + } + } + return true; + } + + private static boolean writeFileFromIS(final File file, + final InputStream is) { + OutputStream os = null; + try { + os = new BufferedOutputStream(new FileOutputStream(file)); + byte data[] = new byte[8192]; + int len; + while ((len = is.read(data, 0, 8192)) != -1) { + os.write(data, 0, len); + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + is.close(); + } catch (IOException e) { + e.printStackTrace(); + } + try { + if (os != null) { + os.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/KeyboardToolUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/KeyboardToolUtils.java new file mode 100644 index 00000000..10d198fa --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/KeyboardToolUtils.java @@ -0,0 +1,157 @@ +package com.bonait.bnframework.common.utils; + +import android.app.Activity; +import android.content.Context; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; + +/** + * Created by LY on 2019/3/28. + * 软键盘工具类 + */ +public class KeyboardToolUtils { + /** + * 避免输入法面板遮挡 + *

在manifest.xml中activity中设置

+ *

android:windowSoftInputMode="stateVisible|adjustResize"

+ */ + + /** + * 动态隐藏软键盘 + * + * @param activity activity + */ + public static void hideSoftInput(Activity activity) { + View view = activity.getWindow().peekDecorView(); + if (view != null) { + InputMethodManager inputManger = (InputMethodManager) activity + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManger != null) { + inputManger.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + } + + /** + * 点击隐藏软键盘 + * + * @param activity + * @param view + */ + public static void hideKeyboard(Activity activity, View view) { + InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null) { + imm.hideSoftInputFromWindow(view.getWindowToken(), 0); + } + } + + /** + * 动态隐藏软键盘 + * + * @param context 上下文 + * @param edit 输入框 + */ + public static void hideSoftInput(Context context, EditText edit) { + edit.clearFocus(); + InputMethodManager inputManger = (InputMethodManager) context + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManger != null) { + inputManger.hideSoftInputFromWindow(edit.getWindowToken(), 0); + } + } + + /** + * 点击屏幕空白区域隐藏软键盘(方法1) + *

在onTouch中处理,未获焦点则隐藏

+ *

参照以下注释代码

+ */ + public static void clickBlankArea2HideSoftInput0() { + /* + @Override + public boolean onTouchEvent (MotionEvent event){ + if (null != this.getCurrentFocus()) { + InputMethodManager mInputMethodManager = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); + return mInputMethodManager.hideSoftInputFromWindow(this.getCurrentFocus().getWindowToken(), 0); + } + return super.onTouchEvent(event); + } + */ + } + + /** + * 点击屏幕空白区域隐藏软键盘(方法2) + *

根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘

+ *

需重写dispatchTouchEvent

+ *

参照以下注释代码

+ */ + public static void clickBlankArea2HideSoftInput1() { + /* + @Override + public boolean dispatchTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_DOWN) { + View v = getCurrentFocus(); + if (isShouldHideKeyboard(v, ev)) { + hideKeyboard(v.getWindowToken()); + } + } + return super.dispatchTouchEvent(ev); + } + // 根据EditText所在坐标和用户点击的坐标相对比,来判断是否隐藏键盘 + private boolean isShouldHideKeyboard(View v, MotionEvent event) { + if (v != null && (v instanceof EditText)) { + int[] l = {0, 0}; + v.getLocationInWindow(l); + int left = l[0], + top = l[1], + bottom = top + v.getHeight(), + right = left + v.getWidth(); + return !(event.getX() > left && event.getX() < right + && event.getY() > top && event.getY() < bottom); + } + return false; + } + // 获取InputMethodManager,隐藏软键盘 + private void hideKeyboard(IBinder token) { + if (token != null) { + InputMethodManager im = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + im.hideSoftInputFromWindow(token, InputMethodManager.HIDE_NOT_ALWAYS); + } + } + */ + } + + /** + * 动态显示软键盘 + * + * @param context 上下文 + * @param edit 输入框 + */ + public static void showSoftInput(Context context, EditText edit) { + edit.setFocusable(true); + edit.setFocusableInTouchMode(true); + edit.requestFocus(); + InputMethodManager inputManager = (InputMethodManager) context + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManager != null) { + inputManager.showSoftInput(edit, 0); + } + } + + /** + * 切换键盘显示与否状态 + * + * @param context 上下文 + * @param edit 输入框 + */ + public static void toggleSoftInput(Context context, EditText edit) { + edit.setFocusable(true); + edit.setFocusableInTouchMode(true); + edit.requestFocus(); + InputMethodManager inputManager = (InputMethodManager) context + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (inputManager != null) { + inputManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/MediaFileUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/MediaFileUtils.java new file mode 100644 index 00000000..364cd4f0 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/MediaFileUtils.java @@ -0,0 +1,353 @@ +package com.bonait.bnframework.common.utils; + +import java.util.HashMap; +import java.util.Locale; + +/** + * Created by LY on 2019/1/24. + * 文件类型工具类 + */ +public class MediaFileUtils { + //音频文件类型 + public static final int FILE_TYPE_MP3 = 1; + public static final int FILE_TYPE_M4A = 2; + public static final int FILE_TYPE_WAV = 3; + public static final int FILE_TYPE_AMR = 4; + public static final int FILE_TYPE_AWB = 5; + public static final int FILE_TYPE_WMA = 6; + public static final int FILE_TYPE_OGG = 7; + public static final int FILE_TYPE_AAC = 8; + public static final int FILE_TYPE_MKA = 9; + public static final int FILE_TYPE_FLAC = 10; + private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3; + private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_FLAC; + + // MIDI文件类型 + public static final int FILE_TYPE_MID = 11; + public static final int FILE_TYPE_SMF = 12; + public static final int FILE_TYPE_IMY = 13; + private static final int FIRST_MIDI_FILE_TYPE = FILE_TYPE_MID; + private static final int LAST_MIDI_FILE_TYPE = FILE_TYPE_IMY; + + //视频文件类型 + public static final int FILE_TYPE_MP4 = 21; + public static final int FILE_TYPE_M4V = 22; + public static final int FILE_TYPE_3GPP = 23; + public static final int FILE_TYPE_3GPP2 = 24; + public static final int FILE_TYPE_WMV = 25; + public static final int FILE_TYPE_ASF = 26; + public static final int FILE_TYPE_MKV = 27; + public static final int FILE_TYPE_MP2TS = 28; + public static final int FILE_TYPE_AVI = 29; + public static final int FILE_TYPE_WEBM = 30; + private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4; + private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_WEBM; + + //更多视频文件类型 + public static final int FILE_TYPE_MP2PS = 200; + private static final int FIRST_VIDEO_FILE_TYPE2 = FILE_TYPE_MP2PS; + private static final int LAST_VIDEO_FILE_TYPE2 = FILE_TYPE_MP2PS; + + //图像文件类型 + public static final int FILE_TYPE_JPEG = 31; + public static final int FILE_TYPE_GIF = 32; + public static final int FILE_TYPE_PNG = 33; + public static final int FILE_TYPE_BMP = 34; + public static final int FILE_TYPE_WBMP = 35; + public static final int FILE_TYPE_WEBP = 36; + private static final int FIRST_IMAGE_FILE_TYPE = FILE_TYPE_JPEG; + private static final int LAST_IMAGE_FILE_TYPE = FILE_TYPE_WEBP; + + // Playlist file types + public static final int FILE_TYPE_M3U = 41; + public static final int FILE_TYPE_PLS = 42; + public static final int FILE_TYPE_WPL = 43; + public static final int FILE_TYPE_HTTPLIVE = 44; + private static final int FIRST_PLAYLIST_FILE_TYPE = FILE_TYPE_M3U; + private static final int LAST_PLAYLIST_FILE_TYPE = FILE_TYPE_HTTPLIVE; + // Drm文件类型 + public static final int FILE_TYPE_FL = 51; + private static final int FIRST_DRM_FILE_TYPE = FILE_TYPE_FL; + private static final int LAST_DRM_FILE_TYPE = FILE_TYPE_FL; + //其他流行的文件类型 + public static final int FILE_TYPE_TEXT = 100; + public static final int FILE_TYPE_HTML = 101; + public static final int FILE_TYPE_PDF = 102; + public static final int FILE_TYPE_XML = 103; + public static final int FILE_TYPE_MS_WORD = 104; + public static final int FILE_TYPE_MS_EXCEL = 105; + public static final int FILE_TYPE_MS_POWERPOINT = 106; + public static final int FILE_TYPE_MS_DOT = 108; + public static final int FILE_TYPE_WPS_DOCX = 109; + public static final int FILE_TYPE_WPS_DOTX = 110; + public static final int FILE_TYPE_WPS_XLSX = 111; + public static final int FILE_TYPE_WPS_XLTX = 112; + public static final int FILE_TYPE_WPS_PPTX = 113; + public static final int FILE_TYPE_WPS_POTX = 114; + public static final int FILE_TYPE_WPS_PPSX = 115; + private static final int FIRST_DOC_FILE_TYPE = FILE_TYPE_TEXT; + private static final int LAST_DOC_FILE_TYPE = FILE_TYPE_WPS_PPSX; + + public static final int FILE_TYPE_ZIP = 300; + public static final int FILE_TYPE_RAR = 301; + private static final int FIRST_COMPRESSION_FILE_TYPE = FILE_TYPE_ZIP; + private static final int LAST_COMPRESSION_FILE_TYPE = FILE_TYPE_RAR; + + + public static class MediaFileType { + public final int fileType; + public final String mimeType; + + MediaFileType(int fileType, String mimeType) { + this.fileType = fileType; + this.mimeType = mimeType; + } + } + + private static final HashMap sFileTypeMap + = new HashMap(); + private static final HashMap sMimeTypeMap + = new HashMap(); + + static void addFileType(String extension, int fileType, String mimeType) { + sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType)); + sMimeTypeMap.put(mimeType, fileType); + } + static { + addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg"); + addFileType("MPGA", FILE_TYPE_MP3, "audio/mpeg"); + addFileType("M4A", FILE_TYPE_M4A, "audio/mp4"); + addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav"); + addFileType("AMR", FILE_TYPE_AMR, "audio/amr"); + addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb"); + addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma"); + addFileType("OGG", FILE_TYPE_OGG, "audio/ogg"); + addFileType("OGG", FILE_TYPE_OGG, "application/ogg"); + addFileType("OGA", FILE_TYPE_OGG, "application/ogg"); + addFileType("AAC", FILE_TYPE_AAC, "audio/aac"); + addFileType("AAC", FILE_TYPE_AAC, "audio/aac-adts"); + addFileType("MKA", FILE_TYPE_MKA, "audio/x-matroska"); + + addFileType("MID", FILE_TYPE_MID, "audio/midi"); + addFileType("MIDI", FILE_TYPE_MID, "audio/midi"); + addFileType("XMF", FILE_TYPE_MID, "audio/midi"); + addFileType("RTTTL", FILE_TYPE_MID, "audio/midi"); + addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi"); + addFileType("IMY", FILE_TYPE_IMY, "audio/imelody"); + addFileType("RTX", FILE_TYPE_MID, "audio/midi"); + addFileType("OTA", FILE_TYPE_MID, "audio/midi"); + addFileType("MXMF", FILE_TYPE_MID, "audio/midi"); + + addFileType("MPEG", FILE_TYPE_MP4, "video/mpeg"); + addFileType("MPG", FILE_TYPE_MP4, "video/mpeg"); + addFileType("MP4", FILE_TYPE_MP4, "video/mp4"); + addFileType("M4V", FILE_TYPE_M4V, "video/mp4"); + addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp"); + addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp"); + addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2"); + addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2"); + addFileType("MKV", FILE_TYPE_MKV, "video/x-matroska"); + addFileType("WEBM", FILE_TYPE_WEBM, "video/webm"); + addFileType("TS", FILE_TYPE_MP2TS, "video/mp2ts"); + addFileType("AVI", FILE_TYPE_AVI, "video/avi"); + addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv"); + addFileType("ASF", FILE_TYPE_ASF, "video/x-ms-asf"); + + addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg"); + addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg"); + addFileType("GIF", FILE_TYPE_GIF, "image/gif"); + addFileType("PNG", FILE_TYPE_PNG, "image/png"); + addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp"); + addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp"); + addFileType("WEBP", FILE_TYPE_WEBP, "image/webp"); + + addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl"); + addFileType("M3U", FILE_TYPE_M3U, "application/x-mpegurl"); + addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls"); + addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl"); + addFileType("M3U8", FILE_TYPE_HTTPLIVE, "application/vnd.apple.mpegurl"); + addFileType("M3U8", FILE_TYPE_HTTPLIVE, "audio/mpegurl"); + addFileType("M3U8", FILE_TYPE_HTTPLIVE, "audio/x-mpegurl"); + addFileType("FL", FILE_TYPE_FL, "application/x-android-drm-fl"); + + addFileType("TXT", FILE_TYPE_TEXT, "text/plain"); + addFileType("HTM", FILE_TYPE_HTML, "text/html"); + addFileType("HTML", FILE_TYPE_HTML, "text/html"); + addFileType("PDF", FILE_TYPE_PDF, "application/pdf"); + addFileType("XML", FILE_TYPE_XML, "text/xml"); + addFileType("DOC", FILE_TYPE_MS_WORD, "application/msword"); + addFileType("DOT", FILE_TYPE_MS_DOT, "application/msword"); + addFileType("DOCX", FILE_TYPE_WPS_DOCX, "application/vnd.openxmlformats-officedocument.wordprocessingml.document"); + addFileType("DOTX", FILE_TYPE_WPS_DOTX, "application/vnd.openxmlformats-officedocument.wordprocessingml.template"); + addFileType("XLSX", FILE_TYPE_WPS_XLSX, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + addFileType("XLTX", FILE_TYPE_WPS_XLTX, "application/vnd.openxmlformats-officedocument.spreadsheetml.template"); + addFileType("PPTX", FILE_TYPE_WPS_PPTX, "application/vnd.openxmlformats-officedocument.presentationml.presentation"); + addFileType("POTX", FILE_TYPE_WPS_POTX, "application/vnd.openxmlformats-officedocument.presentationml.template"); + addFileType("PPSX", FILE_TYPE_WPS_PPSX, "application/vnd.openxmlformats-officedocument.presentationml.slideshow"); + + addFileType("XLS", FILE_TYPE_MS_EXCEL, "application/vnd.ms-excel"); + addFileType("PPT", FILE_TYPE_MS_POWERPOINT, "application/mspowerpoint"); + + addFileType("ZIP", FILE_TYPE_ZIP, "application/zip"); + addFileType("RAR", FILE_TYPE_RAR, "application/rar"); + + addFileType("FLAC", FILE_TYPE_FLAC, "audio/flac"); + addFileType("MPG", FILE_TYPE_MP2PS, "video/mp2p"); + addFileType("MPEG", FILE_TYPE_MP2PS, "video/mp2p"); + } + + /** + * check is audio or not + * @param fileType file type integer value + * @return if is audio type , return true;otherwise , return false + */ + public static boolean isAudioFileType(int fileType) { + return ((fileType >= FIRST_AUDIO_FILE_TYPE && + fileType <= LAST_AUDIO_FILE_TYPE) || + (fileType >= FIRST_MIDI_FILE_TYPE && + fileType <= LAST_MIDI_FILE_TYPE)); + } + + /** + * check is video or not + * @param fileType file type integer value + * @return if is video type , return true ; otherwise , return false + */ + public static boolean isVideoFileType(int fileType) { + return (fileType >= FIRST_VIDEO_FILE_TYPE && + fileType <= LAST_VIDEO_FILE_TYPE) + || (fileType >= FIRST_VIDEO_FILE_TYPE2 && + fileType <= LAST_VIDEO_FILE_TYPE2); + } + + /** + * check is image or not + * @param fileType file type integer value + * @return if is image type , return true ; otherwise , return false ; + */ + public static boolean isImageFileType(int fileType) { + return (fileType >= FIRST_IMAGE_FILE_TYPE && + fileType <= LAST_IMAGE_FILE_TYPE); + } + + /** + * check is playlist or not + * @param fileType file type integer value + * @return if is playlist type , return true ; otherwise , return false ; + */ + public static boolean isPlayListFileType(int fileType) { + return (fileType >= FIRST_PLAYLIST_FILE_TYPE && + fileType <= LAST_PLAYLIST_FILE_TYPE); + } + + /** + * check is drm or not + * @param fileType file type integer value + * @return if is drm type , return true ; otherwise , return false ; + */ + public static boolean isDrmFileType(int fileType) { + return (fileType >= FIRST_DRM_FILE_TYPE && + fileType <= LAST_DRM_FILE_TYPE); + } + + /** + * 检查是否是文本类型 + * */ + public static boolean isWordFileType(int fileType) { + return (fileType >= FIRST_DOC_FILE_TYPE && + fileType <= LAST_DOC_FILE_TYPE); + } + + /** + * 检查是否是压缩文件类型 + * */ + public static boolean isCompressionFileType(int fileType) { + return (fileType >= FIRST_COMPRESSION_FILE_TYPE && + fileType <= LAST_COMPRESSION_FILE_TYPE); + } + + /** + * get file's extension by file' path 按文件路径获取文件的扩展名 + * @param path file's path + * @return MediaFileType if the given file extension exist , or null + */ + public static MediaFileType getFileType(String path) { + int lastDot = path.lastIndexOf('.'); + if (lastDot < 0) + return null; + return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase(Locale.ROOT)); + } + + /** + * 按后缀名返回MediaFileType + * + * @param suffix 文件后缀名 + * + * @return MediaFileType if the given file extension exist , or null + * + * */ + public static MediaFileType getFileTypeBySuffix(String suffix) { + + // toUpperCase(Locale.ROOT) 转换为大写字符串 + return sFileTypeMap.get(suffix.toUpperCase(Locale.ROOT)); + } + + public static String getMimeTypeBySuffix(String suffix) { + MediaFileType mediaFileType = sFileTypeMap.get(suffix.toUpperCase(Locale.ROOT)); + return (mediaFileType == null ? null : mediaFileType.mimeType); + } + + /** + * check the given mime type is mime type media or not 检查给定的mime类型是否是mime类型的媒体 + * @param mimeType mime type to check + * @return if the given mime type is mime type media,return true ;otherwise , false + */ + public static boolean isMimeTypeMedia(String mimeType) { + int fileType = getFileTypeForMimeType(mimeType); + return isAudioFileType(fileType) || isVideoFileType(fileType) + || isImageFileType(fileType) || isPlayListFileType(fileType); + } + + /** + * generates a title based on file name + * @param path file's path + * + * @return file'name without extension + */ + public static String getFileTitle(String path) { + // extract file name after last slash + int lastSlash = path.lastIndexOf('/'); + if (lastSlash >= 0) { + lastSlash++; + if (lastSlash < path.length()) { + path = path.substring(lastSlash); + } + } + // truncate the file extension (if any) + int lastDot = path.lastIndexOf('.'); + if (lastDot > 0) { + path = path.substring(0, lastDot); + } + return path; + } + + /** + * get mine type integer value 根据mime类型获取mime类型整数值 + * @param mimeType mime type to get + * @return return mime type value if exist ;or zero value if not exist + */ + public static int getFileTypeForMimeType(String mimeType) { + Integer value = sMimeTypeMap.get(mimeType); + return (value == null ? 0 : value.intValue()); + } + + /** + * get file's mime type base on path 根据文件路径获取文件的mime类型 + * @param path file path + * @return return mime type if exist , or null + */ + public static String getMimeTypeForFile(String path) { + MediaFileType mediaFileType = getFileType(path); + return (mediaFileType == null ? null : mediaFileType.mimeType); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/NetworkUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/NetworkUtils.java new file mode 100644 index 00000000..aff7b5c2 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/NetworkUtils.java @@ -0,0 +1,109 @@ +package com.bonait.bnframework.common.utils; + +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.os.Build; + +/** + * Created by LY on 2019/1/4. + */ +public class NetworkUtils { + + /** + * 检测当的网络(WLAN、4G/3G/2G)状态,兼容Android 6.0以下 + * @param context Context + * @return true 表示网络可用 + */ + public static boolean isNetworkConnected(Context context) { + boolean result = false; + try { + ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (cm != null) { + NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); + if (capabilities != null) { + if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) { + result = true; + } else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) { + result = true; + } + } + } + } else { + if (cm != null) { + NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork != null) { + // connected to the internet + if (activeNetwork.getType() == ConnectivityManager.TYPE_WIFI) { + result = true; + } else if (activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE) { + result = true; + } + } + } + } + + } catch (Exception e) { + return false; + } + + return result; + } + + /** + * 判断是否是移动网络连接 + * */ + public static boolean isActiveNetworkMobile(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager != null) { + NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_MOBILE; + } + return false; + } + + /** + * 判断是否是wifi + * */ + public static boolean isActiveNetworkWifi(Context context) { + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivityManager != null) { + NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo(); + return networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI; + } + return false; + } + + /** + * @deprecated 请先使用 {@link NetworkUtils#isNetworkConnected(Context)} 方法 + * + * 检测当的网络(WLAN、4G/3G/2G)状态 + * + * @param context Context + * @return true 表示网络可用 + */ + @Deprecated + public static boolean checkNet(Context context) { + + try { + ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (connectivity != null) { + + NetworkInfo info = connectivity.getActiveNetworkInfo(); + if (info != null && info.isConnected()) { + + /*if (info.getState() == NetworkInfo.State.CONNECTED) { + return true; + }*/ + NetworkCapabilities networkCapabilities = connectivity.getNetworkCapabilities(connectivity.getActiveNetwork()); + return networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + } + } + } catch (Exception e) { + return false; + } + return false; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/PreferenceUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/PreferenceUtils.java new file mode 100644 index 00000000..004d1d25 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/PreferenceUtils.java @@ -0,0 +1,139 @@ +package com.bonait.bnframework.common.utils; + +import android.content.Context; +import android.content.SharedPreferences; + +/** + * Created by LY on 2019/3/21. + */ +public class PreferenceUtils { + private static SharedPreferences sharedPreferences; + + /** + * 初始化SharedPreferences + * */ + public static void initPreference(Context context,String name,int mode) { + if (sharedPreferences == null) { + sharedPreferences = context.getSharedPreferences(name, mode); + } + } + + /** + * 存String + * @param key 键 + * @param value 值 + */ + public static void setString(String key, String value) { + sharedPreferences.edit().putString(key,value).apply(); + } + + /** + * 取String + * @param key 键 + * @param devalue 默认值 + * @return String + */ + public static String getString(String key, String devalue) { + return sharedPreferences.getString(key,devalue); + } + + /** + * 存Float + * @param key 键 + * @param value 值 + */ + public static void setFloat(String key, float value) { + sharedPreferences.edit().putFloat(key,value).apply(); + } + + /** + * 取Float + * @param key 键 + * @param devalue 默认值 + * @return Float + */ + public static Float getFloat(String key, float devalue){ + return sharedPreferences.getFloat(key,devalue); + } + + /** + * 存boolean + * @param key 键 + * @param value 值 + */ + public static void setBoolean(String key, boolean value) { + sharedPreferences.edit().putBoolean(key,value).apply(); + } + + /** + * 取boolean + * @param key 键 + * @param defValue 默认值 + * @return Boolean + */ + public static boolean getBoolean(String key, boolean defValue) { + return sharedPreferences.getBoolean(key,defValue); + } + + /** + * 存int + * @param key 键 + * @param value 值 + */ + public static void setInt(String key, int value) { + sharedPreferences.edit().putInt(key,value).apply(); + } + + /** + * 存int + * @param key 键 + * @param value 值 + */ + public static void setInt(String key, Integer value) { + sharedPreferences.edit().putInt(key,(Integer) value).apply(); + } + + /** + * 取int + * @param key 键 + * @param defValue 默认值 + * @return Int + */ + public static int getInt(String key, int defValue) { + return sharedPreferences.getInt(key,defValue); + } + + /** + * 存Long + * @param key 键 + * @param value 值 + */ + public static void setLong(String key, int value) { + sharedPreferences.edit().putLong(key,value).apply(); + } + + /** + * 取Long + * @param key 键 + * @param defValue 默认值 + * @return Long + */ + public static Long getLong(String key, int defValue) { + return sharedPreferences.getLong(key,defValue); + } + + /** + * 清空一个 + * @param key 键 + */ + public static void remove(String key) { + sharedPreferences.edit().remove(key).apply(); + } + + /** + * 清空所有 + */ + public static void removeAll() { + sharedPreferences.edit().clear().apply(); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/ToastUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/ToastUtils.java new file mode 100644 index 00000000..ddec8579 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/ToastUtils.java @@ -0,0 +1,278 @@ +package com.bonait.bnframework.common.utils; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffColorFilter; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.NinePatchDrawable; +import android.support.annotation.CheckResult; +import android.support.annotation.ColorInt; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.MainApplication; + +/** + * Created by LY on 2019/3/19. + */ +public class ToastUtils { + @ColorInt + private static final int DEFAULT_TEXT_COLOR = Color.parseColor("#FFFFFF"); + + @ColorInt + private static final int ERROR_COLOR = Color.parseColor("#F44336"); //#FD4C5B + + @ColorInt + private static final int INFO_COLOR = Color.parseColor("#3F51B5"); + + @ColorInt + private static final int SUCCESS_COLOR = Color.parseColor("#388E3C"); + + @ColorInt + private static final int WARNING_COLOR = Color.parseColor("#FFA900"); + + private static final String TOAST_TYPEFACE = "sans-serif-condensed"; + + private static Toast currentToast; + + //***********************普通 使用ApplicationContext 方法*********************// + private static long mExitTime; + + public static void normal(@NonNull String message) { + normal(MainApplication.getContext(), message, Toast.LENGTH_SHORT, null, false).show(); + } + + public static void normal(@NonNull String message, Drawable icon) { + normal(MainApplication.getContext(), message, Toast.LENGTH_SHORT, icon, true).show(); + } + + public static void normal(@NonNull String message, int duration) { + normal(MainApplication.getContext(), message, duration, null, false).show(); + } + + public static void normal(@NonNull String message, int duration, Drawable icon) { + normal(MainApplication.getContext(), message, duration, icon, true).show(); + } + + public static Toast normal(@NonNull String message, int duration, Drawable icon, boolean withIcon) { + return custom(MainApplication.getContext(), message, icon, DEFAULT_TEXT_COLOR, duration, withIcon); + } + + public static void warning(@NonNull String message) { + warning(MainApplication.getContext(), message, Toast.LENGTH_SHORT, true).show(); + } + + public static void warning(@NonNull String message, int duration) { + warning(MainApplication.getContext(), message, duration, true).show(); + } + + public static Toast warning(@NonNull String message, int duration, boolean withIcon) { + return custom(MainApplication.getContext(), message, getDrawable(MainApplication.getContext(), R.mipmap.ic_error_outline_white_48dp), DEFAULT_TEXT_COLOR, WARNING_COLOR, duration, withIcon, true); + } + + public static void info(@NonNull String message) { + info(MainApplication.getContext(), message, Toast.LENGTH_SHORT, true).show(); + } + + public static void info(@NonNull String message, int duration) { + info(MainApplication.getContext(), message, duration, true).show(); + } + + public static Toast info(@NonNull String message, int duration, boolean withIcon) { + return custom(MainApplication.getContext(), message, getDrawable(MainApplication.getContext(), R.mipmap.ic_info_outline_white_48dp), DEFAULT_TEXT_COLOR, INFO_COLOR, duration, withIcon, true); + } + + public static void success(@NonNull String message) { + success(MainApplication.getContext(), message, Toast.LENGTH_SHORT, true).show(); + } + + public static void success(@NonNull String message, int duration) { + success(MainApplication.getContext(), message, duration, true).show(); + } + + public static Toast success(@NonNull String message, int duration, boolean withIcon) { + return custom(MainApplication.getContext(), message, getDrawable(MainApplication.getContext(), R.mipmap.ic_check_white_48dp), DEFAULT_TEXT_COLOR, SUCCESS_COLOR, duration, withIcon, true); + } + + public static void error(@NonNull String message) { + error(MainApplication.getContext(), message, Toast.LENGTH_SHORT, true).show(); + } + //===========================================使用ApplicationContext 方法========================= + + //*******************************************常规方法******************************************** + + public static void error(@NonNull String message, int duration) { + error(MainApplication.getContext(), message, duration, true).show(); + } + + public static Toast error(@NonNull String message, int duration, boolean withIcon) { + return custom(MainApplication.getContext(), message, getDrawable(MainApplication.getContext(), R.mipmap.ic_clear_white_48dp), DEFAULT_TEXT_COLOR, ERROR_COLOR, duration, withIcon, true); + } + + @CheckResult + public static Toast normal(@NonNull Context context, @NonNull String message) { + return normal(context, message, Toast.LENGTH_SHORT, null, false); + } + + @CheckResult + public static Toast normal(@NonNull Context context, @NonNull String message, Drawable icon) { + return normal(context, message, Toast.LENGTH_SHORT, icon, true); + } + + @CheckResult + public static Toast normal(@NonNull Context context, @NonNull String message, int duration) { + return normal(context, message, duration, null, false); + } + + @CheckResult + public static Toast normal(@NonNull Context context, @NonNull String message, int duration, Drawable icon) { + return normal(context, message, duration, icon, true); + } + + @CheckResult + public static Toast normal(@NonNull Context context, @NonNull String message, int duration, Drawable icon, boolean withIcon) { + return custom(context, message, icon, DEFAULT_TEXT_COLOR, duration, withIcon); + } + + @CheckResult + public static Toast warning(@NonNull Context context, @NonNull String message) { + return warning(context, message, Toast.LENGTH_SHORT, true); + } + + @CheckResult + public static Toast warning(@NonNull Context context, @NonNull String message, int duration) { + return warning(context, message, duration, true); + } + + @CheckResult + public static Toast warning(@NonNull Context context, @NonNull String message, int duration, boolean withIcon) { + return custom(context, message, getDrawable(context, R.mipmap.ic_error_outline_white_48dp), DEFAULT_TEXT_COLOR, WARNING_COLOR, duration, withIcon, true); + } + + @CheckResult + public static Toast info(@NonNull Context context, @NonNull String message) { + return info(context, message, Toast.LENGTH_SHORT, true); + } + + @CheckResult + public static Toast info(@NonNull Context context, @NonNull String message, int duration) { + return info(context, message, duration, true); + } + + @CheckResult + public static Toast info(@NonNull Context context, @NonNull String message, int duration, boolean withIcon) { + return custom(context, message, getDrawable(context, R.mipmap.ic_info_outline_white_48dp), DEFAULT_TEXT_COLOR, INFO_COLOR, duration, withIcon, true); + } + + @CheckResult + public static Toast success(@NonNull Context context, @NonNull String message) { + return success(context, message, Toast.LENGTH_SHORT, true); + } + + @CheckResult + public static Toast success(@NonNull Context context, @NonNull String message, int duration) { + return success(context, message, duration, true); + } + + @CheckResult + public static Toast success(@NonNull Context context, @NonNull String message, int duration, boolean withIcon) { + return custom(context, message, getDrawable(context, R.mipmap.ic_check_white_48dp), DEFAULT_TEXT_COLOR, SUCCESS_COLOR, duration, withIcon, true); + } + + @CheckResult + public static Toast error(@NonNull Context context, @NonNull String message) { + return error(context, message, Toast.LENGTH_SHORT, true); + } + + //===========================================常规方法============================================ + + @CheckResult + public static Toast error(@NonNull Context context, @NonNull String message, int duration) { + return error(context, message, duration, true); + } + + @CheckResult + public static Toast error(@NonNull Context context, @NonNull String message, int duration, boolean withIcon) { + return custom(context, message, getDrawable(context, R.mipmap.ic_clear_white_48dp), DEFAULT_TEXT_COLOR, ERROR_COLOR, duration, withIcon, true); + } + + @CheckResult + public static Toast custom(@NonNull Context context, @NonNull String message, Drawable icon, @ColorInt int textColor, int duration, boolean withIcon) { + return custom(context, message, icon, textColor, -1, duration, withIcon, false); + } + + //*******************************************内需方法******************************************** + + @CheckResult + public static Toast custom(@NonNull Context context, @NonNull String message, @DrawableRes int iconRes, @ColorInt int textColor, @ColorInt int tintColor, int duration, boolean withIcon, boolean shouldTint) { + return custom(context, message, getDrawable(context, iconRes), textColor, tintColor, duration, withIcon, shouldTint); + } + + @CheckResult + public static Toast custom(@NonNull Context context, @NonNull String message, Drawable icon, @ColorInt int textColor, @ColorInt int tintColor, int duration, boolean withIcon, boolean shouldTint) { + if (currentToast == null) { + currentToast = new Toast(context); + } + final View toastLayout = ((LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.toast_layout, null); + final ImageView toastIcon = toastLayout.findViewById(R.id.toast_icon); + final TextView toastTextView = toastLayout.findViewById(R.id.toast_text); + Drawable drawableFrame; + + if (shouldTint) { + drawableFrame = tint9PatchDrawableFrame(context, tintColor); + } else { + drawableFrame = getDrawable(context, R.mipmap.toast_frame); + } + setBackground(toastLayout, drawableFrame); + + if (withIcon) { + if (icon == null) { + throw new IllegalArgumentException("Avoid passing 'icon' as null if 'withIcon' is set to true"); + } + setBackground(toastIcon, icon); + } else { + toastIcon.setVisibility(View.GONE); + } + + toastTextView.setTextColor(textColor); + toastTextView.setText(message); + toastTextView.setTypeface(Typeface.create(TOAST_TYPEFACE, Typeface.NORMAL)); + + currentToast.setView(toastLayout); + currentToast.setDuration(duration); + return currentToast; + } + + public static final Drawable tint9PatchDrawableFrame(@NonNull Context context, @ColorInt int tintColor) { + final NinePatchDrawable toastDrawable = (NinePatchDrawable) getDrawable(context, R.mipmap.toast_frame); + toastDrawable.setColorFilter(new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)); + return toastDrawable; + } + //===========================================内需方法============================================ + + public static final void setBackground(@NonNull View view, Drawable drawable) { + view.setBackground(drawable); + } + + public static Drawable getDrawable(@NonNull Context context, @DrawableRes int id) { + return context.getDrawable(id); + } + + + public static boolean doubleClickExit() { + if ((System.currentTimeMillis() - mExitTime) > 2000) { + ToastUtils.normal("再按一次退出"); + mExitTime = System.currentTimeMillis(); + return false; + } + return true; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/UpdateAppUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/UpdateAppUtils.java new file mode 100644 index 00000000..e80e6c8b --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/UpdateAppUtils.java @@ -0,0 +1,148 @@ +package com.bonait.bnframework.common.utils; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.support.v4.content.FileProvider; + +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.http.callback.files.FileProgressDialogCallBack; +import com.bonait.bnframework.common.http.callback.json.JsonDialogCallback; +import com.bonait.bnframework.modules.mine.model.UpdateAppPo; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.model.Response; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +import java.io.File; + +/** + * Created by LY on 2019/4/1. + */ +public class UpdateAppUtils { + + /** + * apk下载地址 + */ + private static String downloadUrl = ""; + + /** + * 获取服务器apk下载地址ID + */ + private static String serviceApkId = ""; + /** + * 当前版本号 + */ + private static int myVersionCode = 0; + /** + * 服务器的版本号码 + */ + private static int serviceVersionCode = 0; + /** + * 服务器的版本号名称 + */ + private static String serviceVersionName = ""; + /** + * 更新说明 + */ + private static String description = ""; + + /** + * 更新APP版本入口 + */ + public static void updateApp(Context context) { + //获取当前app版本号 + myVersionCode = AppUtils.getVersionCode(context); + //获取json转成Gson,并获取版本等信息 + doPost(context); + } + + /** + * 请求后台服务器,检查apk版本 + */ + private static void doPost(final Context context) { + String getNewVersionUrl = Constants.SERVICE_IP + "/iandroid/appVersionAction!getNewVersion.do"; + OkGo.post(getNewVersionUrl) + .tag(context) + .execute(new JsonDialogCallback(context) { + @Override + public void onSuccess(Response response) { + UpdateAppPo updateAppPo = response.body(); + if (updateAppPo != null) { + serviceVersionCode = updateAppPo.getVersion(); + description = updateAppPo.getDescription(); + serviceApkId = updateAppPo.getApkId(); + //获取apk下载地址 + String url = Constants.SERVICE_IP + "/file-download?fileId="; + downloadUrl = url + serviceApkId; + // 判断Apk是否是最新版本 + if (myVersionCode < serviceVersionCode) { + showUpdateDialog(context); + } else { + ToastUtils.info("当前版本已是最新版本"); + } + + } + } + }); + } + + /** + * 弹出下载对话框,里面有一些更新版本的信息 + */ + private static void showUpdateDialog(final Context context) { + new QMUIDialog.MessageDialogBuilder(context) + .setCancelable(false) + .setTitle("发现新版本") + .setMessage(description) + .addAction("取消", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + dialog.dismiss(); + } + }) + .addAction("去更新", new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + downloadApk(context); + dialog.dismiss(); + } + }) + .create(AlertDialogUtils.mCurrentDialogStyle) + .show(); + } + + private static void downloadApk(final Context context) { + OkGo.get(downloadUrl) + .tag(context) + .execute(new FileProgressDialogCallBack(context) { + @Override + public void onSuccess(Response response) { + File file = response.body(); + String absolutePath = file.getAbsolutePath(); + installApk(context, absolutePath); + } + }); + } + + /** + * 跳转apk安装界面 + */ + public static void installApk(Context context, String filePath) { + Intent i = new Intent(Intent.ACTION_VIEW); + File file = new File(filePath); + if (file.length() > 0 && file.exists() && file.isFile()) { + //判断是否是AndroidN以及更高的版本 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileProvider", file); + i.setDataAndType(contentUri, "application/vnd.android.package-archive"); + } else { + i.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + } + context.startActivity(i); + } + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/utils/UriUtils.java b/app/src/main/java/com/bonait/bnframework/common/utils/UriUtils.java new file mode 100644 index 00000000..04cab253 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/utils/UriUtils.java @@ -0,0 +1,195 @@ +package com.bonait.bnframework.common.utils; + +import android.content.ContentUris; +import android.content.Context; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; +import android.support.v4.content.FileProvider; + +import java.io.File; + +/** + * Created by LY on 2019/1/29. + */ +public class UriUtils { + private UriUtils() { + } + + /** + * 根据file文件获取该文件的uri,适配Android7.0以下版本 + * + * @param mContext 上下文 + * @param file 需要获取uri的文件 + * + * @return 该文件的uri + * */ + public static Uri getUriForFile(Context mContext, File file) { + Uri fileUri = null; + // Android 版本判断 + if (Build.VERSION.SDK_INT >= 24) { + fileUri = getUriForFile24(mContext, file); + } else { + fileUri = Uri.fromFile(file); + } + return fileUri; + } + + /** + * 根据file文件获取该文件的uri,Android7.0以上可直接调用 + * + * @param mContext 上下文 + * @param file 需要获取uri的文件 + * + * @return 该文件的uri + * */ + public static Uri getUriForFile24(Context mContext, File file) { + return FileProvider.getUriForFile(mContext, mContext.getPackageName() + ".fileProvider", file); + } + + /** + * intent 设置uri和type + * */ + public static void setIntentDataAndType(Context mContext, + Intent intent, + String type, + File file, + boolean writeAble) { + if (Build.VERSION.SDK_INT >= 24) { + intent.setDataAndType(getUriForFile(mContext, file), type); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + if (writeAble) { + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + } + } else { + intent.setDataAndType(Uri.fromFile(file), type); + } + } + + /** + * 根据uri获取path + * */ + public static String getFilePathByUri(final Context context, final Uri uri) { + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[] { + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, + null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } finally { + if (cursor != null) + cursor.close(); + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/view/ClearEditTextView.java b/app/src/main/java/com/bonait/bnframework/common/view/ClearEditTextView.java new file mode 100644 index 00000000..1e985c69 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/view/ClearEditTextView.java @@ -0,0 +1,163 @@ +package com.bonait.bnframework.common.view; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.support.v7.widget.AppCompatEditText; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.AttributeSet; +import android.view.MotionEvent; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.CycleInterpolator; +import android.view.animation.TranslateAnimation; + +import com.bonait.bnframework.R; + +/** + * Created by LY on 2019/4/24. + * + */ +public class ClearEditTextView extends AppCompatEditText implements View.OnFocusChangeListener, TextWatcher { + /** + * 删除按钮的引用 + */ + private Drawable mClearDrawable; + /** + * 控件是否有焦点 + */ + private boolean hasFocus; + + public ClearEditTextView(Context context) { + this(context,null); + // super(context); + // this.context = context; + // init(); + } + public ClearEditTextView(Context context, AttributeSet attrs){ + //这里构造方法也很重要,不加这个很多属性不能再XML里面定义 + this(context, attrs, android.R.attr.editTextStyle); + } + + public ClearEditTextView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + private void init() { + + //获取EditText的DrawableRight,假如没有设置我们就使用默认的图片 + mClearDrawable = getCompoundDrawables()[2]; + if (mClearDrawable == null) { + mClearDrawable = getResources().getDrawable(R.drawable.delete_selector,null); + } + mClearDrawable.setBounds(0, 0, mClearDrawable.getIntrinsicWidth(), mClearDrawable.getIntrinsicHeight()); + //默认设置隐藏图标 + setClearIconVisible(false); + //设置焦点改变的监听 + setOnFocusChangeListener(this); + //设置输入框里面内容发生改变的监听 + addTextChangedListener(this); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(MotionEvent event) { + + if (mClearDrawable != null && event.getAction() == MotionEvent.ACTION_UP) { + int x = (int) event.getX(); + //判断触摸点是否在水平范围内 + boolean isInnerWidth = (x > (getWidth() - getTotalPaddingRight())) && + (x < (getWidth() - getPaddingRight())); + //获取删除图标的边界,返回一个Rect对象 + Rect rect = mClearDrawable.getBounds(); + //获取删除图标的高度 + int height = rect.height(); + int y = (int) event.getY(); + //计算图标底部到控件底部的距离 + int distance = (getHeight() - height) / 2; + //判断触摸点是否在竖直范围内(可能会有点误差) + //触摸点的纵坐标在distance到(distance+图标自身的高度)之内,则视为点中删除图标 + boolean isInnerHeight = (y > distance) && (y < (distance + height)); + if (isInnerHeight && isInnerWidth) { + this.setText(""); + } + } + return super.onTouchEvent(event); + } + + /** + * 设置清除图标的显示与隐藏,调用setCompoundDrawables为EditText绘制上去 + * + * @param visible 图标是否隐藏 + */ + private void setClearIconVisible(boolean visible) { + Drawable right = visible ? mClearDrawable : null; + setCompoundDrawables(getCompoundDrawables()[0], getCompoundDrawables()[1], + right, getCompoundDrawables()[3]); + } + + /** + * 当ClearEditText焦点发生变化的时候,判断里面字符串长度设置清除图标的显示与隐藏 + */ + @Override + public void onFocusChange(View v, boolean hasFocus) { + this.hasFocus = hasFocus; + if (hasFocus) { + setClearIconVisible(getText().length() > 0); + } else { + setClearIconVisible(false); + } + } + + /*@Override + protected void onSelectionChanged(int selStart, int selEnd) { + super.onSelectionChanged(selStart, selEnd); + //光标首次获取焦点是在最后面,之后操作就是按照点击的位置移动光标 + if (isEnabled() && hasFocus() && hasFocusable()) { + setSelection(selEnd); + } else { + setSelection(getText().length()); + } + }*/ + + /** + * 当输入框里面内容发生变化的时候回调的方法 + */ + @Override + public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { + if (hasFocus) { + setClearIconVisible(text.length() > 0); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void afterTextChanged(Editable s) { + + } + + /** + * 设置晃动动画 + */ + public void setShakeAnimation() { + this.setAnimation(shakeAnimation(5)); + } + + /** + * 晃动动画 + * + * @param counts 1秒钟晃动多少下 + * @return 动画 + */ + public static Animation shakeAnimation(int counts) { + Animation translateAnimation = new TranslateAnimation(0, 10, 0, 0); + translateAnimation.setInterpolator(new CycleInterpolator(counts)); + translateAnimation.setDuration(1000); + return translateAnimation; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/common/view/QMAutoDialogBuilderView.java b/app/src/main/java/com/bonait/bnframework/common/view/QMAutoDialogBuilderView.java new file mode 100644 index 00000000..2b7eb837 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/common/view/QMAutoDialogBuilderView.java @@ -0,0 +1,60 @@ +package com.bonait.bnframework.common.view; + +import android.content.Context; +import android.support.v4.content.ContextCompat; +import android.view.View; +import android.view.ViewGroup; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import com.bonait.bnframework.R; +import com.qmuiteam.qmui.util.QMUIDisplayHelper; +import com.qmuiteam.qmui.util.QMUIResHelper; +import com.qmuiteam.qmui.util.QMUIViewHelper; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; + +/** + * Created by LY on 2019/3/25. + */ +public class QMAutoDialogBuilderView extends QMUIDialog.AutoResizeDialogBuilder { + + private Context mContext; + private EditText mEditText; + + public QMAutoDialogBuilderView(Context context) { + super(context); + mContext = context; + } + + public EditText getEditText() { + return mEditText; + } + + @Override + public View onBuildContent(QMUIDialog dialog, ScrollView parent) { + LinearLayout layout = new LinearLayout(mContext); + layout.setOrientation(LinearLayout.VERTICAL); + layout.setLayoutParams(new ScrollView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + int padding = QMUIDisplayHelper.dp2px(mContext, 20); + layout.setPadding(padding, padding, padding, padding); + mEditText = new EditText(mContext); + QMUIViewHelper.setBackgroundKeepingPadding(mEditText, QMUIResHelper.getAttrDrawable(mContext, R.attr.qmui_list_item_bg_with_border_bottom)); + mEditText.setHint("输入框"); + LinearLayout.LayoutParams editTextLP = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, QMUIDisplayHelper.dpToPx(50)); + editTextLP.bottomMargin = QMUIDisplayHelper.dp2px(mContext, 15); + mEditText.setLayoutParams(editTextLP); + layout.addView(mEditText); + TextView textView = new TextView(mContext); + textView.setLineSpacing(QMUIDisplayHelper.dp2px(mContext, 4), 1.0f); + textView.setText("观察聚焦输入框后,键盘升起降下时 dialog 的高度自适应变化。\n\n" + + "QMUI Android 的设计目的是用于辅助快速搭建一个具备基本设计还原效果的 Android 项目," + + "同时利用自身提供的丰富控件及兼容处理,让开发者能专注于业务需求而无需耗费精力在基础代码的设计上。" + + "不管是新项目的创建,或是已有项目的维护,均可使开发效率和项目质量得到大幅度提升。"); + textView.setTextColor(ContextCompat.getColor(mContext, R.color.app_color_description)); + textView.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); + layout.addView(textView); + return layout; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/manager/ActivityLifecycleManager.java b/app/src/main/java/com/bonait/bnframework/manager/ActivityLifecycleManager.java new file mode 100644 index 00000000..c1a7a016 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/manager/ActivityLifecycleManager.java @@ -0,0 +1,232 @@ +package com.bonait.bnframework.manager; + +import android.app.Activity; +import android.app.Application; +import android.content.pm.ActivityInfo; +import android.os.Bundle; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +/** + * Created by LY on 2019/4/3. + */ +public class ActivityLifecycleManager implements Application.ActivityLifecycleCallbacks { + + /** + * 维护Activity 的list + */ + private static List activityList = Collections.synchronizedList(new LinkedList()); + //private List activityList = new LinkedList(); + + + /*单例模式静态内部类*/ + private static class SingletonHolder{ + private static ActivityLifecycleManager instance = new ActivityLifecycleManager(); + } + private ActivityLifecycleManager(){ + } + + public static ActivityLifecycleManager get(){ + return SingletonHolder.instance; + } + + /** + * 开启ActivityLifecycleCallbacks接口回调 + */ + public void init(Application application){ + application.registerActivityLifecycleCallbacks(get()); + } + + + + /** + * @param activity 作用说明 :添加一个activity到管理里 + */ + public void pushActivity(Activity activity) { + activityList.add(activity); + } + + /** + * @param activity 作用说明 :删除一个activity在管理里 + */ + public void popActivity(Activity activity) { + activityList.remove(activity); + } + + /** + * get current Activity 获取当前Activity(栈中最后一个压入的) + */ + public Activity currentActivity() { + if (activityList == null|| activityList.isEmpty()) { + return null; + } + return activityList.get(activityList.size()-1); + } + + /** + * 结束当前Activity(栈中最后一个压入的) + */ + public void finishCurrentActivity() { + if (activityList == null|| activityList.isEmpty()) { + return; + } + Activity activity = activityList.get(activityList.size()-1); + finishActivity(activity); + } + + /** + * 结束指定的Activity + */ + public void finishActivity(Activity activity) { + if (activityList == null|| activityList.isEmpty()) { + return; + } + if (activity != null) { + activityList.remove(activity); + activity.finish(); + activity = null; + } + } + + /** + * 结束指定类名的Activity + */ + public void finishActivity(Class cls) { + if (activityList == null|| activityList.isEmpty()) { + return; + } + for (Activity activity : activityList) { + if (activity.getClass().equals(cls)) { + finishActivity(activity); + } + } + } + + /** + * 按照指定类名找到activity + * + */ + public Activity findActivity(Class cls) { + Activity targetActivity = null; + if (activityList != null) { + for (Activity activity : activityList) { + if (activity.getClass().equals(cls)) { + targetActivity = activity; + break; + } + } + } + return targetActivity; + } + + /** + * @return 作用说明 :获取当前最顶部activity的实例 + */ + public Activity getTopActivity() { + Activity mBaseActivity; + synchronized (activityList) { + final int size = activityList.size() - 1; + if (size < 0) { + return null; + } + mBaseActivity = activityList.get(size); + } + return mBaseActivity; + + } + + /** + * @return 作用说明 :获取当前最顶部的acitivity 名字 + */ + public String getTopActivityName() { + Activity mBaseActivity; + synchronized (activityList) { + final int size = activityList.size() - 1; + if (size < 0) { + return null; + } + mBaseActivity = activityList.get(size); + } + return mBaseActivity.getClass().getName(); + } + + /** + * 结束所有Activity + */ + public void finishAllActivity() { + if (activityList == null) { + return; + } + for (Activity activity : activityList) { + activity.finish(); + } + activityList.clear(); + } + + /** + * 退出应用程序 + */ + public void appExit() { + try { + finishAllActivity(); + System.exit(0); + } catch (Exception e) { + e.getStackTrace(); + } + } + + + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + /* + * 监听到 Activity创建事件 将该 Activity 加入list + */ + pushActivity(activity); + // 设置禁止随屏幕旋转界面 + activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } + + @Override + public void onActivityStarted(Activity activity) { + + } + + @Override + public void onActivityResumed(Activity activity) { + + } + + @Override + public void onActivityPaused(Activity activity) { + + } + + @Override + public void onActivityStopped(Activity activity) { + + } + + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) { + + } + + @Override + public void onActivityDestroyed(Activity activity) { + if (null==activityList||activityList.isEmpty()) { + return; + } + if (activityList.contains(activity)) { + /* + * 监听到 Activity销毁事件 将该Activity 从list中移除 + */ + popActivity(activity); + } + + //横竖屏切换或配置改变时, Activity 会被重新创建实例, 但 Bundle 中的基础数据会被保存下来,移除该数据是为了保证重新创建的实例可以正常工作 + // 暂时没用到保存toolbar + //activity.getIntent().removeExtra("isInitToolbar"); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java b/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java new file mode 100644 index 00000000..211f333c --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigation2Activity.java @@ -0,0 +1,129 @@ +package com.bonait.bnframework.modules.home.activity; + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.design.widget.BottomNavigationView; +import android.support.v4.view.ViewPager; +import android.view.KeyEvent; +import android.view.MenuItem; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.common.base.BaseActivity; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.bonait.bnframework.modules.home.fragment.Home1Fragment; +import com.bonait.bnframework.modules.home.fragment.Home2Fragment; +import com.bonait.bnframework.modules.home.adapter.FragmentAdapter; +import com.bonait.bnframework.modules.mine.fragment.MyFragment; +import com.lzy.okgo.OkGo; +import com.qmuiteam.qmui.widget.QMUIViewPager; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class BottomNavigation2Activity extends BaseActivity { + + + @BindView(R.id.navigation) + BottomNavigationView bottomNavigationView; + @BindView(R.id.viewpager) + QMUIViewPager viewPager; + + private MenuItem menuItem; + private long exitTime = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bottom_navigation2); + ButterKnife.bind(this); + + initFragment(); + viewPager.addOnPageChangeListener(pageChangeListener); + // 设置viewPager缓存多少个fragment + viewPager.setOffscreenPageLimit(3); + bottomNavigationView.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener); + } + + /** + * viewPager里添加fragment + */ + private void initFragment() { + FragmentAdapter fragmentAdapter = new FragmentAdapter(getSupportFragmentManager()); + fragmentAdapter.addFragment(new Home1Fragment()); + fragmentAdapter.addFragment(new Home2Fragment()); + fragmentAdapter.addFragment(new MyFragment()); + viewPager.setAdapter(fragmentAdapter); + } + + //-------------------------配置viewPager与fragment关联----------------------------// + + /** + * 配置bottom底部菜单栏监听器,手指点击底部菜单监听 + */ + private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener + = new BottomNavigationView.OnNavigationItemSelectedListener() { + + @Override + public boolean onNavigationItemSelected(@NonNull MenuItem item) { + switch (item.getItemId()) { + case R.id.bottom_navigation_1: + viewPager.setCurrentItem(0); + return true; + case R.id.bottom_navigation_2: + viewPager.setCurrentItem(1); + return true; + case R.id.bottom_navigation_3: + viewPager.setCurrentItem(2); + return true; + } + return false; + } + }; + + + /** + * 配置ViewPager监听器,手指滑动监听 + */ + private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + MenuItem menuItem = bottomNavigationView.getMenu().getItem(position); + } + + @Override + public void onPageSelected(int position) { + menuItem = bottomNavigationView.getMenu().getItem(position); + menuItem.setChecked(true); + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }; + + @Override + protected boolean canDragBack() { + return viewPager.getCurrentItem() == 0; + } + + /** + * 重写返回键,实现双击退出程序效果 + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (System.currentTimeMillis() - exitTime > 2000) { + ToastUtils.normal("再按一次退出程序"); + exitTime = System.currentTimeMillis(); + } else { + OkGo.getInstance().cancelAll(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + ActivityLifecycleManager.get().appExit(); + } + return true; + } + return super.onKeyDown(keyCode, event); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigationActivity.java b/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigationActivity.java new file mode 100644 index 00000000..be538b53 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/home/activity/BottomNavigationActivity.java @@ -0,0 +1,243 @@ +package com.bonait.bnframework.modules.home.activity; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewPager; +import android.view.KeyEvent; +import android.view.View; +import android.view.ViewGroup; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.common.base.BaseActivity; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.bonait.bnframework.modules.home.fragment.Home1Fragment; +import com.bonait.bnframework.modules.home.fragment.Home2Fragment; +import com.bonait.bnframework.modules.mine.fragment.MyFragment; +import com.lzy.okgo.OkGo; +import com.qmuiteam.qmui.util.QMUIResHelper; +import com.qmuiteam.qmui.widget.QMUIPagerAdapter; +import com.qmuiteam.qmui.widget.QMUITabSegment; +import com.qmuiteam.qmui.widget.QMUIViewPager; + +import butterknife.BindView; +import butterknife.ButterKnife; + +public class BottomNavigationActivity extends BaseActivity { + + @BindView(R.id.main_view_pager) + QMUIViewPager mViewPager; + @BindView(R.id.main_tabs) + QMUITabSegment mTabSegment; + + private Context context; + private long exitTime = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_bottom_navigation); + ButterKnife.bind(this); + + context = this; + + // 初始化tabs + initTabs(); + // 初始化viewPager,并填充fragment + initPagers(); + } + + /** + * 初始化tab + * */ + private void initTabs() { + int normalColor = QMUIResHelper.getAttrColor(context, R.attr.qmui_config_color_gray_6); + int selectColor = QMUIResHelper.getAttrColor(context, R.attr.qmui_config_color_blue); + mTabSegment.setDefaultNormalColor(normalColor); + mTabSegment.setDefaultSelectedColor(selectColor); + + QMUITabSegment.Tab home = new QMUITabSegment.Tab( + ContextCompat.getDrawable(context, R.mipmap.icon_tabbar_component), + ContextCompat.getDrawable(context, R.mipmap.icon_tabbar_component_selected), + "主页", false + ); + + QMUITabSegment.Tab Lab = new QMUITabSegment.Tab( + ContextCompat.getDrawable(context, R.mipmap.icon_tabbar_util), + ContextCompat.getDrawable(context, R.mipmap.icon_tabbar_util_selected), + "功能", false + ); + QMUITabSegment.Tab My = new QMUITabSegment.Tab( + ContextCompat.getDrawable(context, R.mipmap.icon_tabbar_lab), + ContextCompat.getDrawable(context, R.mipmap.icon_tabbar_lab_selected), + "我的", false + ); + mTabSegment.addTab(home) + .addTab(Lab) + .addTab(My); + } + + /** + * 初始化viewPager并添加fragment + * */ + private void initPagers() { + QMUIPagerAdapter pagerAdapter = new QMUIPagerAdapter() { + private FragmentTransaction mCurrentTransaction; + private Fragment mCurrentPrimaryItem = null; + + @Override + public boolean isViewFromObject(View view, Object object) { + return view == ((Fragment) object).getView(); + } + + @Override + public int getCount() { + return 3; + } + + @SuppressLint("CommitTransaction") + @Override + protected Object hydrate(ViewGroup container, int position) { + String name = makeFragmentName(container.getId(), position); + if (mCurrentTransaction == null) { + mCurrentTransaction = getSupportFragmentManager() + .beginTransaction(); + } + Fragment fragment = getSupportFragmentManager().findFragmentByTag(name); + if(fragment != null){ + return fragment; + } + switch (position) { + case 0: + return new Home1Fragment(); + case 1: + return new Home2Fragment(); + case 2: + return new MyFragment(); + default: + return new Home1Fragment(); + } + } + + @SuppressLint("CommitTransaction") + @Override + protected void populate(ViewGroup container, Object item, int position) { + String name = makeFragmentName(container.getId(), position); + if (mCurrentTransaction == null) { + mCurrentTransaction = getSupportFragmentManager() + .beginTransaction(); + } + Fragment fragment = getSupportFragmentManager().findFragmentByTag(name); + if (fragment != null) { + mCurrentTransaction.attach(fragment); + if(fragment.getView() != null && fragment.getView().getWidth() == 0){ + fragment.getView().requestLayout(); + } + } else { + fragment = (Fragment) item; + mCurrentTransaction.add(container.getId(), fragment, name); + } + if (fragment != mCurrentPrimaryItem) { + fragment.setMenuVisibility(false); + fragment.setUserVisibleHint(false); + } + } + + @SuppressLint("CommitTransaction") + @Override + protected void destroy(ViewGroup container, int position, Object object) { + if (mCurrentTransaction == null) { + mCurrentTransaction = getSupportFragmentManager() + .beginTransaction(); + } + mCurrentTransaction.detach((Fragment) object); + } + + @Override + public void startUpdate(ViewGroup container) { + if (container.getId() == View.NO_ID) { + throw new IllegalStateException("ViewPager with adapter " + this + + " requires a view id"); + } + } + + @Override + public void finishUpdate(ViewGroup container) { + if (mCurrentTransaction != null) { + mCurrentTransaction.commitNowAllowingStateLoss(); + mCurrentTransaction = null; + } + } + + @Override + public void setPrimaryItem(ViewGroup container, int position, Object object) { + Fragment fragment = (Fragment) object; + if (fragment != mCurrentPrimaryItem) { + if (mCurrentPrimaryItem != null) { + mCurrentPrimaryItem.setMenuVisibility(false); + mCurrentPrimaryItem.setUserVisibleHint(false); + } + if (fragment != null) { + fragment.setMenuVisibility(true); + fragment.setUserVisibleHint(true); + } + mCurrentPrimaryItem = fragment; + } + } + + private String makeFragmentName(int viewId, long id) { + return BottomNavigationActivity.class.getSimpleName() + ":" + viewId + ":" + id; + } + }; + mViewPager.setAdapter(pagerAdapter); + mViewPager.setOffscreenPageLimit(3); + mViewPager.addOnPageChangeListener(pageChangeListener); + mTabSegment.setupWithViewPager(mViewPager,false); + } + + /** + * 配置ViewPager监听器,手指滑动监听 + */ + private ViewPager.OnPageChangeListener pageChangeListener = new ViewPager.OnPageChangeListener() { + @Override + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + } + + @Override + public void onPageScrollStateChanged(int state) { + + } + }; + + @Override + protected boolean canDragBack() { + return mViewPager.getCurrentItem() == 0; + } + + /** + * 重写返回键,实现双击退出程序效果 + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (System.currentTimeMillis() - exitTime > 2000) { + ToastUtils.normal("再按一次退出程序"); + exitTime = System.currentTimeMillis(); + } else { + OkGo.getInstance().cancelAll(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + ActivityLifecycleManager.get().appExit(); + } + return true; + } + return super.onKeyDown(keyCode, event); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/adapter/FragmentAdapter.java b/app/src/main/java/com/bonait/bnframework/modules/home/adapter/FragmentAdapter.java new file mode 100644 index 00000000..74139c9e --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/home/adapter/FragmentAdapter.java @@ -0,0 +1,42 @@ +package com.bonait.bnframework.modules.home.adapter; + +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentPagerAdapter; +import android.view.ViewGroup; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by LY on 2019/3/26. + */ +public class FragmentAdapter extends FragmentPagerAdapter { + private List fragments = new ArrayList<>(); + + public FragmentAdapter(FragmentManager fm) { + super(fm); + + } + + @Override + public Fragment getItem(int position) { + return fragments.get(position); + } + + @Override + public int getCount() { + return fragments.size(); + } + + public void addFragment(Fragment fragment) { + fragments.add(fragment); + } + + @NonNull + @Override + public Object instantiateItem(@NonNull ViewGroup container, int position) { + return super.instantiateItem(container, position); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java new file mode 100644 index 00000000..7402c04c --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home1Fragment.java @@ -0,0 +1,83 @@ +package com.bonait.bnframework.modules.home.fragment; + + +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.v4.app.Fragment; +import android.support.v4.content.ContextCompat; +import android.view.LayoutInflater; +import android.view.View; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.common.base.BaseFragment; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.orhanobut.logger.Logger; +import com.qmuiteam.qmui.widget.QMUITopBar; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * A simple {@link Fragment} subclass. + */ +public class Home1Fragment extends BaseFragment { + + @BindView(R.id.topbar) + QMUITopBar mTopBar; + + public Home1Fragment() { + } + + @Override + protected View onCreateView() { + View root = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_home1, null); + ButterKnife.bind(this, root); + + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Logger.d("第一页创建"); + initTopBar(); + } + + @OnClick(R.id.button) + public void onViewClicked() { + ToastUtils.info("主页"); + } + + private void initTopBar() { + mTopBar.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.app_color_theme_4)); + mTopBar.setTitle("快捷点菜界面"); + } + + /*private void initTopBar() { + mTopBar.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.app_color_theme_4)); + + mTopBar.setTitle("沉浸式状态栏示例"); + }*/ + + @Override + public void onDestroy() { + super.onDestroy(); + Logger.d("第一页销毁"); + } + + /** + * 当在activity设置viewPager + BottomNavigation + fragment时, + * 为防止viewPager左滑动切换界面,与fragment左滑返回上一界面冲突引起闪退问题, + * 必须加上此方法,禁止fragment左滑返回上一界面。 + * + * 切记!切记!切记!否则会闪退! + * + * 当在fragment设置viewPager + BottomNavigation + fragment时,则不会出现这个问题。 + * */ + @Override + protected boolean canDragBack() { + return false; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home2Fragment.java b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home2Fragment.java new file mode 100644 index 00000000..e4d11616 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home2Fragment.java @@ -0,0 +1,78 @@ +package com.bonait.bnframework.modules.home.fragment; + + +import android.app.Fragment; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.view.LayoutInflater; +import android.view.View; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.common.base.BaseFragment; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.orhanobut.logger.Logger; +import com.qmuiteam.qmui.widget.QMUITopBarLayout; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * A simple {@link Fragment} subclass. + */ +public class Home2Fragment extends BaseFragment { + + + @BindView(R.id.topbar) + QMUITopBarLayout mTopBar; + + public Home2Fragment() { + } + + @Override + protected View onCreateView() { + View root = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_home2, null); + ButterKnife.bind(this, root); + + + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Logger.d("第二页创建"); + + initTopBar(); + } + + private void initTopBar() { + mTopBar.setTitle("小炒功能菜单"); + } + + @OnClick(R.id.button) + public void onViewClicked() { + ToastUtils.info("功能"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Logger.d("第二页销毁"); + } + + /** + * 当在activity设置viewPager + BottomNavigation + fragment时, + * 为防止viewPager左滑动切换界面,与fragment左滑返回上一界面冲突引起闪退问题, + * 必须加上此方法,禁止fragment左滑返回上一界面。 + * + * 切记!切记!切记!否则会闪退! + * + * 当在fragment设置viewPager + BottomNavigation + fragment时,则不会出现这个问题。 + * */ + @Override + protected boolean canDragBack() { + return false; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home3Fragment.java b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home3Fragment.java new file mode 100644 index 00000000..296670cf --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/home/fragment/Home3Fragment.java @@ -0,0 +1,68 @@ +package com.bonait.bnframework.modules.home.fragment; + + +import android.app.Fragment; +import android.view.LayoutInflater; +import android.view.View; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.common.base.BaseFragment; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.orhanobut.logger.Logger; +import com.qmuiteam.qmui.widget.QMUITopBarLayout; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +/** + * A simple {@link Fragment} subclass. + */ +public class Home3Fragment extends BaseFragment { + + @BindView(R.id.topbar) + QMUITopBarLayout mTopBar; + + public Home3Fragment() { + // Required empty public constructor + } + + @Override + protected View onCreateView() { + View root = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_home3, null); + ButterKnife.bind(this, root); + Logger.d("第三页创建"); + + initTopBar(); + return root; + } + + private void initTopBar() { + mTopBar.setTitle("第三页"); + } + + @OnClick(R.id.button) + public void onViewClicked() { + ToastUtils.info("我的"); + } + + @Override + public void onDestroy() { + super.onDestroy(); + Logger.d("第三页销毁"); + } + + /** + * 当在activity设置viewPager + BottomNavigation + fragment时, + * 为防止viewPager左滑动切换界面,与fragment左滑返回上一界面冲突引起闪退问题, + * 必须加上此方法,禁止fragment左滑返回上一界面。 + * + * 切记!切记!切记!否则会闪退! + * + * 若底层是BottomNavigationFragment设置viewPager则不会出现这个问题。 + * */ + @Override + protected boolean canDragBack() { + return false; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/mine/fragment/MyFragment.java b/app/src/main/java/com/bonait/bnframework/modules/mine/fragment/MyFragment.java new file mode 100644 index 00000000..a452a925 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/mine/fragment/MyFragment.java @@ -0,0 +1,220 @@ +package com.bonait.bnframework.modules.mine.fragment; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; +import android.support.annotation.RequiresApi; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.ImageView; +import android.widget.TextView; + +import com.allen.library.SuperTextView; +import com.bonait.bnframework.R; +import com.bonait.bnframework.common.base.BaseFragment; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.utils.AlertDialogUtils; +import com.bonait.bnframework.common.utils.UpdateAppUtils; +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.modules.welcome.activity.LoginActivity; +import com.bonait.bnframework.modules.welcome.activity.WelcomeActivity; +import com.orhanobut.logger.Logger; +import com.qmuiteam.qmui.widget.dialog.QMUIDialog; +import com.qmuiteam.qmui.widget.dialog.QMUIDialogAction; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +public class MyFragment extends BaseFragment { + + + @BindView(R.id.h_background) + ImageView hBackground; + @BindView(R.id.h_head) + ImageView hHead; + @BindView(R.id.h_user_name) + TextView hUserName; + @BindView(R.id.stv_change_pwd) + SuperTextView stvChangePwd; + @BindView(R.id.stv_update) + SuperTextView stvUpdate; + @BindView(R.id.stv_logout) + SuperTextView stvLogout; + + /* + private static final String BUNDLE_TITLE = "key_title"; + public static MyFragment newInstance(String title) { + Bundle bundle = new Bundle(); + bundle.putString(BUNDLE_TITLE,title); + MyFragment myFragment = new MyFragment(); + myFragment.setArguments(bundle); + return myFragment; + } + */ + + private Context context; + + @Override + protected View onCreateView() { + View root = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_my, null); + ButterKnife.bind(this, root); + + return root; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + Logger.d("我的fragment创建"); + context = getContext(); + initView(); + } + + @OnClick(R.id.h_head) + public void onViewClicked() { + + } + + private void initView() { + + /* + * 版本更新,点击事件 + * */ + stvUpdate.setOnSuperTextViewClickListener(new SuperTextView.OnSuperTextViewClickListener() { + @Override + public void onClickListener(SuperTextView superTextView) { + //检查权限,并启动版本更新 + checkPermission(); + } + }); + + /** + * 退出按钮 + */ + stvLogout.setOnSuperTextViewClickListener(new SuperTextView.OnSuperTextViewClickListener() { + @Override + public void onClickListener(SuperTextView superTextView) { + String title = "温馨提示!"; + String message = "客官确定要退出程序吗,小菠萝会想你的哦?"; + AlertDialogUtils.showDialog(context, title, message, new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + skipToLoginActivity(); + dialog.dismiss(); + } + }); + } + }); + } + + /** + * 跳转登录界面 + */ + private void skipToLoginActivity() { + // 跳转到登录页面 + Intent intent = new Intent(context, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + //overridePend1ingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + } + + /***********************************检查App更新********************************************/ + @AfterPermissionGranted(Constants.UPDATE_APP) + @Override + public void checkPermission() { + // 检查文件读写权限 + String[] params = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; + if (EasyPermissions.hasPermissions(context,params)) { + + //Android 8.0后,安装应用需要检查打开未知来源应用权限 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + checkInstallPermission(); + } else { + UpdateAppUtils.updateApp(context); + } + } else { + //未获取权限 + EasyPermissions.requestPermissions(this, "更新版本需要读写本地权限!", Constants.UPDATE_APP, params); + } + } + + /** + * Android 8.0后,安装应用需要检查打开未知来源应用权限 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + private void checkInstallPermission() { + // 判断是否已打开未知来源应用权限 + boolean haveInstallPermission = context.getPackageManager().canRequestPackageInstalls(); + + if (haveInstallPermission) { + //已经打开权限,直接启动版本更新 + UpdateAppUtils.updateApp(context); + } else { + AlertDialogUtils.showDialog(getContext(), + "请打开未知来源应用权限", + "为了正常升级APP,请点击设置-高级设置-允许安装未知来源应用,本功能只限用于APP版本升级。", + "权限设置", + new QMUIDialogAction.ActionListener() { + @Override + public void onClick(QMUIDialog dialog, int index) { + // 跳转到系统打开未知来源应用权限,在onActivityResult中启动更新 + toInstallPermissionSettingIntent(context); + dialog.dismiss(); + } + }); + } + } + + /** + * 开启安装未知来源权限 + */ + @RequiresApi(api = Build.VERSION_CODES.O) + private void toInstallPermissionSettingIntent(Context context) { + Uri packageURI = Uri.parse("package:" + context.getPackageName()); + Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES, packageURI); + startActivityForResult(intent, Constants.INSTALL_PERMISSION_CODE); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + switch (requestCode) { + case Constants.INSTALL_PERMISSION_CODE: + checkPermission(); + break; + } + } + + @Override + public void onDestroy() { + super.onDestroy(); + Logger.d("我的fragment销毁"); + } + + /** + * 当在activity设置viewPager + BottomNavigation + fragment时, + * 为防止viewPager左滑动切换界面,与fragment左滑返回上一界面冲突引起闪退问题, + * 必须加上此方法,禁止fragment左滑返回上一界面。 + * + * 切记!切记!切记!否则会闪退! + * + * 当在fragment设置viewPager + BottomNavigation + fragment时,则不会出现这个问题。 + */ + @Override + protected boolean canDragBack() { + return false; + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/mine/model/UpdateAppPo.java b/app/src/main/java/com/bonait/bnframework/modules/mine/model/UpdateAppPo.java new file mode 100644 index 00000000..0ee1d244 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/mine/model/UpdateAppPo.java @@ -0,0 +1,112 @@ +package com.bonait.bnframework.modules.mine.model; + +import com.google.gson.annotations.SerializedName; + +import java.util.Date; + +/** + * Created by LY on 2019/4/8. + */ +public class UpdateAppPo { + /** + * apkId : 10140 + * createtime : 创建时间 + * creator : 发布者 + * description : 描述 + * fileSize : Apk大小byte + * id : 1 + * modifier : null + * updatetime : 更新时间 + * version : Apk版本 + */ + @SerializedName("apkId") + private String apkId; + @SerializedName("createtime") + private Date createTime; + @SerializedName("creator") + private String creator; + @SerializedName("description") + private String description; + @SerializedName("fileSize") + private String fileSize; + @SerializedName("id") + private int id; + @SerializedName("modifier") + private String modifier; + @SerializedName("updatetime") + private Date updateTime; + @SerializedName("version") + private int version; + + public String getApkId() { + return apkId; + } + + public void setApkId(String apkId) { + this.apkId = apkId; + } + + public Date getCreateTime() { + return createTime; + } + + public void setCreateTime(Date createTime) { + this.createTime = createTime; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getFileSize() { + return fileSize; + } + + public void setFileSize(String fileSize) { + this.fileSize = fileSize; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getModifier() { + return modifier; + } + + public void setModifier(String modifier) { + this.modifier = modifier; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public int getVersion() { + return version; + } + + public void setVersion(int version) { + this.version = version; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/welcome/activity/LoginActivity.java b/app/src/main/java/com/bonait/bnframework/modules/welcome/activity/LoginActivity.java new file mode 100644 index 00000000..e0290e37 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/welcome/activity/LoginActivity.java @@ -0,0 +1,477 @@ +package com.bonait.bnframework.modules.welcome.activity; + +import android.animation.ObjectAnimator; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.widget.NestedScrollView; +import android.text.Editable; +import android.text.InputType; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.view.KeyEvent; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.LinearInterpolator; +import android.widget.CheckBox; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.common.base.BaseActivity; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.constant.SPConstants; +import com.bonait.bnframework.common.http.callback.json.JsonDialogCallback; +import com.bonait.bnframework.common.model.BaseCodeJson; +import com.bonait.bnframework.common.utils.AlertDialogUtils; +import com.bonait.bnframework.common.utils.AnimationToolUtils; +import com.bonait.bnframework.common.utils.AppUtils; +import com.bonait.bnframework.common.utils.Des3Utils; +import com.bonait.bnframework.common.utils.KeyboardToolUtils; +import com.bonait.bnframework.common.utils.PreferenceUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.modules.home.activity.BottomNavigation2Activity; +import com.bonait.bnframework.modules.welcome.model.AppLoginPo; +import com.bonait.bnframework.test.TestActivity; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.model.HttpParams; +import com.lzy.okgo.model.Response; +import com.mobsandgeeks.saripaar.ValidationError; +import com.mobsandgeeks.saripaar.Validator; +import com.mobsandgeeks.saripaar.annotation.NotEmpty; +import com.mobsandgeeks.saripaar.annotation.Order; +import com.mobsandgeeks.saripaar.annotation.Password; +import com.qmuiteam.qmui.util.QMUIStatusBarHelper; + +import org.litepal.LitePal; + +import java.util.List; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; + +public class LoginActivity extends BaseActivity implements Validator.ValidationListener { + + @BindView(R.id.logo) + ImageView mLogo; + + @Order(1) + @NotEmpty(message = "用户名不能为空") + @BindView(R.id.et_account) + EditText mEtAccount; + + @Order(2) + @NotEmpty(message = "密码不能为空") + @Password(min = 1, scheme = Password.Scheme.ANY,message = "密码不能少于1位") + @BindView(R.id.et_password) + EditText mEtPassword; + + @BindView(R.id.iv_clean_account) + ImageView mIvCleanAccount; + @BindView(R.id.clean_password) + ImageView mCleanPassword; + @BindView(R.id.iv_show_pwd) + ImageView mIvShowPwd; + + @BindView(R.id.cb_checkbox) + CheckBox cbCheckbox; + @BindView(R.id.content) + LinearLayout mContent; + @BindView(R.id.scrollView) + NestedScrollView mScrollView; + + private long exitTime = 0; + private int screenHeight = 0;//屏幕高度 + private int keyHeight = 0; //软件盘弹起后所占高度 + private final float scale = 0.9f; //logo缩放比例 + private int height = 0; + boolean isCheckBox; + + private Validator validator; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_login); + ButterKnife.bind(this); + + QMUIStatusBarHelper.setStatusBarLightMode(this); + + validator = new Validator(this); + validator.setValidationListener(this); + + initUser(); + initEvent(); + } + + @OnClick({R.id.iv_clean_account, R.id.clean_password, R.id.iv_show_pwd,R.id.forget_password, R.id.btn_login}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.iv_clean_account: + mEtAccount.getText().clear(); + mEtPassword.getText().clear(); + break; + case R.id.clean_password: + mEtPassword.getText().clear(); + break; + case R.id.iv_show_pwd: + changePasswordEye(); + break; + case R.id.forget_password: + forgotPassword(); + break; + case R.id.btn_login: + // 登录,启动表单验证,重写方法:onValidationSucceeded + validator.validate(); + break; + } + } + + /** + * 初始化登录历史记录 + * */ + private void initUser() { + //从sharePreference拿出保存的账号密码 + String username = PreferenceUtils.getString(SPConstants.USER_NAME, ""); + String password = PreferenceUtils.getString(SPConstants.PASSWORD, ""); + mEtAccount.getText().clear(); + mEtPassword.getText().clear(); + + if (!TextUtils.isEmpty(username)) { + mEtAccount.setText(username); + mEtAccount.setSelection(mEtAccount.getText().length()); + mIvCleanAccount.setVisibility(View.VISIBLE); + } + + if (!TextUtils.isEmpty(password)) { + password = Des3Utils.decode(password); + mEtPassword.setText(password); + mEtPassword.setSelection(mEtPassword.getText().length()); + mCleanPassword.setVisibility(View.VISIBLE); + cbCheckbox.setChecked(true); + isCheckBox = true; + } else { + cbCheckbox.setChecked(false); + isCheckBox = false; + } + } + + /** + * 忘记密码 + * */ + private void forgotPassword() { + ToastUtils.info("请与管理员联系修改密码!"); + } + + // *************************以下为登录验证及跳转界面相关*************************// + + /** + * 登录表单验证成功后,请求后台验证账号密码 + * */ + private void attemptLogin() { + String url = Constants.SERVICE_IP + "/appLogin.do"; + + final String userAccount = mEtAccount.getText().toString(); + final String password = mEtPassword.getText().toString(); + //密码加密 + String newPassword = AppUtils.encryptSha256(password); + + //跳转到主页 + skipToMainActivity(); +// OkGo.>post(url) +// .tag(this) +// .params("username",userAccount) +// .params("password",newPassword) +// .execute(new JsonDialogCallback>(this) { +// @Override +// public void onSuccess(Response> response) { +// BaseCodeJson loginJson = response.body(); +// if (loginJson != null) { +// whichDepartment(userAccount,password,loginJson.getResult()); +// } +// } +// }); + } + + /** + * 判断该账号是否有所属部门 + * */ + private void whichDepartment(String userAccount, String password, AppLoginPo appLoginPo) { + String firstDepId = appLoginPo.getFirstDepId(); + + if (firstDepId == null) { + + // 测试数据 + if (Constants.superAdminTest && 1 == appLoginPo.getId()) { + // 测试数据 + superAdminTest(appLoginPo); + // 存储用户必要的数据 + saveUserDataToSharePreference(userAccount, password,appLoginPo); + //判断跳转到测试界面 + if (Constants.SKIP_TO_TEST_ACTIVITY) { + skipToTestActivity(); + return; + } + //跳转到主页 + skipToMainActivity(); + return; + } + + AlertDialogUtils.showDialog( + this, + "登录提示:", + "该账户未确认组织类型,无法登录,请与管理员联系!", + "重新登录"); + } else { + + // 测试数据 + if (Constants.superAdminTest) { + superAdminTest(appLoginPo); + } + + // 存储用户必要的数据 + saveUserDataToSharePreference(userAccount, password,appLoginPo); + //判断跳转到测试界面 + if (Constants.SKIP_TO_TEST_ACTIVITY) { + skipToTestActivity(); + return; + } + + //跳转到主页 + skipToMainActivity(); + } + + } + + /** + * 根据返回的结果,存储用户必要的数据到SharePreference + * */ + private void saveUserDataToSharePreference(String userName, String password,AppLoginPo appLoginPo) { + //保存用户账号密码到sharePreference + PreferenceUtils.setString(SPConstants.USER_NAME, userName); + PreferenceUtils.remove(SPConstants.PASSWORD); + // 如果用户点击了“记住密码”,保存密码 + if (isCheckBox) { + String psd = Des3Utils.encode(password); + PreferenceUtils.setString(SPConstants.PASSWORD, psd); + } + // 保存用户名字 + PreferenceUtils.setString(SPConstants.USER, appLoginPo.getName()); + // 保存用户ID + PreferenceUtils.setInt(SPConstants.USER_ID, appLoginPo.getId()); + // 保存角色 + PreferenceUtils.setString(SPConstants.ROLE_NAMES,appLoginPo.getRoleNames()); + + // 保存部门 + if (appLoginPo.getFirstDepId() != null) { + PreferenceUtils.setString(SPConstants.FIRST_DEP_ID,appLoginPo.getFirstDepId()); + } + + //保存token到sharePreference + PreferenceUtils.setString(SPConstants.TOKEN, appLoginPo.getToken()); + //获取token + HttpParams params = new HttpParams(); + params.put(Constants.APP_TOKEN, PreferenceUtils.getString(SPConstants.TOKEN, "")); + OkGo.getInstance().init(getApplication()) + .addCommonParams(params); + //启动本地数据库 + LitePal.getDatabase(); + } + + /** + * 跳转到测试界面 + * */ + private void skipToTestActivity() { + // 隐藏软键盘 + KeyboardToolUtils.hideSoftInput(LoginActivity.this); + // 退出界面之前把状态栏还原为白色字体与图标 + QMUIStatusBarHelper.setStatusBarDarkMode(LoginActivity.this); + Intent intent = new Intent(LoginActivity.this, TestActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + } + + /** + * 跳转到主界面 + * */ + private void skipToMainActivity() { + // 隐藏软键盘 + KeyboardToolUtils.hideSoftInput(LoginActivity.this); + // 退出界面之前把状态栏还原为白色字体与图标 + QMUIStatusBarHelper.setStatusBarDarkMode(LoginActivity.this); + Intent intent = new Intent(LoginActivity.this, BottomNavigation2Activity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + } + + // *************************以上为登录验证及跳转界面相关*************************// + + /** + * 测试数据 + * */ + private void superAdminTest(AppLoginPo appLoginPo) { + int id = appLoginPo.getId(); + if (id == 1) { + // TODO: do something + } + } + + + //---------------------------------分割线-------------------------------------// + + /** + * 点击眼睛图片显示或隐藏密码 + * */ + private void changePasswordEye() { + if (mEtPassword.getInputType() != InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) { + mEtPassword.setInputType(InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD); + mIvShowPwd.setImageResource(R.drawable.icon_pass_visuable); + } else { + mEtPassword.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + mIvShowPwd.setImageResource(R.drawable.icon_pass_gone); + } + + // 将光标移至文字末尾 + String pwd = mEtPassword.getText().toString(); + if (!TextUtils.isEmpty(pwd)) + mEtPassword.setSelection(pwd.length()); + } + + @SuppressLint("ClickableViewAccessibility") + private void initEvent() { + + // 获取屏幕高度 + screenHeight = this.getResources().getDisplayMetrics().heightPixels; + // 弹起高度为屏幕高度的1/3 + keyHeight = screenHeight / 3; + + // 输入账号状态监听,在右边显示或隐藏clean + addIconClearListener(mEtAccount,mIvCleanAccount); + // 监听EtPassword输入状态,在右边显示或隐藏clean + addIconClearListener(mEtPassword,mCleanPassword); + + /* + * 记住密码Checkbox点击监听器 + * */ + cbCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + isCheckBox = isChecked; + } + }); + + /* + * 禁止键盘弹起的时候可以滚动 + */ + mScrollView.setOnTouchListener(new View.OnTouchListener() { + @Override + public boolean onTouch(View v, MotionEvent event) { + return true; + } + }); + // ScrollView监听滑动状态 + mScrollView.addOnLayoutChangeListener(new ViewGroup.OnLayoutChangeListener() { + @Override + public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { + /* old是改变前的左上右下坐标点值,没有old的是改变后的左上右下坐标点值 + 现在认为只要控件将Activity向上推的高度超过了1/3屏幕高,就认为软键盘弹起*/ + if (oldBottom != 0 && bottom != 0 && (oldBottom - bottom > keyHeight)) { + int dist = mContent.getBottom() - mScrollView.getHeight(); + if (dist > 0) { + ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(mContent, "translationY", 0.0f, -dist); + mAnimatorTranslateY.setDuration(300); + mAnimatorTranslateY.setInterpolator(new LinearInterpolator()); + mAnimatorTranslateY.start(); + AnimationToolUtils.zoomIn(mLogo, scale, dist); + } + + } else if (oldBottom != 0 && bottom != 0 && (bottom - oldBottom > keyHeight)) { + if ((mContent.getBottom() - oldBottom) > 0) { + ObjectAnimator mAnimatorTranslateY = ObjectAnimator.ofFloat(mContent, "translationY", mContent.getTranslationY(), 0); + mAnimatorTranslateY.setDuration(300); + mAnimatorTranslateY.setInterpolator(new LinearInterpolator()); + mAnimatorTranslateY.start(); + //键盘收回后,logo恢复原来大小,位置同样回到初始位置 + AnimationToolUtils.zoomOut(mLogo, scale); + } + } + } + }); + } + + /** + * 设置文本框与右侧删除图标监听器 + */ + private void addIconClearListener(final EditText et, final ImageView iv) { + et.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + //如果文本框长度大于0,则显示删除图标,否则不显示 + if (s.length() > 0) { + iv.setVisibility(View.VISIBLE); + } else { + iv.setVisibility(View.INVISIBLE); + } + } + }); + } + + @Override + public void onValidationSucceeded() { + // 注解验证全部通过验证,开始后台验证 + attemptLogin(); + } + + @Override + public void onValidationFailed(List errors) { + for (ValidationError error : errors) { + View view = error.getView(); + String message = error.getCollatedErrorMessage(this); + + // 显示上面注解中添加的错误提示信息 + if (view instanceof EditText) { + ((EditText) view).setError(message); + } else { + ToastUtils.error(message); + } + } + } + + /** + * 重写返回键,实现双击退出程序效果 + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (System.currentTimeMillis() - exitTime > 2000) { + ToastUtils.normal("再按一次退出程序"); + exitTime = System.currentTimeMillis(); + } else { + OkGo.getInstance().cancelAll(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + ActivityLifecycleManager.get().appExit(); + } + return true; + } + return super.onKeyDown(keyCode, event); + } +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/welcome/activity/WelcomeActivity.java b/app/src/main/java/com/bonait/bnframework/modules/welcome/activity/WelcomeActivity.java new file mode 100644 index 00000000..9487bc20 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/welcome/activity/WelcomeActivity.java @@ -0,0 +1,196 @@ +package com.bonait.bnframework.modules.welcome.activity; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.support.v7.app.AppCompatActivity; +import android.view.KeyEvent; + +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.constant.SPConstants; +import com.bonait.bnframework.common.http.callback.json.JsonCallback; +import com.bonait.bnframework.common.model.BaseCodeJson; +import com.bonait.bnframework.common.utils.PreferenceUtils; +import com.bonait.bnframework.modules.home.activity.BottomNavigation2Activity; +import com.bonait.bnframework.test.TestActivity; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.model.Response; + +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; + +public class WelcomeActivity extends AppCompatActivity { + + private Handler handler = new Handler(); + /* + * 启动模式: + * 1:启动界面时间与App加载时间相等 + * 2:设置启动界面2秒后跳转 + * */ + private final static int SELECT_MODE = 2; + private OkHttpClient.Builder builder; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + //setContentView(R.layout.activity_welcome); + initWelcome(); + } + + private void initWelcome() { + switch (WelcomeActivity.SELECT_MODE) { + case 1: + fastWelcome(); + break; + case 2: + slowWelcome(); + break; + } + } + + + /*方法1:启动界面时间与App加载时间相等*/ + private void fastWelcome() { + new Thread(new Runnable() { + @Override + public void run() { + //耗时任务,比如加载网络数据 + runOnUiThread(new Runnable() { + @Override + public void run() { + //判断token + doPost(); + } + }); + } + }).start(); + } + + /*方法2:设置启动界面2秒后跳转*/ + private void slowWelcome() { + handler.postDelayed(new Runnable() { + @Override + public void run() { + //判断token + doPost(); + } + }, 2000); + } + + + /** + * 请求服务器判断token是否过期 + */ + private void doPost() { + //1.直接进入登录界面 + skipToLoginActivity(); +// //判断是否用户修改过密码 +// boolean isChangePwd = PreferenceUtils.getBoolean(SPConstants.CHANGE_PWD, false); +// if (isChangePwd) { +// //若修改过密码,则使token失效 +// PreferenceUtils.setString(SPConstants.TOKEN, ""); +// } +// String token = PreferenceUtils.getString(SPConstants.TOKEN, ""); +// // 请求后台判断token +// String url = Constants.SERVICE_IP + "/checkToken.do"; +// +// // 修改请求超时时间为6s,与全局超时时间分开 +// builder = new OkHttpClient.Builder(); +// builder.readTimeout(2000, TimeUnit.MILLISECONDS); +// builder.writeTimeout(2000, TimeUnit.MILLISECONDS); +// builder.connectTimeout(2000, TimeUnit.MILLISECONDS); +// +// OkGo.>post(url) +// .tag(this) +// .client(builder.build()) +// .params("token", token) +// .execute(new JsonCallback>() { +// @Override +// public void onSuccess(Response> response) { +// +// // 判断是否开启跳转到测试界面 +// if (Constants.SKIP_TO_TEST_ACTIVITY) { +// skipToTestActivity(); +// } else { +// skipToMainActivity(); +// } +// +// } +// +// @Override +// public void onError(Response> response) { +// super.onError(response); +// skipToLoginActivity(); +// } +// }); + } + + + /** + * 跳转到测试页面 + * */ + private void skipToTestActivity() { + // token未过期,跳转到主界面 + Intent intent = new Intent(WelcomeActivity.this, TestActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + startActivity(intent); + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + } + + + /** + * 跳转到主界面 + * */ + private void skipToMainActivity() { + // token未过期,跳转到主界面 + Intent intent = new Intent(WelcomeActivity.this, BottomNavigation2Activity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + } + + /** + * 跳转到登录页面 + * */ + private void skipToLoginActivity() { + // 跳转到登录页面 + Intent intent = new Intent(WelcomeActivity.this, LoginActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + // 结束所有Activity + ActivityLifecycleManager.get().finishAllActivity(); + } + + /** + * 屏蔽物理返回键 + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + return true; + } + return super.onKeyDown(keyCode, event); + } + + @Override + protected void onDestroy() { + if(builder!=null) + { + OkGo.cancelAll(builder.build()); + } + + if (handler != null) { + //If token is null, all callbacks and messages will be removed. + handler.removeCallbacksAndMessages(null); + } + super.onDestroy(); + } + +} diff --git a/app/src/main/java/com/bonait/bnframework/modules/welcome/model/AppLoginPo.java b/app/src/main/java/com/bonait/bnframework/modules/welcome/model/AppLoginPo.java new file mode 100644 index 00000000..da95bed9 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/modules/welcome/model/AppLoginPo.java @@ -0,0 +1,82 @@ +package com.bonait.bnframework.modules.welcome.model; + +import java.util.List; + +/** + * Created by LY on 2019/4/2. + */ +public class AppLoginPo { + + private String token; + private int id; + private String name; + private List roleIds; + private String roleNames; + private String firstDepId; + private String firstDepName; + private String depName; + + public String getToken() { + return token; + } + + public void setToken(String token) { + this.token = token; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getRoleIds() { + return roleIds; + } + + public void setRoleIds(List roleIds) { + this.roleIds = roleIds; + } + + public String getRoleNames() { + return roleNames; + } + + public void setRoleNames(String roleNames) { + this.roleNames = roleNames; + } + + public String getFirstDepId() { + return firstDepId; + } + + public void setFirstDepId(String firstDepId) { + this.firstDepId = firstDepId; + } + + public String getFirstDepName() { + return firstDepName; + } + + public void setFirstDepName(String firstDepName) { + this.firstDepName = firstDepName; + } + + public String getDepName() { + return depName; + } + + public void setDepName(String depName) { + this.depName = depName; + } +} diff --git a/app/src/main/java/com/bonait/bnframework/test/Test.java b/app/src/main/java/com/bonait/bnframework/test/Test.java new file mode 100644 index 00000000..6fa2b271 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/test/Test.java @@ -0,0 +1,7 @@ +package com.bonait.bnframework.test; + +/** + * Created by LY on 2019/4/3. + */ +public class Test { +} diff --git a/app/src/main/java/com/bonait/bnframework/test/TestActivity.java b/app/src/main/java/com/bonait/bnframework/test/TestActivity.java new file mode 100644 index 00000000..51fb6bf3 --- /dev/null +++ b/app/src/main/java/com/bonait/bnframework/test/TestActivity.java @@ -0,0 +1,189 @@ +package com.bonait.bnframework.test; + +import android.Manifest; +import android.content.Context; +import android.os.Bundle; +import android.view.KeyEvent; +import android.view.View; + +import com.bonait.bnframework.R; +import com.bonait.bnframework.manager.ActivityLifecycleManager; +import com.bonait.bnframework.common.base.BaseActivity; +import com.bonait.bnframework.common.constant.Constants; +import com.bonait.bnframework.common.http.callback.files.FileProgressDialogCallBack; +import com.bonait.bnframework.common.http.callback.files2.FileProgressDialogCallBack2; +import com.bonait.bnframework.common.http.callback.json.JsonDialogCallback; +import com.bonait.bnframework.common.model.BaseCodeJson; +import com.bonait.bnframework.common.utils.AppUtils; +import com.bonait.bnframework.common.utils.ToastUtils; +import com.bonait.bnframework.modules.welcome.model.AppLoginPo; +import com.bonait.bnframework.modules.mine.model.UpdateAppPo; +import com.lzy.okgo.OkGo; +import com.lzy.okgo.model.Response; +import com.qmuiteam.qmui.widget.roundwidget.QMUIRoundButton; + +import java.io.File; + +import butterknife.BindView; +import butterknife.ButterKnife; +import butterknife.OnClick; +import pub.devrel.easypermissions.AfterPermissionGranted; +import pub.devrel.easypermissions.EasyPermissions; + +public class TestActivity extends BaseActivity { + + @BindView(R.id.button) + QMUIRoundButton button; + @BindView(R.id.button2) + QMUIRoundButton button2; + + private String token = ""; + private Context context; + private long exitTime = 0; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_test); + ButterKnife.bind(this); + context = this; + } + + @OnClick({R.id.button, R.id.button2}) + public void onViewClicked(View view) { + switch (view.getId()) { + case R.id.button: + //ToastUtils.info("提交成功!"); + //SmartToast.info("提交成功!"); + //login(); + break; + case R.id.button2: + //SmartToast.warning("保存失败!"); + //ToastUtils.warning("保存失败!"); + // token未过期,跳转到主界面 + /*Intent intent = new Intent(TestActivity.this, BottomNavigation2Activity.class); + startActivity(intent);*/ + //checkToken(); + checkPermission(); + break; + } + } + + + private void checkToken() { + String url = Constants.SERVICE_IP + "/checkToken.do"; + + OkGo.>post(url) + .tag(this) + .params("token", token) + .execute(new JsonDialogCallback>(this) { + @Override + public void onSuccess(Response> response) { + BaseCodeJson baseCodeJson = response.body(); + ToastUtils.info(baseCodeJson.getMsg()); + } + }); + } + + + private void login() { + String userName = "admin"; + String passWord = AppUtils.encryptSha256("1"); + String url = Constants.SERVICE_IP + "/appLogin.do"; + OkGo.>post(url) + .tag(this) + .params("username",userName) + .params("password",passWord) + .execute(new JsonDialogCallback>(this) { + @Override + public void onSuccess(Response> response) { + BaseCodeJson loginPo = response.body(); + token = loginPo.getResult().getToken(); + } + }); + } + + /** + * 检查权限是否授权 + */ + @AfterPermissionGranted(Constants.ALL_PERMISSION) + @Override + public void checkPermission() { + + //String[] params = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA}; + String[] params = {Manifest.permission.WRITE_EXTERNAL_STORAGE}; + //判断是否获取权限 + if (EasyPermissions.hasPermissions(this, params)) { + // 全部权限申请成功后 + doDownload(); + } else { + //未获取权限或拒绝权限时 + EasyPermissions.requestPermissions(this, + "xxx需要用到以下权限:\n\n1. 录制音频权限\n\n2. 录制视频权限\n\n3. 文件读取存储权限", + Constants.ALL_PERMISSION, params); + } + } + + private String downloadUrl; + private long totalSize; + + private void getAppId() { + String url = Constants.SERVICE_IP+"/iandroid/appVersionAction!getNewVersion.do"; + OkGo.post(url) + .tag(this) + .execute(new JsonDialogCallback(this) { + @Override + public void onSuccess(Response response) { + UpdateAppPo updateAppPo = response.body(); + String appId = updateAppPo.getApkId(); + //获取apk下载地址 + downloadUrl = Constants.SERVICE_IP+ "/file-download?fileId=" + appId; + totalSize = Long.parseLong(updateAppPo.getFileSize()); + //toDownload(); + doDownload(); + } + }); + } + + private void toDownload() { + OkGo.get(downloadUrl) + .tag(this) + .execute(new FileProgressDialogCallBack2(this,totalSize) { + @Override + public void onSuccess(Response response) { + ToastUtils.success("下载完成!"); + } + }); + } + + private void doDownload() { + downloadUrl = Constants.SERVICE_IP+ "/file-download?fileId=1509"; + OkGo.get(downloadUrl) + .tag(this) + .execute(new FileProgressDialogCallBack(this) { + @Override + public void onSuccess(Response response) { + ToastUtils.success("下载完成!"); + } + }); + } + + /** + * 重写返回键,实现双击退出程序效果 + */ + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (System.currentTimeMillis() - exitTime > 2000) { + ToastUtils.normal("再按一次退出程序"); + exitTime = System.currentTimeMillis(); + } else { + OkGo.getInstance().cancelAll(); + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out); + ActivityLifecycleManager.get().appExit(); + } + return true; + } + return super.onKeyDown(keyCode, event); + } +} diff --git a/app/src/main/res/color/s_app_color_blue_2.xml b/app/src/main/res/color/s_app_color_blue_2.xml new file mode 100644 index 00000000..20c3beb5 --- /dev/null +++ b/app/src/main/res/color/s_app_color_blue_2.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/s_app_color_gray.xml b/app/src/main/res/color/s_app_color_gray.xml new file mode 100644 index 00000000..c395c1be --- /dev/null +++ b/app/src/main/res/color/s_app_color_gray.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/s_btn_blue_bg.xml b/app/src/main/res/color/s_btn_blue_bg.xml new file mode 100644 index 00000000..c391feb9 --- /dev/null +++ b/app/src/main/res/color/s_btn_blue_bg.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/s_btn_blue_border.xml b/app/src/main/res/color/s_btn_blue_border.xml new file mode 100644 index 00000000..c391feb9 --- /dev/null +++ b/app/src/main/res/color/s_btn_blue_border.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/s_btn_blue_text.xml b/app/src/main/res/color/s_btn_blue_text.xml new file mode 100644 index 00000000..c391feb9 --- /dev/null +++ b/app/src/main/res/color/s_btn_blue_text.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/s_topbar_btn_color.xml b/app/src/main/res/color/s_topbar_btn_color.xml new file mode 100644 index 00000000..70978d9e --- /dev/null +++ b/app/src/main/res/color/s_topbar_btn_color.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-hdpi/icon_pass_gone.png b/app/src/main/res/drawable-hdpi/icon_pass_gone.png new file mode 100644 index 0000000000000000000000000000000000000000..52098e18b3e3cb4013c4504cc4825037605002ef GIT binary patch literal 505 zcmVf&kPy!{S1dd4ap5+oKff8mf9^{Z=NTN+vt6fVu1T_Bq z`-ZCew>KGp3xLa~p)JV>p#3;hM7rMtRn-H4+8hlemE>%{f#emyMO6>q0#(&R2;pp? zXdoqd6_HLvdKXBvU-L55GHeYL&3WT9%AHw5LkOpbp#Uzvj9Z6Q^{@_P1wE6TMWo-& zlH@gIm;<^>_|i_}I39E5P0vWq0O}HSE-ktBG^Sy)tcpDiA)H7~ssA@Gt%vj73ui9+ zCilG5Q%h{PId46UdvZo{TK6l{(`m=YKr)69PL-8dO9w3C7(zJbpysq$oR(l5bT zDYYbX9%`V5WMltllA6ob>w}1N*@~p7EQlq04+T&=DUC^;)(%Z<78Tt62=EKBmHZ$`;M0dOA3_$nth^Q~}6Vd(Yq1|BuaH}a|cb@m&VdiVAbI0*XUgW5C z>JnEGnQG6i^K^hw$opXO?aEZbRRfB5kSQ8ZB}1C_*>MF=|n0wNKO5S5G& zmx75CQcVZSwt}4-vnyp~35 zbyUfav0(EaBP7g{zY1(VGOtCkg_vdDM6{-(O{Z1Co@XjStrlQ#><07=c;RP<2mHaX zwCk;T-{4t_XQ6HBk|A<47=iAS|G?2-#~>JrKzmaueBEw=N2+sp-CLQSL^Dp$L(GA{ zn2l{B7F%{C~~`gw&3w%VJ7ziDxPZuOGZf7qEV1VKfQ@ zVfgXx;iPL8fAoZ3C~uVv!SC&geLTx=d4AbQAf%7{9)MN>xReg%ZvHbx#39= za~JTYu8|U|QbMSFk&gR(-Ks*UEY?ADZ9crzB*vcqd1VT+(V{wS)T*OiKTQX`V3ZOP zQ|5hKWm!R{?E5E?%=f=tg$g9CQVFp{BhbI7gyfH2qLh#^6(Pd{H7w4RCzP$z&~X3! zrz^-Ls=1<22UGqKZeNK0&cmp?5eV~!8>O{uf}Le##fS>8UJt}MwN#emXF~JxJR1H) z>=(VXu7#OpR+MMKh{w+xIw7@?OHN4!I_cYMm%=;i%HY%EF6ykw#I`)2T8hZXgdcuq zeEi)oxY5)$bX*eFhwY9gV_5Gv{5OPg zwih2R1^ZuKaJZ75-LcZ=Cyh#p*rkL{u>*OWm|5OwM!zV>i23CBWtvCY8$~$y%fGZn zc_xF@ucRv{{!+bop%OB^On<`h<5jZF(#lSXq#i?_D17C>09|=)Sr&BcAAkY358tV1 zO32Do)9ob1lhiSDLccs+pLh zw~>6oOP&gn-gY*=Tgd<+oex%b;`@*=-@c%!V4lhU0t^60xRo9-K|9z00000QD$X*hKLqDc6m>8al)+%=uYsG4eK7W8*+&PW2)b2Q zYk#QGth#DrlvHbsomm?{YLoch>*<<%?mb!VO&xvV;lRz!InRB+-y_9hF-#;$H}i65 z#i1Np>Iv0^DuR%^vYGHcK}yjzl#i)n+B-nFA$n03 zkSV8SKO-cKlfO!AKV{$Yq&37evx~6mIR(<_zB`RHWn{9=X369(u%mG09ON#vL(WXY z|*LoBpg%#g_{%|q#qIz1UWDi)0)Eqf9i8|Guy?gmJcWFwzyVdok; zb+g&fp1O1<Lr_u<`h8vWmQtIuCs!x!C#c`ab&l>RaOWM{B{HOby@-ijk6(1 z5^jI%L3DTouWWn`Qu?#+&a}TE+Nz2t z0>xWu{*APIN4*Gj^a|EoigSC?fHSNRKmvFJkh0Co+;M?D! zmJ3_0gNkTbs*ZfnXihU~@|C%%Fuu_rtyhX^lnya_c0PhfdQ&0u9XkgJn|y9AdtRfE zLS`SK+i13z1s4F_Y;HQh`&0@NWBctKC6ly<#&Ncfk0!&}p4X6+%>7W3w;!UUm+9=0B7KC!dNC|761SM07*qoM6N<$f?=pFtN;K2 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/icon_personal_sex.png b/app/src/main/res/drawable-hdpi/icon_personal_sex.png new file mode 100644 index 0000000000000000000000000000000000000000..498571c66019606f4b7a0bb368c9f9d3ce932729 GIT binary patch literal 1154 zcmV-|1bzF7P)MsM|8Z<6mJy-6>=2o+cnDZL0$3uYl1nrn)<>gufS z?@Z_3y}7&BncbOH!vi0K>z#YQIrp6J+;as@)8IInWMyz@FyO{VC!!tUM>Hd95nex7Uyg7C|^?4QUw z(|n+dTx$0f8#7|51hx|nV^lizVycg=tLvfOB2$}Md|m-9`6um-qzY1%3!MIn%;%*v zqwFK=4(51>DRB2ef+7`)LNpMfh|g_-R9#}$*WGOWoGt6jqrjHT=Cy2_#4DM&0uaM3kTqbVr6S-(d{c^ib-k4=#4+-3;*c|y(kMmsN6LP^Id z5Tx9o5IiC{Jmp|N?*%QM0YTPR34m8yaAeknr+P2EmF?Ar#_p*&X06Y}b7iwy3rG@0be z4k&MeklKvx{Xel7fjDkU>5V#&>q|jXaoqB5ZuYImKp77f+A=R6)L~ukwE94B+Nfu? zUoGa4+4Ddo3Cin5P?ZGTQrG3mVv;k=6Vm+z62MgSeuhEaiW|_Ry{Pq*3S^a zRwTl@gB*)}-^5@}&=NsZSgzf#y5v|<)?J^@My&Egr!3kSTYum2W0Z9V9odJf$cH6d zumMCYnx5NDSS&Ybxgd&63MiEZrouZDE0EfVSt0X@t(%F(?9P>)7@zZDY(M7#_w^bo zl)9%ujG4&CWiypM=QYHA^vYA5MQwWJ!-eNvaLG4)Bt0sku;@eqMbq(cfRnUvtfJKQ5xkt#^dZUG{gb#zE2 z#miWU2c%+Bkv$*}8z5nH$RuTO;4(vldzrbUC{hloMe0VS1WY6yvKf%VP4TK=<8k9g zIvmu@Guuh4hbWP(<7SC@O3XuO2H6gB7fxqV6&e@zfm zscc@+B}wTGb5X$CRR^M;IG+j7#h%B><~&^ND8r{8A8_~cZLvp_+5CMQBb``Ge^)!?uMzy1mmwxheA*Yq;>{owD%a~YKsvW7%d62tu7rmKVEUz z*+L+dqfdr-h4LvKO{pzrKYM;4>(wTHA9BJ}LLj3fPX&1eQvjbv>`3t(_|@~1&#-HT zOU_1sQ3%v-_Z2)x;!w1X|0IZ++438^8n&X!aKV-QXcq$M?ZMQGhO@0Fd2d)fb@1J86gMB03v-Xn-v>nBkuKI9 z46p!?5ttW}h_Y5)@444s>uAIKj$@4naN_-8cT|v7Z2mz78rP~U>tBK-ge-)Q(ucg$ ziX~&0W0g7$<9yZ7HRX|V-2wA}%`4spv5HY=Ez%06mm@JWiT`4Mp-;e7Lj|foT!z8a z;SLyX;`QX6BFPe#yGT$`)_RvKxULv3(neQ7wahI(z1?WJVttxeU&a-_p}3xP70h0#W>G28Z#S^MDTl4KyJ9AXjoTpEu?)A~{{Q>+V_5z^ h^e;_`VP<~>7yzvfYvFJ*hc5sC002ovPDHLkV1kEV3EThx literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/icon_personal_update.png b/app/src/main/res/drawable-hdpi/icon_personal_update.png new file mode 100644 index 0000000000000000000000000000000000000000..0e7b00f04a4b2cfaed84b4c6a896a443b800b017 GIT binary patch literal 658 zcmV;D0&V??P)PcK^Vv1_a$>^c-sPz zYI2Z38j`4VD$t@J-I_B00Clao*8B&`Cbt%$4B9jyYzP+zCk<(6b8tz>z9)~Qm#?0$ zKhA?Mr|0q9$LF5=@toRhHkDZ_bHjA5`A-*6c_26Bf*gWHDi{>&D6FAi0VI%)d?GRkFwII)yz%p1FWdCbW>+cF%ixs@If7MPRgL zn=-$*qgXcATi+Y6@5=Y{i}LgFDGK!k#rjSe1#m>fqV;fBu?BpKW?O2BeP(S#6gE!S z5Qvs`NHk_Fo(-Y40WlS#aeI0wILy*+WZ#6^1Xf?Li;^83v;#l(+4RO8L!E_fp8MFb z>zzP8d`?R|!u$Yw>YOsSQO54YW$>cN>=AP4UuyJQd9c_3Xf zkj?|uBm>oWpsHk`Di2hW3{>KQ3X*{eJWyUTP@V_M(wQQoWN7?9AUfDml7Ld}gO$I2 zuv8^CWtbd|`~N-!$}*)Y(@JI8I0+iJ`#!{7W3TC6J?X;Ip%|aJY6cyX6NX9BID_*t z5VO?ea)MAvvR@HS6aiJFyeU4$m}-(eX18c~^B?paGh%Mvxte literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-hdpi/icon_personal_user.png b/app/src/main/res/drawable-hdpi/icon_personal_user.png new file mode 100644 index 0000000000000000000000000000000000000000..5ede653e1e429f93ea333585b71090d42ee10ef5 GIT binary patch literal 1070 zcmV+}1kwA6P)Qhoz<_PJ=geXnl|#W3W{Yu?Vze$3A7&O9L;4#QCrZ6eC0eJ_mos6$jEoCpUZ z4`D_G5PpOg;X#Zbh7h56HpiRpB~A#56yl=^QIBvbs97i~5jW{OH(~(MhnP-^hmg!& ze6%1g>|+O96pe@;MEAZ4A&~-nv>{p%!U02r{4=%GdtHbbB}GwzOgTQDQ9?qJ;#Hvb zh~08-4^huNM%+21MB20aNX}DoTh_)y#;Q++>-i42l3N6Jqe*(_3kBib+!Va|;f3YR zbv058i1xUIXaKjWk;yWoL8sFtk%^RJ1|w8ib0l=L;L?B9q!JYB56w z8||fV@pyrv4jCglf_hA~&~;^lx``*X({*|{iyqL5qH^lu81WL(<;cFK7((;}uIGtJ zONt&cQVbk=1~8}Sc}&*R^`ksg7v}_yk`z$`T0vCR(+C3SM3MLKtYZmP^XBX>grz?o zN#NeDW(hfYXRdB-LU4OaRgdTdwl{grI$1&v-kHDfeEGV%7}rN+jOeUxZSb0Pu!Qn> zXOhJ}{G7r=FrcVI#)!@?|DNTsgv^PY{i7@MFgQC2J2DHBcVvv{Bp_z?qz=$phFe~a ze+Ioq3k`M$nCN*E#F**#d8M%gCBPE$^Agfv%9mNJVAH2T_rxfCTAT$}w*5~vHt&;S z=X_Q!EN-sBx7B6z*e;J*KTF6_LLUue&tCJAVs}Cz{pg7$E zPdp!CeP>%vZG_FTAzF~fNV(Ytw+hM*T$MH8?SICaEjQ$*>;EH!tw@M=2QgBWmBV`{ zsorh1HreBDfUX}?evnpWnAJX=nVq!SwKBuTz8a?M##W5TavIBk9@-rUkyRiG+GO;% zrs(>JnCv0zMpbQWD~+U{)4KYwg|tKMZ09N}DE1yQnO?e%dS8Z&No(@-5J~k?`!n2) zB9a=TeNz|R>tsln+9P(y6f1wV&oKw=?IW{EwzubrA$9^W(v0X(Iy~cuo6c+I`Ov`S oq5u7TNH}U=&_p<5@>_rb0Gr1!uq)2#k^lez07*qoM6N<$f-*DmNdN!< literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable-xhdpi/icon_login_pwd.png b/app/src/main/res/drawable-xhdpi/icon_login_pwd.png new file mode 100644 index 0000000000000000000000000000000000000000..fe767e9bf87196f8896e3be431bbab1d6c434516 GIT binary patch literal 1186 zcmV;T1YP@yP)1G{?+81NafZ;pXP%iJ_sPKb8x8 zS%#3~IF|?^b^^EtfLKf%Gao0S0{}K@tq(61WJv(F#{ukM=7)(WmM>;z0520!zt%ci zEl8}Or9oWRT@wVsULrcT_??+^0NxiuyvfYph6rBW9F=mKy%5uH+E z^Q{nKz3=;9RRd8KfRyq!0B;jfvkl73zY&q&)YPOajqibhfzxMaXB}qlBcjtn(Z`ti zE@RBzxDc@cq?8vD(IEgULh+x?&CT61Iy(AGoZx+ZeJcxvg3X|v@U|QSaHZDz%Q${w z0rdCxpPJ9-zXq@>4DwD}Tie~9=jG!BEw=SMuPK|&?gns2*cp>8EiIjcgM-si{lx;1 zQtl+8jbX4u>2!K+wc?8cdwYAEQ>oNJ0GEb-Uopl!vg`m#sq^f_B%(%Uo+YByT5G$u zFS+#e^mL?Bsqcz&iij@vegD%ajw5G4N;yPCo5Eh&Zj5;*N`R>>dur7i~W zMc6y<8DrL0a$HTlQtA@`YeUxF|U{E zISIgVoX3R_w%0B3hD;{2D@lM#dg=h>veVVomFno|7$l;5n0ZnNvBmfOiE1*LNCuQr zy#O)`1h3Ot?~4nO6u?ISZdf38pVoR^tpI!s;JQ+=%v{wODv=C0j&q+7;*BDJz3dAi zu8n%eF9lBozzUF3K1D?Hdk8Z>X^ipX4jsXOq|vssn^QP|rXuo<>h>Ld?9)7-P@li4`g3W+HknJl@}% z$zE*qAa zcNk;(7gm8<6SRu+91nx3A%ikasx)JYT~%FWEo z932}QvwwcdmPjviNr;acvknlor~a*WHJ^dM0Gbh{PZdSu`~Uy|07*qoM6N<$f<*Kt A?f?J) literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xhdpi/icon_login_user.png b/app/src/main/res/drawable-xhdpi/icon_login_user.png new file mode 100644 index 0000000000000000000000000000000000000000..35fba6ef1552f41dabbd47131d49c1447f982ae2 GIT binary patch literal 1597 zcmV-D2EzG?P)q&|TFqq6CnRxG^AI)jbQ2 zTST8g1p@}b7mX4F51=gk4| z7!h64hS8%$w3eAyR8&-~O{G#nF<`n3kV>UW8yg#|h-f~5@-U{Dc{dTguC;!rtFzK2 z04ZgEW?lneL`&n$ybr)8X8sMpG5hQ`BD%Fz+gx8n7)U0QgPC~?5nXI` zgw{IaqG4j40g}n&Rm}V|5qZp<129S{WsEqtWHLFHnb+Hn$4{R=J*d9EzR^X<89)e8 z58ydFYKyhjvt1Ilj+Ig_C!(k9{}(EyUUJcM1%MFZAb>v1><2;6JDbfGP8pZHXJcp0 znl;Gx{on0?Y|~l~brEs`h{xkM#bU9-a`{Rrb-PQl_F{z)n*rQno;Ni$^>^7%P5@HM zhluDCYvfg>)Vv}A)B>oqT4R(_8^h{40SF-~0n}RYDM~5xChc;QQqCo!SL`x+L~C6b zq)xS60cHTm78^haF%Q7P{{rAq04r>w`C9AdQR`}xQq~aBbgMI3DfL}gP1hM11K=Ag zzD6l^f7nDF#R(xc0vK&$1}LTWhShZfFgMR5M~;|_SC2CXMpK`pTOdQ&m+}-+Vs5orwC{gn#(He`hwE-4(__B9XY-^E`9b-(bz} z%H?vyqU@(g04AwfN}lI^3m|4qpJL{BgCJ1tfyCqSvREuOo0;bVC@b{K=kvpBYHEH8 z1K&C(GJxICcbJcKbGa)BGcyC&U_L2*-#4d=`MUzZHOzch;r?&q|MGqRzHBzTHH!Jj zOQ>ZsTaCy7c#!!_)#S{H*nTr{06qmUC7n(mj%vQk0Fx$7D(}^+*Aya}Lqz=@jr?p?leptW-bSCArV~!;IcN1 zZqMiQ<7#SZOsU1WMFL195|aVERj4cwk=elSGxK}Bd-vYjUeRjCj2Q!BvDo7P1Q88t z;r4hC1QRovjCn~qHD>@Rr72=9Zjoi3@B0(8+3dlvnN9hzv9a+2&(Du!%Z~lTtoSL~ol)2NC6c-ya$FXblBWSy|byw6yd`BI;#~1VM05CX@N180HHa zLWn5<8thE$E-Nb=T3uazyt5pk0Hl;35z%-nzD#R9tux^$&!v>+R2ygehAOdi3SerW z0L&3*3R(M0N=mL=vSf)lB8%Sa9qUgbDkY-BzV8pnX0xX|N)!fQSz(hAQKeFvZA4(A=jM%wsg7E=J;AEWyZg+p? zgXs+b&7Z2wJXUM{WmJG60L*c@qpYlKd`BZuOt54!c^xww)B8=eBArg3iUd$hQ{AQ2 v=_c;3#KqD*4^XU;jH+{f2BMB!vHJZBb%2adklgwx00000NkvXXu0mjf%(&}% literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_delete_fill.png b/app/src/main/res/drawable-xxhdpi/icon_delete_fill.png new file mode 100644 index 0000000000000000000000000000000000000000..3edd18cdb515ec3ae4d09f729f82a800cae0be01 GIT binary patch literal 1198 zcmV;f1X25mP)oS<-m(i0F)0Gyy?fi$d0SB#uR(i1d2LDLfyPSErOgcCH~k~pvP z7`tN}Cwh|QU?%Yl!y@|oK7H>`5{M1|V8ii?Edg5vY&Zj36WDM8Y@Og*OrY26?agMh z4?>6oL_9*oCIB?E>O;ga0MOs95MtD5H168%_M~csRz?7Uetdj927n6!?g(Uri2X1O ze^f<=cLHv1Zkmlo;}j7I_?{#1e76Yz^k=i#x2vnGv8OSghVe>3x7)q;419qIAw)+? z`NbCrUI?IF-`(B)UP9`&ct&v?pZHKiX#%v?EdcoHQSMwaCWv^hlp1n76tycwKomu1 zLI?u?lHa+Ma^SiEX98*hKE>@!f)fFW?8)Y9^C9Aii#b>mkT|?wFJSW~vL|sIAKE%b zYXYJuBJi$>jR{2A-$zo)Lz_XY2+&%00N~nYf_1k=#I{nZ&z+Y|K#irpkSy7l0-J!> zu=^R3^W%B!QJ1a0#&X{CNUs@g@@T z;Hi(J`lu@_A{Yrs7VyWCeUkA|DRq+Xw~T}$@Cj)b0MN6YpV|jO@YZAoMgmG^eFG`w zxru~j;9X2FI~ONA5=TGp=w3IGKZIfHsuK}qqQCZzTTaNcn=V22-$KpFXJLC&&&*pq!Vvk|7`&J5g zEh?z&VkDERTSo-;_LXeN_59gs*d~y6s;eoG^yl;WLFJdDi7C*lkj=avPnF=384h3Y7*JY zQ`a%96Cut-l(*AE#;Vki?0U!M0fcGYrF_5&HPG`pDq_53jR|miGrVw#VghLT-8%v4 zgvmKVi=NK9ng&Uo-UtUlFys_ACa~V40C&Qqnxv}k008Je9aQEIS|5ZEV?=yk>hB5$rwue0>Y*DC2jySF}}0g4Z#H<${s#Hr-qo2?GD)h zV!-?UAACLq8!=%&H!yktz<6(BlQN?8{~KoB&v{7}01-JSBCPduZfDGlq{-CG0A?)x zp=8PI3ON9t!t8f2J}@)Jn`|Zkv9S4>6Zlm1I)s2E2@UHs3L{ff)gS1KfF>g7i`pWm zAt3Hr3MeZ8Pr`!%{3~3nhD75JyTQj&`Zk2{EQdhM5|as%6@Vx5KLCKirywjFANwH1 z&Qf0lWm*xE0HXYVXVS;xSIm60+;0rRkns@!_7grjzK(l!WTz&%Q3-+$#TGw+rH&{E5L{Z~S$HS_)PGV{twwkV(`fKk5`5lB@k z!oGjitN@G!yw2!Up5TEq8vrTer-D!nplw^Ep)Wo3yFM$HI;6adRG#4dwyOSWnr3AH zPsV`KWzewBo026Dgj&m>I}tJ%pUM+Fm0+J$c_QT90L?IdN(fa_VN_$DVu+Xh+px|* za)*L)7+}kbA-tt+TO3!v%JjnrA2%33ssv9q3I#Szlia*b20@G}WAJ@O$nno&|9A0p z*UAvSkIl_H5%HG8ro$2|09It17x}j6Dy0EA)>1KCxoz*+6zEm6M#62 z?$y9L?G>L(M*r?xIRJLz)s@`6xr%nBUzIcaSqgYUDwyVAq+{<~5ziJY^ zh_jB1lI9B#DL_>L#JGShsAE+ug=KTL!6tD#_ tV**X`~{dTF<%%?+6Djs002ovPDHLkV1j+O@c;k- literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_personal_announcement.png b/app/src/main/res/drawable-xxhdpi/icon_personal_announcement.png new file mode 100644 index 0000000000000000000000000000000000000000..95e6aeb874c0566ae2fd881aafa3ce9a699d1e1c GIT binary patch literal 1967 zcmV;g2T=HlP)XTgk>~ zEr~RiVj7!v1fd7Z!H zogn-HyS`GZ^kMa!1c;4t-_<&2Elo&TvXbWPvj*jBm3XdtI04d=fW@Y;EXPXwW=Z2}GIIn&2P!?O<=?e<2abA$w%yH^cTU3-#- z_JilqojdG~P6t{^hActIAgiIIs6DEJrK7imMJcS#A{3S`< zffI zyxa_}RvTW^Lvnm1%(^=Uh7Al;9`2HB%bFSQM$XSw*J1VE^SswFO^Tq@Kw6BZL9;Ey zz+0>o9O+#`fI}y*z-tF;MJRfz)D|=LEEi-q;k+oS3azrO?@Aq0t0ilAtvt!Z+&vEv@pm{=`IaNhNJk8e~HMI(f}9~plxtC0#^HBWW90d0+gO>6h%^- zI)YMmsJL$K2>UUS04bFsh~2?%5xu#yKdS`O#uISM;$MNw5e$A?e$AP<>M{Ag@90f6w0b8&v*v`?stHwIV&9$IyyBMbb>zvkz@* z`qM>w&O%w$bw$5jf0vb!9T(asg*6gX7I<-f3 z?(!|jeCrHY+B&o%r;g?h-ZV%PEpmFKt-m_mxYpVuM^Lt!F27sEeWLpVg@UV=H8f}6 z2trk3dd|*;G|_jKXZ=!r!|mGDCSlIiK3AG^_wYilT#6oz#RSu}(xg4hlHs1a`a?zi zEm;0ug{W>ykQ|TqBzP;3y$GGR?rUvJEU6Wv7v&C#RC7I2YSGju*g+XkU0Y)-pr zj>-rklJf9le+Ux9=9K5{OFkU|AW7_Dv|}Me4x5Wl57_y0M#3IOGlu{MC>X^BkwP(owN3#1_)C;iOuu~2=3mI-c`#B&3UDPV`f z^Hzv$2v#}Y1sc?osLx$AhHbnW)eBJVwFvecWiFyzaGKIaedJP~Yk6;Vk?#-MOi=QI zB{{J`h`eCQSx#`7M2~w#m#{eK|02Bvd6EAF7yxO0Ai54e72*H@002ovPDHLkV1mvE BrMv(D literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_personal_download_files.png b/app/src/main/res/drawable-xxhdpi/icon_personal_download_files.png new file mode 100644 index 0000000000000000000000000000000000000000..2afadcd8c715f82d3a65f16c0926f0c7cc285c57 GIT binary patch literal 1707 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 Eg4H>oyZ`_I literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_personal_sex.png b/app/src/main/res/drawable-xxhdpi/icon_personal_sex.png new file mode 100644 index 0000000000000000000000000000000000000000..d16c37815ffdae9b4cbd6aae764b784effe0014d GIT binary patch literal 2162 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_personal_user.png b/app/src/main/res/drawable-xxhdpi/icon_personal_user.png new file mode 100644 index 0000000000000000000000000000000000000000..40420631ff5a59ee55db08932e9b987c91ce422b GIT binary patch literal 1793 zcmV+c2mbhpP)_nz~dbAIx_uwBf z_aq361x5`NHGddN@-*ZN$O>c$G7FiEv?F7YHcm%wJ&pVuIf@)Yb|c%7Cy>935}?<) zO|C#1P>I}xtU~4@6Ex(paov)bBxD)eX+pY@=a7eyZI7d;;MJwiExfa3HqQlp%)eM0ML4mKcQM9Kk$br=~4t@&uI}6_`-;CZbOm`4{D8Y zalA!p6)F%1P+N`&3DP{2*U6}5YQjR;xV#9S%*cVn1#wWg=p|lKMD|{4UZMJ~vrm=pXT>J$N zcKswnq!DB-uIOEQyVwEI;*cO8l|KtxiyjLq&TM*0OL?@U*D+5zpeqKU_t1J~t>_xP zTCf^w9teF!LU|~!1gzz@Y8MDI(C1r3FCXl&3zTl97nGN5NLZF^;Wlgbphg)B&vls( z!s57i-T5i+(^(J6THrQ@B9K$c1Y~KJVH;&>nX-m{rzU~W)ppC;&nZbxvxE&laB$m$ zdXQ6A4VKvx4ckbGUnp||=?o|kDoA@M#j;9Ij<*}OK@&=eC}s`(deA1B`#W(l#s|ek zas`TG4g3mJCA0LY8RL8D)1DcSj4D-u(6!P)@}BbW#2v#nu1$=|7ATjsnCC%s;+Y_` z0*N$S(Et&}Bn$i1LyR6HuZiP?qdc zNB^0GZ?5(0&QE#Ch6%`06DZk?k`MKq4@=%qr)+8h+07`Kb_-8ly%cgj%0qcgf?Z9Z zShGssf1w?In;6%ep3+ht6A`N>kj<>pX^(lU>nlz1gwjx2N&C|NU<? zv4L)7XwO`fq?~wsdwswxT|8<6O@qW6XSCs7n_UF26s%GjO9E~F;gk|zj`nvdRNW$3 zmlN?Vbht$3I3E&!!%>i!vyU+Ix0&{(f?+hSjV21QR6X%D)gbj@7X z5e@2!2fKcRGsFF&O%I_!4&N>|BuXIKdwyd@Ijl-aml`NY_(N-+fsWfF@P4NYJ#k2w zKx(5{w+PF%%M0OfqL39+ zyP80BgWb?dkb*IZ6fRIOe>s1ZQ8SaQ#oYp7P@?PoZlm2R+<4wmVDt>%&046=q|fQK zO2e|mGpmrg`RddRBPQt^HGn{i)n+}GSKPPMIszr9>diW@?ehde1?j1o!B8z1>HpP)!?9?Io@|5McE786 zUnRGPV!iCtXAt2~?K%GTgLW7l@*@mxZnHLl(ADT{=2H;(+?9o)w3XXD55aD^(wv|& z$^~jeas1v%2)5<=Cj5U@Hd-;lZ3JBc@%`Z@y^}|agWA*-=#I1&4aUB??sk^z9@3~B^2sWB60!#WJsKgS+Z5Ne210yZRhK|Mf%{#H&yz;Dj`eyC7&@Eh zDFF59Hj6DwX1GqZT846I0(k{KnyT-z$O6~Rb*U0DpoqNPu^*X-`~Y<9CkenHmz~FT z5rP;AuHAslM(#mgkQ~UTxQsnqW`pRiF7y3CdzpG(uoMWB5G5~In%hqBYJ}xp(G?7~ jdvC!rHwHx={u5vT|K{`LXYF!#00000NkvXXu0mjfJNQ}9 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/icon_right.png b/app/src/main/res/drawable-xxhdpi/icon_right.png new file mode 100644 index 0000000000000000000000000000000000000000..d071f4361815f99c22cb0db5b134d06767063879 GIT binary patch literal 2883 zcmV-J3%vA+P)EX>4Tx0C?J+Q)g6D=@vcr-tj1^HV42lZa2jn55j)S9!ipu-pd!uXCy!YnK{> z2n?1;Gf_2w45>mM5#WQz#Kz&|EGkvK~TfD`~gdX7S-06<0ofSs5oQvjd@0AR~wV&ec% zEdXFAf9BHwfSvf6djSAjlpz%XppgI|6J>}*0BAb^tj|`8MF3bZ02F3R#5n-iEdVe{ zS7t~6u(trf&JYW-00;~KFj0twDF6g}0AR=?BX|IWnE(_<@>e|ZE3OddDgXd@nX){& zBsoQaTL>+22Uk}v9w^R97b_GtVFF>AKrX_0nHe&HG!NkO%m4tOkrff(gY*4(&JM25 z&Nhy=4qq+mzXtyzVq)X|<DpKGaQJ>aJVl|9x!Kv}EM4F8AGNmGkLXs)P zCDQ+7;@>R$13uq10I+I40eg`xs9j?N_Dd%aSaiVR_W%I$yKlkNCzL=651DUOSSq$Ed=-((3YAKgCY2j1FI1_jrmEhm z3sv(~%T$l4UQ>OpMpZLYTc&xiMv2YpRx)mRPGut5K^*>%BIv?Wdil zy+ylO`+*KY$4Vz$Cr4+G&IO(4Q`uA9rwXSQO+7mGt}d!;r5mBUM0dY#r|y`ZzFvTy zOmC;&dA;ZQ9DOhSRQ+xGr}ak+SO&8UBnI0I&KNw!HF0k|9WTe*@liuv!$3o&VU=N* z;e?U7(LAHoMvX=fjA_PP<0Rv4#%;!P6gpNq-kQ#w?mvCS^p@!_XIRe=&)75LwiC-K#A%&Vo6|>U7iYP1 zgY$@siA#dZE|)$on;XX6$i3uBboFsv;d;{botv|p!tJQrukJSPY3_&IpUgC$DV|v~ zbI`-cL*P;6(LW2Hl`w1HtbR{JPl0E(=OZs;FOgTR*RZ#xcdGYc?-xGyK60PqKI1$$ z-ZI`wBrnsy*W_HW0Wrec-#cqqYFCLW#$!oKa ztOZ#u3bsO~=u}!L*D43HXJuDrzs-rtIhL!QE6wf9v&!3$H=OUE|LqdO65*1zrG`sa zEge|qy{u|EvOIBl+X~|q1uKSD2CO`|inc0k)laMKSC_7Sy(W51Yk^+D%7VeQ0c-0E zRSM;Wee2xU?Ojh;FInHUVfu!h8$K0@imnvf7nc=(*eKk1(e4|2y!JHg)!SRV_x(P}zS~s+RZZ1q)n)rh`?L2yu8FGY z_?G)^U9C=SaqY(g(gXbmBM!FLxzyDi(mhmCkJc;eM-ImyzW$x>cP$Mz4ONYt#^NJz zM0w=t_X*$k9t}F$c8q(h;Rn+nb{%IOFKR-X@|s4QQ=0o*Vq3aT%s$c9>fU<%N829{ zoHRUHc}nwC$!Xf@g42^{^3RN&m7RTlF8SPG+oHC6=VQ*_Y7cMkx)5~X(nbG^=R3SR z&Rp`ibn>#>OB6F(@)2{oV%K?xm;_x?s~noduI3P8=g1L-SoYA z@fQEq)t)&$-M#aAZ}-Lb_1_lVesU-M&da;mcPH+xyidGe^g!)F*+boj)jwPQ+}Q8j ze`>&Yp!3n(NB0JWgU|kv^^Xrj1&^7J%Z3ex>z+71IXU7#a{cN2r$f(V&nBK1{-XZN zt``^}my^G3e5L*B!0Q>W+s4Ai9=^$VGcjKDR{QP2cieX!@1x%j zPvm?ce<=TG`LXp=(5L&88IzO$1Ou4!{O>iCf&c&j8FWQhbW?9;ba!ELWdK2BZ(?O2 zNo`?gWm08fWO;GPWjp`?0F+5YK~#9!z#t3*p>y+O@ATUzu`dczv%npZFlI)? z=$dCV&Jld?9jzy?5J;_^7C3^hwPuCD@yRO$QfsFL(pQZXknTApAcqVgU?rR@V8^Q? zfMmNWfITdV!2PdUHut|`BAQ`l zd`=S4e-qJ85qU;y{XJ$rvKl?LDnMGyk{`2YBMqBBF0aQS^n0iHZA|dCn8x!rAl$0GiF_rStRi*E93G z3in9&0upBayOEKR+s4Mm4s_41SPsDgKtv*A%sYwbBkei8SWo!pj#1#tB64dS$2j8 zcM;LGTI=srAp3_RD+2(QbaQia_Yx7-@%>5&6mQa6f1)Y}R005N?Y35{bq_OdsLJrb z(W!01+8B#(a~R-&~{ttDLvAR|j7y z%FaRnfH~h^wOVcAGh~XQ=wjbT(Yyk{7=t1I z8=*N7ojx@+Mbp#M3!T0a0Su>{ate)&jnUe*YiVd`C^S=cDW%T$c|g@00H&v>PoJHg z{U0Jyp?K}ckt1Y`q1oAdn{EI>9LGV83?i~4j^khTIz!>vb2${aahY!8)h}>C< z*EgQ$PEBv%mfkr9}&fQh5LA})oRhc zeft*H^<@$QLre!%6EokYl=^U9J-PwFXNc&x^9uI$9Oit?{Z$Zx!qL&uDn!3R=Frg4 zt0yKVevn6zTmYDwnu_P<=5Xs}B#%&E&)`?TckkZPhW?ZF##DfKAON~UYyE*ddgKCt zG3FK``n@~?eLjQb9#?0cPGnjMluLVp+KmqlseV5OpP-Dj4>!~ za7iFWE&#yymTz9$0viEf#@n>kPA9XR03af3t+mLv*77>Z_jAYq&dr5Uiz%cS0DflX zPbsBtaPhel0ForRoSD6!NOe(gr8a|Az{=hektgFgMy8XPHu^chR8A zO`wz24kZt;M6Z@o3PC%no^~WB03=EB@67zVG-hRfj~oC-fvPE#OyDwpa5^`b3IOE( zt+kobi)D2R_5yGoph^lb09*{6naB7P05wMADjp1=()XG>$B2)n+gD93@Uh`l2?#aWiYsLaDm@PAs`p2=)*Wwbxl92o zU#E3dqoj7tW^>^(Xi^|WwD0JJZEG+m{I(hgZdfWX&*kpOQ8JGoj!?v3NP<4ZO=0-$~8zxtDxPUik- zqt#d+Ad4$R% zE0{I{Y?z&$J=n`mI9YeZ1~^B6>0xn-K;Z|>@%US_rt(|vKHZIX9^##eD^gkzIBu;C zU3i&=}J*%fSBWS7%Ef)LM6})tH(GNRs4m)z`qB&F#<-DPzq4 z_+0vj;U9B7hJSqa`n-RV3%;VYzPhUsAe5UxSwVnK#wq~8AC`r#OF|@jkmW!u78#2yiJL;Q|1>^DgyZK^Db| zXp0U<$EPjxx{-}(Vq#)q0Svt`Dlqehlu{_>bb)^Di*XvYCKX;Eo8IOu_S{)HD_sBFkjb0&&L;k-Zy zxdajUVjRcsPt(~I0AH{A1rVqKjLz?tkn2YWE$06bKVUS$w1R*#Z)NdCy}%ncg{uYu zGhe5aN-g5)1ORL8heYJBx^;fu=ey~AbTp!M*Q5?WfY<_~4dmvTYC-_P@bK_y>(;GH zt)1%%fD1&VOKW|0ns4#F69C$A z0LpM{7pA`2U|4?s`R6m0fkhC8H9C?tz5=qmOU|kaN4TVb7yHN=09Din%6bJH4o4(j z)T>^wpMd43>%Ib8&n0J|L@hm0kF#Vi)=9#&t zp&Y(Eu9VWQ?-g$&Hib6)PDYB$#5Wm1+S=q2AEzDsL;t*bAb z0nm;EK1)Q`yE4z)4(VMu*o7(@Vi#1y9t1`z7b@!|6U=<6QVK8eaH32An4Fw^{rvp= zzd3Qv*(gg5U=(LWz*i+9CUDt^!d5cklOjn(4#shusa#nm06_U9Np>=GrfQT~jCLJb zk#)lrEnks-79E$i#YzQ5D_!yhhy{vF5Ha(gD5d^1i!FHo@N3Nc`7A=Rv~{TficZHX zTSD1|b4^qsFM(jSAVSIbP4#*`(_6DL0|0LYx7O|_qEm}7#H9jgXl2>phyDBarzVSV z*8nj~L6oUN=%Nmp0e}aMF{nNB@gfXKtN({zKZeqx*7|vEaw%F>U{PrlOguYn?|(Vl zasfa@#*Hy38}I!{Oj=}(qRbcpLZpKS4=x-+U5N_3R{r-|>-XmINiG1igTX&z=0D6M zw9_*PrZAi?mu*8{|I||pnR&dJfsG{SzT6wkid?aX$WO#^yfcpxxdDKyX zRDlOX4d2t*+->o<=0BC~%<;Z@iTZIPC zr+Prz7d{?IZRya!8vsP)6l?7>M0AdW+(DE49=Jto{rmYJf8~?j006d!4<9~vc6RnD zW=_43;LCtMJn!5R-F#ZHn|W0K*=OYzQD|>H&c8YwA%QI$gx_jwryf!;y;S8 ztuO$H2wQ7WY5b}p6z>zB^CEI(9LEnAWp7~sSX4Mkl1G^N%|#ion)x#GwMwZ%{ z&^}Fk{~%qQ3955Z6urCKG_Q-q!T|s!L}bVqb2l@0tXu1%a38ceE+X6GIDV)MTT21J zlIdUvGrzA4!+KGMh|Dna~vpJl3DIsn>v zf^Dr<>z*PGla%Q^kFxGpO1+1fOMgFOB>;fqrlzLW&&|zYFZ)d%9ocKFW|{d0rPS?J zDP9=>77e!6{*j1ey3A0OvwX@Gk^dbU8oH*@XguMQZ&qHF1E4)6Y!wmw3f`90fL?1a zB2!Tm{c)qwxSg5jtJXgV04zG8*=%0cYPCK`L|dzNOabz-gLhZ0R{LxCpq&S2*Ai0DEh`bi?fiUL3x6Ol1|E&+)pqG@c#;1j3Xh-haNMbAW0^j}4t z#t8vEs}=wuIJXyc7yzIbdZ|Di2LLG0xxK)80DxZTr2=&v0H8qU_5$kx0D7U93e<4` nfC8P{3# + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/common_bg_with_radius_and_border.xml b/app/src/main/res/drawable/common_bg_with_radius_and_border.xml new file mode 100644 index 00000000..2daa8af1 --- /dev/null +++ b/app/src/main/res/drawable/common_bg_with_radius_and_border.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/delete_selector.xml b/app/src/main/res/drawable/delete_selector.xml new file mode 100644 index 00000000..ca9460fc --- /dev/null +++ b/app/src/main/res/drawable/delete_selector.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml new file mode 100644 index 00000000..46fc8dee --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 00000000..f8bb0b55 --- /dev/null +++ b/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,9 @@ + + + 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 00000000..0d025f9b --- /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_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 00000000..78b75c39 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/icon_home1_selector.xml b/app/src/main/res/drawable/icon_home1_selector.xml new file mode 100644 index 00000000..51468f1e --- /dev/null +++ b/app/src/main/res/drawable/icon_home1_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_home2_selector.xml b/app/src/main/res/drawable/icon_home2_selector.xml new file mode 100644 index 00000000..52ecd23a --- /dev/null +++ b/app/src/main/res/drawable/icon_home2_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/icon_home_my_selector.xml b/app/src/main/res/drawable/icon_home_my_selector.xml new file mode 100644 index 00000000..2abf8456 --- /dev/null +++ b/app/src/main/res/drawable/icon_home_my_selector.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/radius_button_bg.xml b/app/src/main/res/drawable/radius_button_bg.xml new file mode 100644 index 00000000..16ca8fd8 --- /dev/null +++ b/app/src/main/res/drawable/radius_button_bg.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/radius_button_bg_pressed.xml b/app/src/main/res/drawable/radius_button_bg_pressed.xml new file mode 100644 index 00000000..b3cb51cf --- /dev/null +++ b/app/src/main/res/drawable/radius_button_bg_pressed.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/s_radius_button_bg.xml b/app/src/main/res/drawable/s_radius_button_bg.xml new file mode 100644 index 00000000..c64a0d8a --- /dev/null +++ b/app/src/main/res/drawable/s_radius_button_bg.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/app/src/main/res/drawable/tab_panel_bg.xml b/app/src/main/res/drawable/tab_panel_bg.xml new file mode 100644 index 00000000..388c57af --- /dev/null +++ b/app/src/main/res/drawable/tab_panel_bg.xml @@ -0,0 +1,15 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/welcome.xml b/app/src/main/res/drawable/welcome.xml new file mode 100644 index 00000000..3db75348 --- /dev/null +++ b/app/src/main/res/drawable/welcome.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_bottom_navigation.xml b/app/src/main/res/layout/activity_bottom_navigation.xml new file mode 100644 index 00000000..3068c8d2 --- /dev/null +++ b/app/src/main/res/layout/activity_bottom_navigation.xml @@ -0,0 +1,25 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_bottom_navigation2.xml b/app/src/main/res/layout/activity_bottom_navigation2.xml new file mode 100644 index 00000000..ef5abdf4 --- /dev/null +++ b/app/src/main/res/layout/activity_bottom_navigation2.xml @@ -0,0 +1,28 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 00000000..8f9b3327 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,214 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +