From 256a13b7df4a0e15f7d7c39f880fd5dde6f6dcb3 Mon Sep 17 00:00:00 2001 From: lunaticbum <> Date: Mon, 11 Nov 2024 18:12:06 +0900 Subject: [PATCH] ... --- annotations/.gitignore | 1 + annotations/build.gradle | 15 + app/build.gradle.kts | 2 + app/src/main/AndroidManifest.xml | 10 +- .../lunatic/launcher/home/LauncherHome.kt | 660 ++++--- .../bums/lunatic/launcher/home/RssViewer.kt | 121 ++ .../launcher/home/adapters/RssItemAdapter.kt | 41 +- .../bums/lunatic/launcher/utils/RssList.kt | 8 +- .../lunatic/launcher/workers/ArcaGetter.kt | 4 +- .../lunatic/launcher/workers/ClienGetter.kt | 2 +- .../lunatic/launcher/workers/DotaxGetter.kt | 2 +- .../lunatic/launcher/workers/TheQooGetter.kt | 2 +- .../lunatic/launcher/workers/WorkersDb.kt | 4 +- app/src/main/res/layout/behavior.xml | 24 +- app/src/main/res/layout/launcher_home.xml | 6 + app/src/main/res/layout/rss_viewer.xml | 26 + app/src/main/res/values/styles.xml | 7 + build.gradle.kts | 18 + gradle.properties | 3 +- library/.gitignore | 2 + library/build.gradle | 42 + library/maven_publish.gradle | 16 + library/proguard-rules.pro | 25 + library/src/main/AndroidManifest.xml | 14 + .../wuadam/awesomewebview/AwesomeWebView.java | 1016 ++++++++++ .../AwesomeWebViewActivity.java | 1748 +++++++++++++++++ .../wuadam/awesomewebview/enums/Position.java | 11 + .../helpers/Base64ImgHelper.java | 25 + .../awesomewebview/helpers/BitmapHelper.java | 69 + .../awesomewebview/helpers/ColorHelper.java | 21 + .../awesomewebview/helpers/DownPicUtil.java | 248 +++ .../helpers/FileProvider4WebView.java | 9 + .../awesomewebview/helpers/FormatHelper.java | 94 + .../awesomewebview/helpers/Md5Helper.java | 69 + .../helpers/PermissionHelper.java | 78 + .../helpers/TypefaceHelper.java | 48 + .../awesomewebview/helpers/UrlParser.java | 22 + .../jsInterface/BaseJsInterface.java | 19 + .../jsInterface/CommonJsHelper.java | 61 + .../jsInterface/VideoJsHelper.java | 67 + .../listeners/BroadCastManager.java | 224 +++ .../listeners/WebViewListener.java | 38 + .../awesomewebview/objects/CustomMenu.java | 23 + .../awesomewebview/views/ShadowLayout.java | 187 ++ .../views/VideoEnabledWebChromeClient.java | 283 +++ .../views/VideoEnabledWebView.java | 101 + .../src/main/res/anim/accelerate_cubic.xml | 3 + .../src/main/res/anim/accelerate_quart.xml | 3 + .../src/main/res/anim/accelerate_quint.xml | 4 + .../main/res/anim/activity_close_enter.xml | 23 + .../src/main/res/anim/activity_close_exit.xml | 9 + .../src/main/res/anim/activity_open_enter.xml | 9 + .../src/main/res/anim/activity_open_exit.xml | 23 + .../src/main/res/anim/decelerate_cubic.xml | 3 + .../src/main/res/anim/decelerate_quart.xml | 4 + .../src/main/res/anim/decelerate_quint.xml | 4 + library/src/main/res/anim/fade_in_fast.xml | 8 + library/src/main/res/anim/fade_in_medium.xml | 8 + library/src/main/res/anim/fade_out_fast.xml | 8 + library/src/main/res/anim/fade_out_medium.xml | 9 + .../main/res/anim/fragment_close_enter.xml | 23 + .../src/main/res/anim/fragment_close_exit.xml | 23 + .../src/main/res/anim/fragment_open_enter.xml | 22 + .../src/main/res/anim/fragment_open_exit.xml | 22 + library/src/main/res/anim/hold.xml | 8 + .../res/anim/modal_activity_close_enter.xml | 23 + .../res/anim/modal_activity_close_exit.xml | 9 + .../res/anim/modal_activity_open_enter.xml | 9 + .../res/anim/modal_activity_open_exit.xml | 23 + library/src/main/res/anim/none.xml | 6 + .../src/main/res/anim/popup_flyout_hide.xml | 14 + .../src/main/res/anim/popup_flyout_show.xml | 15 + library/src/main/res/anim/slide_down.xml | 8 + library/src/main/res/anim/slide_left_in.xml | 8 + library/src/main/res/anim/slide_right_out.xml | 8 + library/src/main/res/anim/slide_up.xml | 8 + library/src/main/res/drawable-hdpi/back.png | Bin 0 -> 3304 bytes library/src/main/res/drawable-hdpi/close.png | Bin 0 -> 3176 bytes .../src/main/res/drawable-hdpi/forward.png | Bin 0 -> 3319 bytes library/src/main/res/drawable-hdpi/more.png | Bin 0 -> 3039 bytes library/src/main/res/drawable-mdpi/back.png | Bin 0 -> 3121 bytes library/src/main/res/drawable-mdpi/close.png | Bin 0 -> 3170 bytes .../src/main/res/drawable-mdpi/forward.png | Bin 0 -> 3111 bytes library/src/main/res/drawable-mdpi/more.png | Bin 0 -> 2911 bytes .../res/drawable-v21/selector_dark_theme.xml | 5 + .../drawable-v21/selector_dark_theme_holo.xml | 14 + .../res/drawable-v21/selector_light_theme.xml | 5 + .../selector_light_theme_holo.xml | 14 + library/src/main/res/drawable-xhdpi/back.png | Bin 0 -> 3480 bytes library/src/main/res/drawable-xhdpi/close.png | Bin 0 -> 3265 bytes .../src/main/res/drawable-xhdpi/forward.png | Bin 0 -> 3499 bytes library/src/main/res/drawable-xhdpi/more.png | Bin 0 -> 3154 bytes library/src/main/res/drawable-xxhdpi/back.png | Bin 0 -> 3858 bytes .../src/main/res/drawable-xxhdpi/close.png | Bin 0 -> 4039 bytes .../src/main/res/drawable-xxhdpi/forward.png | Bin 0 -> 3879 bytes library/src/main/res/drawable-xxhdpi/more.png | Bin 0 -> 3350 bytes library/src/main/res/drawable/back_vector.xml | 11 + .../src/main/res/drawable/close_vector.xml | 11 + .../src/main/res/drawable/forward_vector.xml | 11 + library/src/main/res/drawable/more_vector.xml | 17 + .../main/res/drawable/progress_drawable.xml | 12 + .../main/res/drawable/selector_dark_theme.xml | 14 + .../res/drawable/selector_light_theme.xml | 14 + .../src/main/res/layout/awesome_web_view.xml | 75 + library/src/main/res/layout/menus.xml | 145 ++ .../src/main/res/layout/toolbar_content.xml | 92 + .../src/main/res/layout/view_custom_menu.xml | 27 + .../main/res/layout/view_loading_video.xml | 31 + library/src/main/res/values-es/strings.xml | 9 + library/src/main/res/values-ko/strings.xml | 9 + library/src/main/res/values-land/dimens.xml | 5 + library/src/main/res/values-ldrtl/bools.xml | 4 + .../src/main/res/values-pt-rBR/strings.xml | 9 + library/src/main/res/values/bools.xml | 4 + library/src/main/res/values/colors.xml | 20 + library/src/main/res/values/dimens.xml | 17 + library/src/main/res/values/strings.xml | 14 + library/src/main/res/values/styles.xml | 39 + library/src/main/res/xml/provider_paths.xml | 6 + settings.gradle.kts | 3 +- utils/.gitignore | 1 + utils/build.gradle | 46 + .../utils/ApplicationTest.java | 13 + .../utils/etc/PreferencesUtilTest.java | 278 +++ utils/src/main/AndroidManifest.xml | 1 + .../main/java/com/thefinestartist/Base.java | 57 + .../thefinestartist/annotations/Extra.java | 19 + .../thefinestartist/binders/ExtrasBinder.java | 56 + .../builders/ActivityBuilder.java | 82 + .../builders/BundleBuilder.java | 34 + .../com/thefinestartist/converters/Unit.java | 9 + .../converters/UnitConverter.java | 43 + .../com/thefinestartist/enums/LogLevel.java | 19 + .../com/thefinestartist/enums/Rotation.java | 29 + .../listeners/KeyboardStateListener.java | 14 + .../utils/content/ContextUtil.java | 502 +++++ .../thefinestartist/utils/content/Ctx.java | 9 + .../thefinestartist/utils/content/Res.java | 9 + .../utils/content/ResourcesUtil.java | 278 +++ .../utils/content/ThemeUtil.java | 64 + .../utils/content/TypedValueUtil.java | 29 + .../thefinestartist/utils/etc/APILevel.java | 237 +++ .../utils/etc/IntArrayUtil.java | 29 + .../utils/etc/PackageUtil.java | 58 + .../utils/etc/SparseArrayUtil.java | 24 + .../thefinestartist/utils/etc/ThreadUtil.java | 15 + .../utils/etc/TypefaceUtil.java | 55 + .../utils/log/AndroidLogPrinter.java | 9 + .../utils/log/FileLogPrinter.java | 40 + .../java/com/thefinestartist/utils/log/L.java | 9 + .../thefinestartist/utils/log/LogHelper.java | 702 +++++++ .../thefinestartist/utils/log/LogPrinter.java | 40 + .../thefinestartist/utils/log/LogUtil.java | 406 ++++ .../thefinestartist/utils/log/Settings.java | 109 + .../utils/preferences/Pref.java | 9 + .../utils/preferences/PreferencesUtil.java | 271 +++ .../utils/service/ClipboardManagerUtil.java | 57 + .../utils/service/ServiceUtil.java | 330 ++++ .../utils/service/VibratorUtil.java | 48 + .../utils/service/WindowManagerUtil.java | 21 + .../thefinestartist/utils/ui/DisplayUtil.java | 82 + .../thefinestartist/utils/ui/Keyboard.java | 9 + .../utils/ui/KeyboardUtil.java | 184 ++ .../thefinestartist/utils/ui/ViewUtil.java | 39 + .../java/com/thefinestartist/wip/AgeUtil.java | 29 + .../thefinestartist/wip/AudioManagerUtil.java | 42 + .../com/thefinestartist/wip/AwakeUtil.java | 52 + .../com/thefinestartist/wip/BitmapUtil.java | 213 ++ .../com/thefinestartist/wip/DateUtil.java | 87 + .../com/thefinestartist/wip/EmailUtil.java | 20 + .../com/thefinestartist/wip/FileUtil.java | 22 + .../thefinestartist/wip/LanguageDetector.java | 53 + .../com/thefinestartist/wip/NetworkUtil.java | 80 + .../com/thefinestartist/wip/PhotoUtil.java | 126 ++ .../com/thefinestartist/wip/RippleUtil.java | 18 + .../com/thefinestartist/wip/Validator.java | 48 + utils/src/main/res/anim/accelerate_cubic.xml | 3 + utils/src/main/res/anim/accelerate_quart.xml | 3 + utils/src/main/res/anim/accelerate_quint.xml | 4 + .../main/res/anim/activity_close_enter.xml | 23 + .../src/main/res/anim/activity_close_exit.xml | 9 + .../src/main/res/anim/activity_open_enter.xml | 9 + .../src/main/res/anim/activity_open_exit.xml | 23 + utils/src/main/res/anim/decelerate_cubic.xml | 3 + utils/src/main/res/anim/decelerate_quart.xml | 4 + utils/src/main/res/anim/decelerate_quint.xml | 4 + utils/src/main/res/anim/fade_in.xml | 8 + utils/src/main/res/anim/fade_in_fast.xml | 7 + utils/src/main/res/anim/fade_out.xml | 8 + utils/src/main/res/anim/fade_out_fast.xml | 7 + utils/src/main/res/anim/fade_out_slow.xml | 9 + .../src/main/res/anim/fragment_close_exit.xml | 23 + .../res/anim/fragment_close_exit_reverse.xml | 23 + .../src/main/res/anim/fragment_open_enter.xml | 22 + .../res/anim/fragment_open_enter_reverse.xml | 22 + .../res/anim/modal_activity_close_enter.xml | 23 + .../res/anim/modal_activity_close_exit.xml | 9 + .../res/anim/modal_activity_open_enter.xml | 9 + .../res/anim/modal_activity_open_exit.xml | 23 + utils/src/main/res/anim/no_anim.xml | 6 + utils/src/main/res/anim/splash_out.xml | 9 + utils/src/main/res/anim/view_blink.xml | 10 + utils/src/main/res/anim/view_bounce.xml | 13 + utils/src/main/res/anim/view_fade_in.xml | 11 + utils/src/main/res/anim/view_fade_out.xml | 11 + utils/src/main/res/anim/view_move.xml | 10 + utils/src/main/res/anim/view_rotate.xml | 13 + utils/src/main/res/anim/view_slide_down.xml | 13 + utils/src/main/res/anim/view_slide_up.xml | 13 + utils/src/main/res/anim/view_zoom_in.xml | 14 + utils/src/main/res/anim/view_zoom_out.xml | 14 + .../main/res/animator/card_flip_left_in.xml | 60 + .../main/res/animator/card_flip_left_out.xml | 52 + .../main/res/animator/card_flip_right_in.xml | 60 + .../main/res/animator/card_flip_right_out.xml | 52 + utils/src/main/res/values/colors_basic.xml | 145 ++ utils/src/main/res/values/colors_grey.xml | 315 +++ .../main/res/values/colors_transparent.xml | 91 + .../helpers/ExampleUnitTest.java | 15 + 219 files changed, 12544 insertions(+), 326 deletions(-) create mode 100644 annotations/.gitignore create mode 100644 annotations/build.gradle create mode 100644 app/src/main/kotlin/bums/lunatic/launcher/home/RssViewer.kt create mode 100644 app/src/main/res/layout/rss_viewer.xml create mode 100644 library/.gitignore create mode 100644 library/build.gradle create mode 100644 library/maven_publish.gradle create mode 100644 library/proguard-rules.pro create mode 100644 library/src/main/AndroidManifest.xml create mode 100644 library/src/main/java/com/wuadam/awesomewebview/AwesomeWebView.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/AwesomeWebViewActivity.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/enums/Position.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/Base64ImgHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/BitmapHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/ColorHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/DownPicUtil.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/FileProvider4WebView.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/FormatHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/Md5Helper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/PermissionHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/TypefaceHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/helpers/UrlParser.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/jsInterface/BaseJsInterface.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/jsInterface/CommonJsHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/jsInterface/VideoJsHelper.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/listeners/BroadCastManager.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/listeners/WebViewListener.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/objects/CustomMenu.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/views/ShadowLayout.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebChromeClient.java create mode 100644 library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebView.java create mode 100755 library/src/main/res/anim/accelerate_cubic.xml create mode 100755 library/src/main/res/anim/accelerate_quart.xml create mode 100755 library/src/main/res/anim/accelerate_quint.xml create mode 100755 library/src/main/res/anim/activity_close_enter.xml create mode 100755 library/src/main/res/anim/activity_close_exit.xml create mode 100755 library/src/main/res/anim/activity_open_enter.xml create mode 100755 library/src/main/res/anim/activity_open_exit.xml create mode 100755 library/src/main/res/anim/decelerate_cubic.xml create mode 100755 library/src/main/res/anim/decelerate_quart.xml create mode 100755 library/src/main/res/anim/decelerate_quint.xml create mode 100644 library/src/main/res/anim/fade_in_fast.xml create mode 100644 library/src/main/res/anim/fade_in_medium.xml create mode 100644 library/src/main/res/anim/fade_out_fast.xml create mode 100644 library/src/main/res/anim/fade_out_medium.xml create mode 100755 library/src/main/res/anim/fragment_close_enter.xml create mode 100755 library/src/main/res/anim/fragment_close_exit.xml create mode 100755 library/src/main/res/anim/fragment_open_enter.xml create mode 100755 library/src/main/res/anim/fragment_open_exit.xml create mode 100644 library/src/main/res/anim/hold.xml create mode 100755 library/src/main/res/anim/modal_activity_close_enter.xml create mode 100755 library/src/main/res/anim/modal_activity_close_exit.xml create mode 100755 library/src/main/res/anim/modal_activity_open_enter.xml create mode 100755 library/src/main/res/anim/modal_activity_open_exit.xml create mode 100644 library/src/main/res/anim/none.xml create mode 100644 library/src/main/res/anim/popup_flyout_hide.xml create mode 100644 library/src/main/res/anim/popup_flyout_show.xml create mode 100644 library/src/main/res/anim/slide_down.xml create mode 100644 library/src/main/res/anim/slide_left_in.xml create mode 100644 library/src/main/res/anim/slide_right_out.xml create mode 100644 library/src/main/res/anim/slide_up.xml create mode 100755 library/src/main/res/drawable-hdpi/back.png create mode 100755 library/src/main/res/drawable-hdpi/close.png create mode 100755 library/src/main/res/drawable-hdpi/forward.png create mode 100755 library/src/main/res/drawable-hdpi/more.png create mode 100755 library/src/main/res/drawable-mdpi/back.png create mode 100755 library/src/main/res/drawable-mdpi/close.png create mode 100755 library/src/main/res/drawable-mdpi/forward.png create mode 100755 library/src/main/res/drawable-mdpi/more.png create mode 100644 library/src/main/res/drawable-v21/selector_dark_theme.xml create mode 100644 library/src/main/res/drawable-v21/selector_dark_theme_holo.xml create mode 100644 library/src/main/res/drawable-v21/selector_light_theme.xml create mode 100644 library/src/main/res/drawable-v21/selector_light_theme_holo.xml create mode 100755 library/src/main/res/drawable-xhdpi/back.png create mode 100755 library/src/main/res/drawable-xhdpi/close.png create mode 100755 library/src/main/res/drawable-xhdpi/forward.png create mode 100755 library/src/main/res/drawable-xhdpi/more.png create mode 100755 library/src/main/res/drawable-xxhdpi/back.png create mode 100755 library/src/main/res/drawable-xxhdpi/close.png create mode 100755 library/src/main/res/drawable-xxhdpi/forward.png create mode 100755 library/src/main/res/drawable-xxhdpi/more.png create mode 100644 library/src/main/res/drawable/back_vector.xml create mode 100644 library/src/main/res/drawable/close_vector.xml create mode 100644 library/src/main/res/drawable/forward_vector.xml create mode 100644 library/src/main/res/drawable/more_vector.xml create mode 100644 library/src/main/res/drawable/progress_drawable.xml create mode 100644 library/src/main/res/drawable/selector_dark_theme.xml create mode 100644 library/src/main/res/drawable/selector_light_theme.xml create mode 100644 library/src/main/res/layout/awesome_web_view.xml create mode 100644 library/src/main/res/layout/menus.xml create mode 100644 library/src/main/res/layout/toolbar_content.xml create mode 100644 library/src/main/res/layout/view_custom_menu.xml create mode 100644 library/src/main/res/layout/view_loading_video.xml create mode 100644 library/src/main/res/values-es/strings.xml create mode 100644 library/src/main/res/values-ko/strings.xml create mode 100644 library/src/main/res/values-land/dimens.xml create mode 100644 library/src/main/res/values-ldrtl/bools.xml create mode 100644 library/src/main/res/values-pt-rBR/strings.xml create mode 100644 library/src/main/res/values/bools.xml create mode 100644 library/src/main/res/values/colors.xml create mode 100644 library/src/main/res/values/dimens.xml create mode 100644 library/src/main/res/values/strings.xml create mode 100644 library/src/main/res/values/styles.xml create mode 100644 library/src/main/res/xml/provider_paths.xml create mode 100644 utils/.gitignore create mode 100644 utils/build.gradle create mode 100644 utils/src/androidTest/java/com/thefinestartist/utils/ApplicationTest.java create mode 100755 utils/src/androidTest/java/com/thefinestartist/utils/etc/PreferencesUtilTest.java create mode 100644 utils/src/main/AndroidManifest.xml create mode 100644 utils/src/main/java/com/thefinestartist/Base.java create mode 100644 utils/src/main/java/com/thefinestartist/annotations/Extra.java create mode 100644 utils/src/main/java/com/thefinestartist/binders/ExtrasBinder.java create mode 100644 utils/src/main/java/com/thefinestartist/builders/ActivityBuilder.java create mode 100644 utils/src/main/java/com/thefinestartist/builders/BundleBuilder.java create mode 100644 utils/src/main/java/com/thefinestartist/converters/Unit.java create mode 100644 utils/src/main/java/com/thefinestartist/converters/UnitConverter.java create mode 100644 utils/src/main/java/com/thefinestartist/enums/LogLevel.java create mode 100644 utils/src/main/java/com/thefinestartist/enums/Rotation.java create mode 100644 utils/src/main/java/com/thefinestartist/listeners/KeyboardStateListener.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/content/ContextUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/content/Ctx.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/content/Res.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/content/ResourcesUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/content/ThemeUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/content/TypedValueUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/etc/APILevel.java create mode 100755 utils/src/main/java/com/thefinestartist/utils/etc/IntArrayUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/etc/PackageUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/etc/SparseArrayUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/etc/ThreadUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/etc/TypefaceUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/AndroidLogPrinter.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/FileLogPrinter.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/L.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/LogHelper.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/LogPrinter.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/LogUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/log/Settings.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/preferences/Pref.java create mode 100755 utils/src/main/java/com/thefinestartist/utils/preferences/PreferencesUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/service/ClipboardManagerUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/service/ServiceUtil.java create mode 100755 utils/src/main/java/com/thefinestartist/utils/service/VibratorUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/service/WindowManagerUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/ui/DisplayUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/ui/Keyboard.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/ui/KeyboardUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/utils/ui/ViewUtil.java create mode 100755 utils/src/main/java/com/thefinestartist/wip/AgeUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/AudioManagerUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/AwakeUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/BitmapUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/DateUtil.java create mode 100755 utils/src/main/java/com/thefinestartist/wip/EmailUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/FileUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/LanguageDetector.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/NetworkUtil.java create mode 100755 utils/src/main/java/com/thefinestartist/wip/PhotoUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/RippleUtil.java create mode 100644 utils/src/main/java/com/thefinestartist/wip/Validator.java create mode 100644 utils/src/main/res/anim/accelerate_cubic.xml create mode 100644 utils/src/main/res/anim/accelerate_quart.xml create mode 100644 utils/src/main/res/anim/accelerate_quint.xml create mode 100644 utils/src/main/res/anim/activity_close_enter.xml create mode 100644 utils/src/main/res/anim/activity_close_exit.xml create mode 100644 utils/src/main/res/anim/activity_open_enter.xml create mode 100644 utils/src/main/res/anim/activity_open_exit.xml create mode 100644 utils/src/main/res/anim/decelerate_cubic.xml create mode 100644 utils/src/main/res/anim/decelerate_quart.xml create mode 100644 utils/src/main/res/anim/decelerate_quint.xml create mode 100644 utils/src/main/res/anim/fade_in.xml create mode 100644 utils/src/main/res/anim/fade_in_fast.xml create mode 100644 utils/src/main/res/anim/fade_out.xml create mode 100644 utils/src/main/res/anim/fade_out_fast.xml create mode 100644 utils/src/main/res/anim/fade_out_slow.xml create mode 100644 utils/src/main/res/anim/fragment_close_exit.xml create mode 100644 utils/src/main/res/anim/fragment_close_exit_reverse.xml create mode 100644 utils/src/main/res/anim/fragment_open_enter.xml create mode 100644 utils/src/main/res/anim/fragment_open_enter_reverse.xml create mode 100644 utils/src/main/res/anim/modal_activity_close_enter.xml create mode 100644 utils/src/main/res/anim/modal_activity_close_exit.xml create mode 100644 utils/src/main/res/anim/modal_activity_open_enter.xml create mode 100644 utils/src/main/res/anim/modal_activity_open_exit.xml create mode 100644 utils/src/main/res/anim/no_anim.xml create mode 100644 utils/src/main/res/anim/splash_out.xml create mode 100644 utils/src/main/res/anim/view_blink.xml create mode 100644 utils/src/main/res/anim/view_bounce.xml create mode 100644 utils/src/main/res/anim/view_fade_in.xml create mode 100644 utils/src/main/res/anim/view_fade_out.xml create mode 100644 utils/src/main/res/anim/view_move.xml create mode 100644 utils/src/main/res/anim/view_rotate.xml create mode 100644 utils/src/main/res/anim/view_slide_down.xml create mode 100644 utils/src/main/res/anim/view_slide_up.xml create mode 100644 utils/src/main/res/anim/view_zoom_in.xml create mode 100644 utils/src/main/res/anim/view_zoom_out.xml create mode 100644 utils/src/main/res/animator/card_flip_left_in.xml create mode 100644 utils/src/main/res/animator/card_flip_left_out.xml create mode 100644 utils/src/main/res/animator/card_flip_right_in.xml create mode 100644 utils/src/main/res/animator/card_flip_right_out.xml create mode 100755 utils/src/main/res/values/colors_basic.xml create mode 100755 utils/src/main/res/values/colors_grey.xml create mode 100755 utils/src/main/res/values/colors_transparent.xml create mode 100644 utils/src/test/java/com/thefinestartist/helpers/ExampleUnitTest.java diff --git a/annotations/.gitignore b/annotations/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/annotations/.gitignore @@ -0,0 +1 @@ +/build diff --git a/annotations/build.gradle b/annotations/build.gradle new file mode 100644 index 00000000..ea568f81 --- /dev/null +++ b/annotations/build.gradle @@ -0,0 +1,15 @@ +apply plugin: 'java' +//apply plugin: 'com.novoda.bintray-release' + +//publish { +// userOrg = 'thefinestartist' +// groupId = 'com.thefinestartist' +// artifactId = 'annotations' +// publishVersion = rootProject.ext.versionName +// desc = 'Context free and basic utils to build Android project conveniently.' +// website = 'https://github.com/TheFinestArtist/AndroidBaseUtils' +//} +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0eabe85c..cb9c54ce 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -102,6 +102,8 @@ dependencies { implementation("com.squareup.retrofit2:converter-scalars:2.6.4") implementation("androidx.viewpager2:viewpager2:1.0.0") implementation("com.squareup.picasso:picasso:2.71828") + implementation("com.github.delight-im:Android-AdvancedWebView:v3.2.1") + implementation(project(":library")) // implementation ("me.everything:providers-android:1.0.1") // implementation ("me.everything:providers-core:1.0.1") // implementation ("androidx.window:window:1.0.0") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 93110aa8..791ecfdc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -120,6 +120,8 @@ + + + + diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/LauncherHome.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/LauncherHome.kt index 8f43256e..43b21eed 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/LauncherHome.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/LauncherHome.kt @@ -120,6 +120,7 @@ import java.math.RoundingMode import java.net.URLEncoder import java.util.Calendar import java.util.Date +import java.util.Locale import java.util.regex.Pattern @@ -132,63 +133,66 @@ internal class LauncherHome : Fragment() { private var shouldResume = true companion object { - var home : LauncherHome? = null - var lastedFinishedPageUrl : String = "" - // var recentCalls = arrayListOf() -// var callList = arrayListOf() -// var smsList = arrayListOf() + var home: LauncherHome? = null + var lastedFinishedPageUrl: String = "" var listTags = arrayListOf() } -// private var nReceiver: NotificationReceiver? = null -// -// class NotificationReceiver : BroadcastReceiver() { -// override fun onReceive(context: Context?, intent: Intent) { -// BLog.LOGE("NotificationReceiver >>>> ${intent.extras?.keySet()}") -// } -// } - val UPDATE_DELAY = 5L val commandHandler = Handler(Looper.getMainLooper()) - val smsUpdate = Runnable { + val infoUpdate = Runnable { chooseAdpater() } + val notiUpdate = Runnable { chooseAdpater() } - chooseAdpater() + var weatherJob: Job? = null + var result: RealmResults? = null + val hideListViewTime = 1000L * 60L * 15L + val hideListView = { +// binding.notiList.visibility = View.GONE +// binding.mainList.visibility = View.GONE +// binding.infoList.visibility = View.GONE +// binding.smsList.visibility = View.GONE +// binding.otherCheck.isSelected = false +// binding.recentSms.isSelected = false +// binding.missedCalls.isSelected = false +// binding.notice.isSelected = false } - val callUpdate = Runnable { + val nomoreShowCount = 5 + var rssStateVote = false - chooseAdpater() - } + var lasted: List? = null + var lastedNoti: List? = null + var infosJob: Job? = null + var noticeJob: Job? = null - - val infoUpdate = Runnable { - chooseAdpater() - } - - val notiUpdate = Runnable { - chooseAdpater() - } - - - var lasted : List? = null - var lastedNoti : List? = null + lateinit var mRecentCallsAdapter: RecentCallsAdapter + lateinit var mSmsLogsAdapter: SmsLogsAdapter + lateinit var mRssAdapter: RssItemAdapter + lateinit var mNotiAdapter: NotificationItemAdapter + var mWeatherAdapter: WeatherAdapter? = null + var weatherDressAdapter: WeatherDressAdatper? = null + var weatherHourlyAdapter: WeatherHourlyAdapter? = null + var mRssDataResult: RealmResults? = null + var mNotificationResult: RealmResults? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) home = this -// BLog.LOGE("${this} ::::: onCreate >>>> ") } - var mWeatherResult : RealmResults? = null - var musicJob : Job? = null - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { -// BLog.LOGE("${this} ::::: onCreateView >>>> ") + + var musicJob: Job? = null + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { binding = LauncherHomeBinding.inflate(inflater, container, false) fragManager = lActivity!!.supportFragmentManager settingsPrefs = requireContext().getSharedPreferences(PREFS_SETTINGS, 0) batteryReceiver = BatteryReceiver(binding.batteryProgress) - mRecentCallsAdapter = RecentCallsAdapter(arrayListOf(), requireContext()) - mSmsLogsAdapter = SmsLogsAdapter(arrayListOf(), requireContext()) + mRecentCallsAdapter = RecentCallsAdapter(arrayListOf(), requireContext()) + mSmsLogsAdapter = SmsLogsAdapter(arrayListOf(), requireContext()) mNotiAdapter = NotificationItemAdapter(requireContext()) mRssAdapter = RssItemAdapter(requireContext()) @@ -198,8 +202,14 @@ internal class LauncherHome : Fragment() { var weatherPages = arrayListOf() var weatherAdapter = arrayListOf?>() - PrefBoolean.weatherDress.get(false).letTrue { weatherPages.add(R.layout.hourly_weather); weatherAdapter.add(weatherDressAdapter!!)} - PrefBoolean.weatherState.get(false).letTrue { weatherPages.add(R.layout.recommended_hourly_dress); weatherAdapter.add(weatherHourlyAdapter!!)} + PrefBoolean.weatherDress.get(false).letTrue { + weatherPages.add(R.layout.hourly_weather); weatherAdapter.add(weatherDressAdapter!!) + } + PrefBoolean.weatherState.get(false).letTrue { + weatherPages.add(R.layout.recommended_hourly_dress); weatherAdapter.add( + weatherHourlyAdapter!! + ) + } if (weatherPages.size > 0) { mWeatherAdapter = WeatherAdapter( weatherPages, @@ -219,10 +229,6 @@ internal class LauncherHome : Fragment() { binding.smsList.visibility = View.GONE binding.infoList.visibility = View.GONE - binding.notiList.layoutManager = LinearLayoutManager(requireContext()) - binding.mainList.layoutManager = GridLayoutManager(requireContext(),2) - binding.smsList.layoutManager = GridLayoutManager(requireContext(),2) - binding.infoList.layoutManager = LinearLayoutManager(requireContext()) binding.noticeSummary.weatherViewPager.orientation = ViewPager2.ORIENTATION_VERTICAL binding.mainList.adapter = mRecentCallsAdapter @@ -230,7 +236,8 @@ internal class LauncherHome : Fragment() { binding.infoList.adapter = mRssAdapter binding.notiList.adapter = mNotiAdapter binding.noticeSummary.weatherViewPager.adapter = mWeatherAdapter - binding.noticeSummary.weatherViewPager.registerOnPageChangeCallback(object: ViewPager2.OnPageChangeCallback() { + binding.noticeSummary.weatherViewPager.registerOnPageChangeCallback(object : + ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) // 페이지가 변경될 때 호출됨 @@ -238,16 +245,6 @@ internal class LauncherHome : Fragment() { } - override fun onPageScrollStateChanged(state: Int) { - super.onPageScrollStateChanged(state) - // 스크롤 상태가 변경될 때 호출됨 - } - - override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) { - super.onPageScrolled(position, positionOffset, positionOffsetPixels) - // 페이지 스크롤이 발생할 때 호출됨 - - } }) binding.favAppsGroup.setOnLongClickListener { binding.otherCheck.isSelected = true @@ -274,17 +271,26 @@ internal class LauncherHome : Fragment() { QuickAccess().show(fragManager, BOTTOM_SHEET_TAG) } } + arrayListOf(binding.mainList,binding.smsList,binding.infoList,binding.notiList).forEach { + try { + it.removeOnScrollListener(onScrChanged) + } catch (e: Exception) { + e.printStackTrace() + } + it.addOnScrollListener(onScrChanged) + } - try{binding.mainList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()} - binding.mainList.addOnScrollListener(onScrChanged) - try{binding.smsList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()} - binding.smsList.addOnScrollListener(onScrChanged) - try{binding.infoList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()} - binding.infoList.addOnScrollListener(onScrChanged) - try{binding.notiList.removeOnScrollListener(onScrChanged)}catch (e : Exception){e.printStackTrace()} - binding.notiList.addOnScrollListener(onScrChanged) - binding.currentMusic?.setOnClickListener { + + queryInfos() + queryNotice() + queryWeather() + setMusicFunction() + return binding.root + } + + fun setMusicFunction() { + binding.currentMusic.setOnClickListener { lActivity?.apply { packageManager?.apply { startActivity(getLaunchIntentForPackage("com.google.android.apps.youtube.music")) @@ -294,36 +300,37 @@ internal class LauncherHome : Fragment() { musicJob?.cancel() musicJob = CoroutineScope(Dispatchers.Default).launch { WorkersDb.getRealm().apply { - query().find().asFlow().collect { changes: ResultsChange -> - binding.currentMusic?.postDelayed({ + query().find().asFlow() + .collect { changes: ResultsChange -> + binding.currentMusic.postDelayed({ - if (changes.list.size > 0) { - PrefBoolean.showNowPlaying.get(false).letTrue { - changes.list?.first()?.let { - binding.currentMusic.visibility = View.VISIBLE - binding.nextPlay.visibility = View.GONE - binding.artist.text = it.artists - binding.artist.isSelected = true - binding.title.text = it.title - binding.title.isSelected = true - try { - BitmapConverter.StringToBitmap(it.albumArt)?.let { - binding.albumArt.setImageBitmap(it) + if (changes.list.size > 0) { + PrefBoolean.showNowPlaying.get(false).letTrue { + changes.list.first()?.let { + binding.currentMusic.visibility = View.VISIBLE + binding.nextPlay.visibility = View.GONE + binding.artist.text = it.artists + binding.artist.isSelected = true + binding.title.text = it.title + binding.title.isSelected = true + try { + BitmapConverter.StringToBitmap(it.albumArt)?.let { + binding.albumArt.setImageBitmap(it) + } + } catch (exception: java.lang.Exception) { + // log error } - } catch (exception: java.lang.Exception) { - // log error } } + } else { + binding.currentMusic.visibility = View.GONE + binding.nextPlay.visibility = View.VISIBLE } - } else { - binding.currentMusic.visibility = View.GONE - binding.nextPlay.visibility = View.VISIBLE - }}, 150L) - } + }, 150L) + } } } musicJob?.start() -// BLog.LOGE("onCreateView()") binding.nextBtn.setOnClickListener { val mAudioManager = requireContext().getSystemService(AUDIO_SERVICE) as AudioManager @@ -352,27 +359,30 @@ internal class LauncherHome : Fragment() { KeyEvent(eventtime, eventtime, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_MEDIA_PLAY, 0) mAudioManager.dispatchMediaKeyEvent(upEvent) } - - queryInfos() - queryNotice() - queryWeather() - - return binding.root } - @SuppressLint("NotifyDataSetChanged") - fun queryWeatherWithLoc(){ + fun queryWeatherWithLoc() { WorkersDb.getRealm().apply { - var latR = latitudeRange(BigDecimal.valueOf(LocationGetter.latitude).setScale(6,RoundingMode.HALF_UP).toDouble(), 200) - var lonR = longitudeRange(BigDecimal.valueOf(LocationGetter.latitude).setScale(6,RoundingMode.HALF_UP).toDouble(),BigDecimal.valueOf(LocationGetter.longitude).setScale(6,RoundingMode.HALF_UP).toDouble(), 200) + var latR = latitudeRange( + BigDecimal.valueOf(LocationGetter.latitude).setScale(6, RoundingMode.HALF_UP) + .toDouble(), 200 + ) + var lonR = longitudeRange( + BigDecimal.valueOf(LocationGetter.latitude).setScale(6, RoundingMode.HALF_UP) + .toDouble(), + BigDecimal.valueOf(LocationGetter.longitude).setScale(6, RoundingMode.HALF_UP) + .toDouble(), + 200 + ) query() - .query("lat >= $0 AND lat <= $1 AND lon >= $2 AND lon <= $3 AND time_epoch >= $4", - latR.first(),latR.last(), - lonR.first(),lonR.last(), + .query( + "lat >= $0 AND lat <= $1 AND lon >= $2 AND lon <= $3 AND time_epoch >= $4", + latR.first(), latR.last(), + lonR.first(), lonR.last(), (System.currentTimeMillis() / 1000L).toLong() ).also { BLog.LOGE("re >>> ${it.description()}") - }.find().let {hours -> + }.find().let { hours -> Handler(Looper.getMainLooper()).post { weatherDressAdapter?.let { it.update( @@ -398,7 +408,8 @@ internal class LauncherHome : Fragment() { it.notifyDataSetChanged() } mWeatherAdapter?.let { - binding.noticeSummary.textLocation = if (hours.isNotEmpty()) WeatherInfoManager.getShowingInfo(hours.first()).textLocation else "도시 / 나라" + binding.noticeSummary.textLocation = + if (hours.isNotEmpty()) WeatherInfoManager.getShowingInfo(hours.first()).textLocation else "도시 / 나라" it.notifyDataSetChanged() } } @@ -421,8 +432,7 @@ internal class LauncherHome : Fragment() { // } } - var weatherJob : Job? = null - var result : RealmResults? = null + // lateinit var weatherJob : Job // @SuppressLint("NotifyDataSetChanged") private fun queryWeather() { @@ -442,19 +452,6 @@ internal class LauncherHome : Fragment() { weatherJob?.start() } - val hideListViewTime = 1000L * 60L * 15L - val hideListView = { - -// binding.notiList.visibility = View.GONE -// binding.mainList.visibility = View.GONE -// binding.infoList.visibility = View.GONE -// binding.smsList.visibility = View.GONE -// binding.otherCheck.isSelected = false -// binding.recentSms.isSelected = false -// binding.missedCalls.isSelected = false -// binding.notice.isSelected = false - } - val onScrChanged = object : RecyclerView.OnScrollListener() { override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { @@ -464,8 +461,10 @@ internal class LauncherHome : Fragment() { RecyclerView.SCROLL_STATE_IDLE -> { commandHandler.postDelayed(hideListView, hideListViewTime) } + RecyclerView.SCROLL_STATE_DRAGGING -> { } + RecyclerView.SCROLL_STATE_SETTLING -> { } } @@ -478,60 +477,57 @@ internal class LauncherHome : Fragment() { } private fun queryNotice() { - try { noticeJob?.cancel() } catch (e:Exception) {e.printStackTrace()} mNotificationResult = null - try { - System.gc() - }catch (e : Exception){ - e.printStackTrace() - } - - mNotificationResult = WorkersDb.getRealm().query().sort("postTime",Sort.DESCENDING).find() + clearJob(noticeJob) + mNotificationResult = WorkersDb.getRealm().query().sort("postTime", Sort.DESCENDING).find() noticeJob = CoroutineScope(Dispatchers.Default).launch { mNotificationResult?.asFlow()?.collect { changes: ResultsChange -> commandHandler.removeCallbacks(hideListView) when (changes) { is UpdatedResults -> { -// BLog.LOGE("ResultsChange onNotificationPosted") WorkersDb.getRealm().apply { lastedNoti = copyFromRealm(changes.list) } commandHandler.removeCallbacks(notiUpdate) commandHandler.postDelayed(notiUpdate, UPDATE_DELAY) } + else -> { - // types other than UpdatedResults are not changes -- ignore them } } } } noticeJob?.start() } - val nomoreShowCount = 5 + fun clearJob(job : Job?) { + try { job?.cancel() } catch (e: Exception) { e.printStackTrace() } + try { System.gc() } catch (e: Exception) { e.printStackTrace() } + } fun beforeQuery() { mRssDataResult = null - try { infosJob?.cancel() } catch (e:Exception) {e.printStackTrace()} - try { System.gc() }catch (e : Exception){e.printStackTrace()} + clearJob(infosJob) WorkersDb.getRealm().writeBlocking { - delete(query().query("pubDate < $0",beforeDay(Date(),3)).query("category != $0 AND category != $1 ", RssDataType.GURU.name,RssDataType.MOST.name).query("vote != $0", true).find()) + delete( + query() + .query("pubDate < $0", beforeDay(Date(), 3)) + .query("category != $0 AND category != $1 ", RssDataType.GURU.name, RssDataType.MOST.name) + .query("vote != $0", true).find() + ) } } - fun updateQuery(q : RealmQuery) { - mRssDataResult = q.sort("pubDate ", Sort.DESCENDING).limit(300).find() + fun updateQuery(q: RealmQuery) { + mRssDataResult = q.sort("pubDate ", Sort.DESCENDING).limit(300).distinct("chosung").find() infosJob = CoroutineScope(Dispatchers.Default).launch { mRssDataResult?.asFlow()?.collect { changes: ResultsChange -> commandHandler.removeCallbacks(hideListView) commandHandler.removeCallbacks(infoUpdate) - BLog.LOGE("enableSwipeToDeleteAndUndo in changes") when (changes) { - is InitialResults,is UpdatedResults -> { + is InitialResults, is UpdatedResults -> { WorkersDb.getRealm().apply { lasted = copyFromRealm(changes.list) - BLog.LOGE("enableSwipeToDeleteAndUndo in new querys") } commandHandler.postDelayed(infoUpdate, UPDATE_DELAY) - BLog.LOGE("enableSwipeToDeleteAndUndo postDelayed") } else -> { } @@ -540,54 +536,55 @@ internal class LauncherHome : Fragment() { } infosJob?.start() } - var rssStateVote = false + + fun queryVotes() { beforeQuery() - var rQ = WorkersDb.getRealm().query().query("vote == $0",true) + var rQ = WorkersDb.getRealm().query().query("vote == $0", true) updateQuery(rQ) rssStateVote = true } - fun queryInfos(filter: Collection? = arrayListOf(RssDataType.GURU,RssDataType.MOST,RssDataType.REDDIT_NSFW), noLimit : Boolean = false) { + fun queryInfos( + filter: Collection? = arrayListOf( + RssDataType.GURU, + RssDataType.MOST, + RssDataType.REDDIT_NSFW + ), noLimit: Boolean = false + ) { beforeQuery() var rQ = WorkersDb.getRealm().query().query("read < $0", nomoreShowCount) - if(!noLimit) rQ.query("pubDate > $0", beforeDay(Date(),3)) + if (!noLimit) rQ.query("pubDate > $0", beforeDay(Date(), 3)) filter!!.forEach { rQ = rQ.query("category != $0", it.name) } - updateQuery(rQ) rssStateVote = false } - fun queryInfos(keyword : String, category : ArrayList = arrayListOf(), noLimit : Boolean = false) { + fun queryInfos( + keyword: String, + category: ArrayList = arrayListOf(), + noLimit: Boolean = false + ) { beforeQuery() var rQ = WorkersDb.getRealm().query() - if (!noLimit)rQ.query("pubDate > $0", beforeDay(Date(),3)) - if(keyword.length > 0) { -// BLog.LOGE("queryInfos it >>> ${keyword}") + if (!noLimit) rQ.query("pubDate > $0", beforeDay(Date(), 3)) + keyword.isNotEmpty().letTrue { if (JamoUtils.CHOSUNG.contains(keyword.split("")[0])) { - rQ = rQ.query( - "title CONTAINS $0 OR chosung CONTAINS $1 ", - keyword, - keyword - ) - } else if(Pattern.matches("^[가-힣]*\$", keyword)){ - rQ = rQ.query( - "title CONTAINS $0", - keyword - ) - }else { - + rQ = rQ.query("title CONTAINS $0 OR chosung CONTAINS $1 ", keyword, keyword) + } else if (Pattern.matches("^[가-힣]*\$", keyword)) { + rQ = rQ.query("title CONTAINS $0", keyword) + } else { rQ = rQ.query( "title CONTAINS $0 OR title CONTAINS $1", - keyword.toUpperCase(), - keyword.toLowerCase() + keyword.uppercase(Locale.getDefault()), + keyword.lowercase(Locale.getDefault()) ) } } var queryString = "" - if (category.size > 0) { + category.isNotEmpty().letTrue { category.forEachIndexed { idx, it -> if (idx == 0) { queryString = queryString.plus("category == '${it}'") @@ -595,47 +592,41 @@ internal class LauncherHome : Fragment() { queryString = queryString.plus(" OR category == '${it}' ") } } - rQ = rQ.query(queryString) } - if(keyword.length == 0 && category.size == 0){ + + if (keyword.length == 0 && category.size == 0) { rQ = rQ.query("read < $0", 3).query("vote != $0", true) } updateQuery(rQ) rssStateVote = false } - var infosJob : Job? = null - var noticeJob : Job? = null - lateinit var mRecentCallsAdapter : RecentCallsAdapter - lateinit var mSmsLogsAdapter : SmsLogsAdapter - lateinit var mRssAdapter : RssItemAdapter - lateinit var mNotiAdapter : NotificationItemAdapter - var mWeatherAdapter: WeatherAdapter? =null - var weatherDressAdapter: WeatherDressAdatper? = null - var weatherHourlyAdapter: WeatherHourlyAdapter? = null - var mRssDataResult : RealmResults? = null - var mNotificationResult : RealmResults? = null override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) -// BLog.LOGE("${this} ::::: onViewCreated >>>> ") - rootViewGestures() batteryProgressGestures() todosGestures() BLog.LOGE("onViewCreated()") - binding.missedCalls.visibility = if (PrefBoolean.showCallHistory.get(false)) View.VISIBLE else View.GONE - binding.recentSms.visibility = if (PrefBoolean.showSMSHistory.get(false)) View.VISIBLE else View.GONE - binding.notice.visibility = if (PrefBoolean.showNotificationHistory.get(false)) View.VISIBLE else View.GONE - binding.otherCheck.visibility = if (PrefBoolean.showNewsHistory.get(false)) View.VISIBLE else View.GONE + binding.missedCalls.visibility = + if (PrefBoolean.showCallHistory.get(false)) View.VISIBLE else View.GONE + binding.recentSms.visibility = + if (PrefBoolean.showSMSHistory.get(false)) View.VISIBLE else View.GONE + binding.notice.visibility = + if (PrefBoolean.showNotificationHistory.get(false)) View.VISIBLE else View.GONE + binding.otherCheck.visibility = + if (PrefBoolean.showNewsHistory.get(false)) View.VISIBLE else View.GONE binding.summaryChoose.visibility = View.GONE - (PrefBoolean.showCallHistory.get(false) || PrefBoolean.showSMSHistory.get(false) || PrefBoolean.showNotificationHistory.get(false) || PrefBoolean.showNewsHistory.get(false)).letTrue { binding.summaryChoose.visibility = View.VISIBLE } + (PrefBoolean.showCallHistory.get(false) || PrefBoolean.showSMSHistory.get(false) || PrefBoolean.showNotificationHistory.get( + false + ) || PrefBoolean.showNewsHistory.get(false)).letTrue { + binding.summaryChoose.visibility = View.VISIBLE + } + -// mWorkManager. - /* refresh the to-do list after getting back from TodoManager */ fragManager.addOnBackStackChangedListener { BLog.LOGE("addOnBackStackChangedListener()") shouldResume = if (fragManager.backStackEntryCount == 0) { @@ -647,18 +638,25 @@ internal class LauncherHome : Fragment() { } } - var checkListner = object : View.OnClickListener { + var checkListner = object : View.OnClickListener { override fun onClick(v: View?) { commandHandler.removeCallbacks(hideListView) - var views = arrayListOf(binding.mainList, binding.smsList, binding.infoList, binding.notiList) - var chechboxs = arrayListOf(binding.missedCalls, + var views = arrayListOf( + binding.mainList, + binding.smsList, + binding.infoList, + binding.notiList + ) + var chechboxs = arrayListOf( + binding.missedCalls, binding.recentSms, binding.otherCheck, - binding.notice) + binding.notice + ) chechboxs.remove(v) when (v) { binding.missedCalls -> { - if (binding.missedCalls.isSelected ) { + if (binding.missedCalls.isSelected) { binding.missedCalls.isSelected = false } else { @@ -667,8 +665,9 @@ internal class LauncherHome : Fragment() { binding.mainList.visibility = View.VISIBLE } } + binding.recentSms -> { - if (binding.recentSms.isSelected ) { + if (binding.recentSms.isSelected) { binding.recentSms.isSelected = false } else { @@ -677,8 +676,9 @@ internal class LauncherHome : Fragment() { binding.smsList.visibility = View.VISIBLE } } + binding.otherCheck -> { - if (binding.otherCheck.isSelected ) { + if (binding.otherCheck.isSelected) { if (rssStateVote) { queryInfos() } else { @@ -692,8 +692,9 @@ internal class LauncherHome : Fragment() { binding.infoList.visibility = View.VISIBLE } } + binding.notice -> { - if (binding.notice.isSelected ) { + if (binding.notice.isSelected) { binding.notice.isSelected = false } else { queryNotice() @@ -719,8 +720,6 @@ internal class LauncherHome : Fragment() { } enableSwipeToDeleteAndUndo() } -// https://www.youtube.com/results?search_query=sds - fun searchData() { val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) @@ -728,7 +727,8 @@ internal class LauncherHome : Fragment() { val viewInflated: View = LayoutInflater.from(context) .inflate(R.layout.search_layout, view as ViewGroup?, false) val input = viewInflated.findViewById(R.id.input) as EditText - val categoryz = viewInflated.findViewById(R.id.categoryz) as TableRadioGroup + val categoryz = + viewInflated.findViewById(R.id.categoryz) as TableRadioGroup categoryz.setMaxColumns(5) categoryz.setMaxRows(5) categoryz.setOnCheckedChangeListener(object : TableRadioGroup.OnCheckedChangeListener { @@ -740,7 +740,7 @@ internal class LauncherHome : Fragment() { RssDataType.values().reversed().toList().chunked(5).forEach { var tb = TableRow(requireContext()) it.forEach { c -> - if(c.equals(RssDataType.NO_DATA) == false) { + if (c.equals(RssDataType.NO_DATA) == false) { tb.addView( CheckBox(requireContext()).apply { this.tag = c.name @@ -756,9 +756,9 @@ internal class LauncherHome : Fragment() { builder.setPositiveButton(android.R.string.ok, DialogInterface.OnClickListener { dialog, which -> dialog.dismiss() - var category = arrayListOf() + var category = arrayListOf() categoryz.children.forEach { - if(it is TableRow) { + if (it is TableRow) { it.children.forEach { if (it is CheckBox && it.isChecked) { (it.tag as? String)?.let { category.add(it) } @@ -766,7 +766,7 @@ internal class LauncherHome : Fragment() { } } } - queryInfos(keyword =input.text.toString(),category, false) + queryInfos(keyword = input.text.toString(), category, false) }) builder.setNegativeButton(android.R.string.cancel, DialogInterface.OnClickListener { dialog, which -> dialog.cancel() }) @@ -774,15 +774,21 @@ internal class LauncherHome : Fragment() { } - fun showAl() { binding.alcholKatalkT.visibility = View.VISIBLE binding.alcholKatalkT.setOnClickListener { - openSearchApps("kakaot://taxi?dest_lat=${URLEncoder.encode("37.467696")}&dest_lng=${URLEncoder.encode("127.101063")}","com.kakao.taxi") + openSearchApps( + "kakaot://taxi?dest_lat=${URLEncoder.encode("37.467696")}&dest_lng=${ + URLEncoder.encode( + "127.101063" + ) + }", "com.kakao.taxi" + ) } } - fun openSearchApps(schemeString : String, pakage : String? = null) { + + fun openSearchApps(schemeString: String, pakage: String? = null) { val gmmIntentUri = Uri.parse(schemeString) val mapIntent = Intent(Intent.ACTION_VIEW, gmmIntentUri) pakage?.let { @@ -790,6 +796,7 @@ internal class LauncherHome : Fragment() { } startActivity(mapIntent) } + fun hideAl() { binding.alcholKatalkT.visibility = View.GONE binding.alcholKatalkT.setOnClickListener { @@ -797,17 +804,18 @@ internal class LauncherHome : Fragment() { } } - fun chooseAdpater () { + fun chooseAdpater() { commandHandler.removeCallbacks(hideListView) binding.mainList.visibility = View.INVISIBLE binding.smsList.visibility = View.INVISIBLE binding.infoList.visibility = View.INVISIBLE binding.notiList.visibility = View.INVISIBLE - var dateParam = beforeDay(Date(),30).toString() + var dateParam = beforeDay(Date(), 30).toString() if (binding.missedCalls.isSelected) { WorkersDb.getRealm().apply { - val result = query().query("callDayTime >= $0", dateParam).sort("callDayTime", Sort.DESCENDING).find() + val result = query().query("callDayTime >= $0", dateParam) + .sort("callDayTime", Sort.DESCENDING).find() val list = copyFromRealm(result) binding.missedCalls.text = "통화 목록 [${list.size}]" binding.mainList.visibility = View.VISIBLE @@ -816,15 +824,15 @@ internal class LauncherHome : Fragment() { binding.otherCheck.isSelected = false binding.notice.isSelected = false } - } else if(binding.recentSms.isSelected){ + } else if (binding.recentSms.isSelected) { WorkersDb.getRealm().apply { val result = query().query("rcvDate >= $0 OR pstDate >= $0 ", dateParam) - .sort("rcvDate",Sort.DESCENDING).find() + .sort("rcvDate", Sort.DESCENDING).find() if (result.size > 0) { try { binding.recentSms.text = "문자 내역 [${result.size}]" binding.smsList.visibility = View.VISIBLE - val list = copyFromRealm(result) + val list = copyFromRealm(result) mSmsLogsAdapter.updateData(list) binding.missedCalls.isSelected = false binding.otherCheck.isSelected = false @@ -834,57 +842,70 @@ internal class LauncherHome : Fragment() { } } } - } else if(binding.otherCheck.isSelected) { + } else if (binding.otherCheck.isSelected) { binding.missedCalls.isSelected = false binding.recentSms.isSelected = false binding.notice.isSelected = false binding.infoList.visibility = View.VISIBLE binding.otherCheck.text = "글타래 [${lasted?.size ?: "-"}]" lasted?.let { mRssAdapter.updateData(it) } - } - else if(binding.notice.isSelected) { + } else if (binding.notice.isSelected) { binding.missedCalls.isSelected = false binding.recentSms.isSelected = false binding.otherCheck.isSelected = false binding.notiList.visibility = View.VISIBLE binding.notice.text = "알림 [${lastedNoti?.size ?: "-"}]" - lastedNoti?.let { mNotiAdapter.updateData(it)} + lastedNoti?.let { mNotiAdapter.updateData(it) } } commandHandler.postDelayed(hideListView, hideListViewTime) } private fun enableSwipeToDeleteAndUndo() { - val swipeToDeleteCallback: SwipeToDeleteCallback = object : SwipeToDeleteCallback(requireContext()) { - override fun onSwiped(@NonNull viewHolder: RecyclerView.ViewHolder, i: Int) { - (viewHolder.itemView.getTag() as? RssData)?.let { rss -> - WorkersDb.getRealm().apply { - writeBlocking { - BLog.LOGE("enableSwipeToDeleteAndUndo in ") - if (rssStateVote && rss.vote) { - rss.vote = false - rss.read = 0 - } else { - rss.read += nomoreShowCount + val swipeToDeleteCallback: SwipeToDeleteCallback = + object : SwipeToDeleteCallback(requireContext()) { + override fun onSwiped(@NonNull viewHolder: RecyclerView.ViewHolder, i: Int) { + (viewHolder.itemView.tag as? RssData)?.let { rss -> + WorkersDb.getRealm().apply { + writeBlocking { + if(query("chosung == $0",rss.chosung).find().size == 1) { + if (rssStateVote && rss.vote) { + rss.vote = false + rss.read = 0 + } else { + rss.read += nomoreShowCount + } + copyToRealm(rss, UpdatePolicy.ALL) + } else { + query("chosung == $0",rss.chosung).find().forEach { it -> + if (rssStateVote && rss.vote) { + it.vote = false + it.read = 0 + } else { + it.read += nomoreShowCount + } + copyToRealm(it, UpdatePolicy.ALL) + } + } } - BLog.LOGE("enableSwipeToDeleteAndUndo in before updated ${rss.read}") - copyToRealm(rss, UpdatePolicy.ALL) - BLog.LOGE("enableSwipeToDeleteAndUndo in updated") } } } } - } val itemTouchhelper = ItemTouchHelper(swipeToDeleteCallback) itemTouchhelper.attachToRecyclerView(binding.infoList) } + override fun onResume() { super.onResume() // BLog.LOGE("${this} ::::: onResume >>>> ") if (shouldResume) { /* register battery changes */ - requireContext().registerReceiver(batteryReceiver, IntentFilter(Intent.ACTION_BATTERY_CHANGED)) + requireContext().registerReceiver( + batteryReceiver, + IntentFilter(Intent.ACTION_BATTERY_CHANGED) + ) /* time and date */ // binding.time.textLocale = Locale.US // binding.date.textLocale = Locale.US @@ -902,11 +923,6 @@ internal class LauncherHome : Fragment() { } - - - - - override fun onPause() { super.onPause() /* unregister battery changes */ @@ -921,16 +937,23 @@ internal class LauncherHome : Fragment() { gestureDuration: Long, gestureDistance: Double ): Boolean { - when(fingers) { - 4->{ + when (fingers) { + 4 -> { lActivity!!.startActivity(Intent(requireContext(), Behavior::class.java)) } + 3 -> QuickAccess().show(fragManager, BOTTOM_SHEET_TAG) - 2->{ + 2 -> { var startIntene = Intent(Intent.ACTION_MAIN) - startIntene.setComponent(ComponentName("com.mime.dualscreenview","com.mime.dualscreenview.activity.Intro")) + startIntene.setComponent( + ComponentName( + "com.mime.dualscreenview", + "com.mime.dualscreenview.activity.Intro" + ) + ) startActivity(startIntene) } + else -> {} } return false @@ -942,21 +965,34 @@ internal class LauncherHome : Fragment() { gestureDuration: Long, gestureDistance: Double ): Boolean { - when(fingers) { + when (fingers) { 2 -> - if (targetView?.equals(binding.batteryProgress) ?: false) { + if (targetView.equals(binding.batteryProgress)) { expandNotificationPanel(requireContext()) } else { expandNotificationPanel(requireContext()) } - 4->{ + + 4 -> { var startIntene = Intent(Intent.ACTION_MAIN) - startIntene.setComponent(ComponentName("com.samsung.android.app.interpreter","com.samsung.android.app.interpreter.interpretation.view.InterpretationActivity")) + startIntene.setComponent( + ComponentName( + "com.samsung.android.app.interpreter", + "com.samsung.android.app.interpreter.interpretation.view.InterpretationActivity" + ) + ) startActivity(startIntene) } - 3-> { - lActivity!!.startActivity(Intent(requireContext(), SettingsActivity::class.java)) + + 3 -> { + lActivity!!.startActivity( + Intent( + requireContext(), + SettingsActivity::class.java + ) + ) } + else -> {} } return false @@ -1006,27 +1042,39 @@ internal class LauncherHome : Fragment() { return false } - override fun onDoubleTap(targetView: View,fingers: Int): Boolean { + override fun onDoubleTap(targetView: View, fingers: Int): Boolean { + + when (fingers) { + 1 -> lockMethod( + settingsPrefs.getInt(KEY_LOCK_METHOD, 0), + requireContext(), + binding.favAppsGroup + ) - when(fingers) { - 1 -> lockMethod(settingsPrefs.getInt(KEY_LOCK_METHOD, 0), requireContext(), binding.favAppsGroup) else -> {} } return false } - override fun onLongPress(targetView: View,fingers: Int): Boolean { - if (view?.equals(binding.batteryProgress) ?: false) { + override fun onLongPress(targetView: View, fingers: Int): Boolean { + if (view?.equals(binding.batteryProgress) == true) { lActivity!!.startActivity(Intent(requireContext(), SettingsActivity::class.java)) - } else if (view?.equals(binding.mainList) ?: false) { + } else if (view?.equals(binding.mainList) == true) { when (settingsPrefs.getBoolean(KEY_TODO_LOCK, false)) { false -> launchTodoManager() /* show authentication screen if lock is on */ true -> { if (canAuthenticate(requireContext())) { - val biometricPrompt = BiometricPrompt(lActivity!!, authenticationCallback) + val biometricPrompt = + BiometricPrompt(lActivity!!, authenticationCallback) try { - biometricPrompt.authenticate(biometricPromptInfo(lActivity!!.getString(R.string.todo_manager))) + biometricPrompt.authenticate( + biometricPromptInfo( + lActivity!!.getString( + R.string.todo_manager + ) + ) + ) } catch (exception: Exception) { exception.printStackTrace() } @@ -1037,19 +1085,25 @@ internal class LauncherHome : Fragment() { return false } - override fun onClick(targetView: View,fingers: Int): Boolean { + override fun onClick(targetView: View, fingers: Int): Boolean { // BLog.LOGE("onClick ${view} , fingers ${fingers}") - when(fingers) { + when (fingers) { 1 -> { - if (view?.equals(binding.batteryProgress) ?: false && fingers == 1) { + if (view?.equals(binding.batteryProgress) == true && fingers == 1) { requireContext().startActivity( Intent(AlarmClock.ACTION_SHOW_ALARMS).setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) ) } } - 2-> { - lockMethod(settingsPrefs.getInt(KEY_LOCK_METHOD, 0), requireContext(), binding.favAppsGroup) + + 2 -> { + lockMethod( + settingsPrefs.getInt(KEY_LOCK_METHOD, 0), + requireContext(), + binding.favAppsGroup + ) } + else -> { } @@ -1061,55 +1115,59 @@ internal class LauncherHome : Fragment() { } - fun jsonObjLog(pkey : String ,key : String, jsonObject: JSONObject) { - if (jsonObject?.has(key) == true && jsonObject?.get(key) is String) { - BLog.LOGE("jsonObjLog $pkey String in $key >> ${jsonObject?.getString(key)}") - } - else if (jsonObject?.has(key) == true && jsonObject?.get(key) is JSONObject) { - var obj = jsonObject?.getJSONObject(key) + fun jsonObjLog(pkey: String, key: String, jsonObject: JSONObject) { + if (jsonObject.has(key) == true && jsonObject.get(key) is String) { + BLog.LOGE("jsonObjLog $pkey String in $key >> ${jsonObject.getString(key)}") + } else if (jsonObject.has(key) == true && jsonObject.get(key) is JSONObject) { + var obj = jsonObject.getJSONObject(key) BLog.LOGE("jsonObjLog $pkey JSONObject in $key >> ${obj}") obj?.keys()?.forEach { // jsonObjLog(key,it, obj) } - } - else if (jsonObject?.has(key) == true && jsonObject?.get(key) is JSONArray) { - BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${jsonObject?.getJSONArray(key)}") - var array = jsonObject?.getJSONArray(key) - for ( i in 0..<(array?.length() ?: 0)) { + } else if (jsonObject.has(key) == true && jsonObject.get(key) is JSONArray) { + BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${jsonObject.getJSONArray(key)}") + var array = jsonObject.getJSONArray(key) + for (i in 0..<(array?.length() ?: 0)) { if (array?.get(i) is String) { } else if (array?.get(i) is JSONObject) { - var child = array?.getJSONObject(i) + var child = array.getJSONObject(i) child?.keys()?.forEach { // jsonObjLog(key, it, child) } } else if (array?.get(i) is JSONArray) { - BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${array?.getJSONArray(i)}") + BLog.LOGE("jsonObjLog $pkey JSONArray in $key >> ${array.getJSONArray(i)}") } } } else { - BLog.LOGE("jsonObjLog $pkey else in $key >> ${if(jsonObject?.has(key) == true) (jsonObject?.get(key)) else ("")}") + BLog.LOGE( + "jsonObjLog $pkey else in $key >> ${ + if (jsonObject.has(key) == true) (jsonObject.get( + key + )) else ("") + }" + ) } } - - - - - /* gestures on root view */ @SuppressLint("ClickableViewAccessibility") private fun rootViewGestures() { - binding.root.setOnTouchListener(SimpleFingerGestures(context = requireContext(), binding.root , mFingerGestureListener)) + binding.root.setOnTouchListener( + SimpleFingerGestures( + context = requireContext(), + binding.root, + mFingerGestureListener + ) + ) } - /* gestures on battery progress indicator area */ @SuppressLint("ClickableViewAccessibility") private fun batteryProgressGestures() { - binding.batteryProgress.setOnClickListener { startActivity( Intent(android.provider.Settings.ACTION_SETTINGS))} + binding.batteryProgress.setOnClickListener { startActivity(Intent(android.provider.Settings.ACTION_SETTINGS)) } // binding.time.setOnTouchListener(SimpleFingerGestures(context = requireContext(), binding.time , mFingerGestureListener)) // binding.batteryProgress.setOnTouchListener(SimpleFingerGestures(context = requireContext(), binding.batteryProgress , mFingerGestureListener)) } @@ -1117,7 +1175,13 @@ internal class LauncherHome : Fragment() { /* gestures on to-do area */ @SuppressLint("ClickableViewAccessibility") private fun todosGestures() { - binding.mainList.setOnTouchListener(SimpleFingerGestures(context = requireContext(), binding.mainList , mFingerGestureListener)) + binding.mainList.setOnTouchListener( + SimpleFingerGestures( + context = requireContext(), + binding.mainList, + mFingerGestureListener + ) + ) } /* authentication callback for TodoManager lock */ @@ -1125,11 +1189,21 @@ internal class LauncherHome : Fragment() { override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { launchTodoManager() } + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - Toast.makeText(requireContext(), lActivity!!.getString(R.string.authentication_error), Toast.LENGTH_SHORT).show() + Toast.makeText( + requireContext(), + lActivity!!.getString(R.string.authentication_error), + Toast.LENGTH_SHORT + ).show() } + override fun onAuthenticationFailed() { - Toast.makeText(requireContext(), lActivity!!.getString(R.string.authentication_failed), Toast.LENGTH_SHORT).show() + Toast.makeText( + requireContext(), + lActivity!!.getString(R.string.authentication_failed), + Toast.LENGTH_SHORT + ).show() } } @@ -1140,9 +1214,9 @@ internal class LauncherHome : Fragment() { } - /* get time format string */ - private val timeFormat: String? get() { + private val timeFormat: String + get() { // when (settingsPrefs.getInt(KEY_TIME_FORMAT, 0)) { // 0 -> return if (DateFormat.is24HourFormat(requireContext())) { // "kk:mm" @@ -1152,21 +1226,23 @@ internal class LauncherHome : Fragment() { // 1 -> return "HH:mm a" // 2 -> return "kk:mm" // } - return "a HH : mm" - } + return "a HH : mm" + } /* get date number suffix */ - private val dateNumberSuffix: String get() { - return when (Calendar.getInstance()[Calendar.DAY_OF_MONTH]) { - 1, 21, 31 -> "ˢᵗ" - 2, 22 -> "ⁿᵈ" - 3, 23 -> "ʳᵈ" - else -> "ᵗʰ" + private val dateNumberSuffix: String + get() { + return when (Calendar.getInstance()[Calendar.DAY_OF_MONTH]) { + 1, 21, 31 -> "ˢᵗ" + 2, 22 -> "ⁿᵈ" + 3, 23 -> "ʳᵈ" + else -> "ᵗʰ" + } } - } /* get date format string */ - private val dateFormat: String get() { + private val dateFormat: String + get() { // settingsPrefs.getString(KEY_DATE_FORMAT, DEFAULT_DATE_FORMAT).let { // return if (it!!.contains("x")) { // it.replace("x", dateNumberSuffix) @@ -1174,8 +1250,8 @@ internal class LauncherHome : Fragment() { // it // } // } - return "yyyy년 M월 W주차, dd일 E요일" - } + return "yyyy년 M월 W주차, dd일 E요일" + } } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/RssViewer.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/RssViewer.kt new file mode 100644 index 00000000..a8b4bfa1 --- /dev/null +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/RssViewer.kt @@ -0,0 +1,121 @@ +/* + * Lunar Launcher + * Copyright (C) 2022 Md Rasel Hossain + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package bums.lunatic.launcher.home + +import android.content.Intent +import android.content.pm.PackageManager +import android.os.Bundle +import android.os.Environment +import android.print.PDFPrint +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import androidx.fragment.app.DialogFragment +import bums.lunatic.launcher.LauncherActivity.Companion.lActivity +import bums.lunatic.launcher.R +import bums.lunatic.launcher.databinding.RssViewerBinding +import bums.lunatic.launcher.utils.BLog +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import java.io.File +import java.io.IOException + + +internal class RssViewer : DialogFragment() { + + private lateinit var binding: RssViewerBinding + private lateinit var packageName: String + private lateinit var packageManager: PackageManager + private lateinit var defAppName: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setStyle(STYLE_NO_TITLE, R.style.FilterFullScreenDialog) + } + + override fun onStart() { + super.onStart() + if (dialog != null) { +// val metrics = resources.displayMetrics +// val screenHeight = metrics.heightPixels +// val bottomSheet = +// dialog!!.findViewById(com.google.android.material.R.id.design_bottom_sheet) +// bottomSheet.layoutParams.height = (screenHeight * 0.95).toInt() +// val behavior = BottomSheetBehavior.from(bottomSheet) +// behavior.state = BottomSheetBehavior.STATE_EXPANDED +// behavior.skipCollapsed = true + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = RssViewerBinding.inflate(inflater, container, false) + binding.webview.loadUrl(tag!!) + binding.webview.setDesktopMode(false) + binding.pdfPrint.setOnClickListener { pdfPring() } + return binding.root + } + + fun pdfPring() { + val fileName = tag?.toUri()?.path?.replace("/","_")?.replace(".","_") + val path = File(Environment.getExternalStorageDirectory(),"bums") + if (path.exists() == false) { + path.mkdirs() + } + val file = File(path, fileName.plus(".pdf")) + + BLog.LOGE("file >>> ${file.absolutePath}") + try { + PDFPrint.generatePDFFromWebView(file,binding.webview, object : PDFPrint.OnPDFPrintListener { + override fun onSuccess(file: File?) { + BLog.LOGE("file >>>> ${file!!.absolutePath}") + val shareIntent: Intent = Intent().apply { + action = Intent.ACTION_SEND + this.`package` = "com.synology.dsdrive" + val imageUri = FileProvider.getUriForFile( + lActivity!!, + "bums.lunatic.launcher.fileprovider", //(use your app signature + ".provider" ) + file + ) + putExtra(Intent.EXTRA_STREAM, imageUri) + type = "pdf" + } + lActivity!!.startActivity(shareIntent) + } + + override fun onError(exception: java.lang.Exception?) { + Toast.makeText(lActivity!!, + "Pdf Save Failk ${exception?.localizedMessage}", Toast.LENGTH_LONG).show() + exception?.printStackTrace() + } + } ) + } catch (e: IOException) { + e.printStackTrace() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) +// (requireDialog() as BottomSheetDialog).dismissWithAnimation = true + + } +} diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt index ac322bbe..81d192bc 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/adapters/RssItemAdapter.kt @@ -28,14 +28,13 @@ import androidx.core.net.toUri import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import bums.lunatic.launcher.LauncherActivity.Companion.lActivity import bums.lunatic.launcher.R import bums.lunatic.launcher.databinding.ListItemWithBinding +import bums.lunatic.launcher.home.RssViewer import bums.lunatic.launcher.model.RssData import bums.lunatic.launcher.model.RssDataInterface import bums.lunatic.launcher.model.RssDataType -import bums.lunatic.launcher.openDotax -import bums.lunatic.launcher.openNews -import bums.lunatic.launcher.openOpera import bums.lunatic.launcher.openReddit import bums.lunatic.launcher.openYouTube import bums.lunatic.launcher.utils.BLog @@ -43,12 +42,13 @@ import bums.lunatic.launcher.workers.WorkersDb import com.google.android.material.imageview.ShapeableImageView import com.google.gson.Gson import com.squareup.picasso.Picasso +import com.wuadam.awesomewebview.AwesomeWebView import io.realm.kotlin.UpdatePolicy import java.text.SimpleDateFormat import java.util.Date -internal class RssItemAdapter ( +internal class RssItemAdapter ( private val context: Context) : RecyclerView.Adapter() { companion object { @SuppressLint("SimpleDateFormat") @@ -74,16 +74,39 @@ internal class RssItemAdapter ( } else { if (RssDataType.REDDIT_NSFW.equals(rss.category())) { openReddit(rss.originPage()) +// RssViewer().apply { +// show(lActivity!!.supportFragmentManager,rss.originPage) +// } } else { - openOpera(rss.originPage()) + RssViewer().apply { + show(lActivity!!.supportFragmentManager,rss.originPage) + } +// openOpera(rss.originPage()) } } } } - RssDataType.REDDIT -> { openReddit(rss.originPage()) } - RssDataType.DOTAX -> { openDotax(rss.originPage()) } - RssDataType.YOUTUBE -> { openYouTube(rss.originPage()) } - else -> { openNews(rss.originPage()) } + RssDataType.REDDIT -> { + RssViewer().apply { + show(lActivity!!.supportFragmentManager,rss.originPage) + } +// openReddit(rss.originPage()) + } + RssDataType.DOTAX -> { + RssViewer().apply { + AwesomeWebView.Builder(lActivity!!).show(rss.originPage!!) +// show(lActivity!!.supportFragmentManager,rss.originPage) + } +// openDotax(rss.originPage()) + } + RssDataType.YOUTUBE -> { openYouTube(rss.originPage()) + } + else -> { + RssViewer().apply { + show(lActivity!!.supportFragmentManager,rss.originPage) + } +// openNews(rss.originPage()) + } } } } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/utils/RssList.kt b/app/src/main/kotlin/bums/lunatic/launcher/utils/RssList.kt index 0a3ed88d..648c1236 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/utils/RssList.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/utils/RssList.kt @@ -19,12 +19,12 @@ object RssList { ) val newsFeeds = arrayListOf( "https://news.google.com/rss?hl=ko&gl=KR&ceid=KR:ko", - "https://imnews.imbc.com/rss/google_news/narrativeNews.rss", +// "https://imnews.imbc.com/rss/google_news/narrativeNews.rss", "http://www.hani.co.kr/rss", "http://biz.heraldcorp.com/common_prog/rssdisp.php?ct=010000000000.xml", - "https://rss.inews24.com/rss/news_inews.xml", - "https://rss.nocutnews.co.kr/category/it.xml", - "https://rss.nocutnews.co.kr/news/news.xml", +// "https://rss.inews24.com/rss/news_inews.xml", +// "https://rss.nocutnews.co.kr/category/it.xml", +// "https://rss.nocutnews.co.kr/news/news.xml", "https://rss.nocutnews.co.kr/news/top.xml", ) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt index b9718e74..1c873613 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/ArcaGetter.kt @@ -26,12 +26,12 @@ class ArcaGetter : BaseGetter { temp.clear() val urls = arrayListOf( "https://arca.live/b/singbung?mode=best", -// "https://arca.live/b/headline", + "https://arca.live/b/headline", // "https://arca.live/b/live", "https://arca.live/b/namuhotnow", "https://arca.live/b/society", // "https://arca.live/b/replay", -// "https://arca.live/b/breaking" + "https://arca.live/b/breaking" ) urls.forEach { Jsoup.connect(it) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt index 94a4fbef..67c2688b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/ClienGetter.kt @@ -70,7 +70,7 @@ class ClienGetter : BaseGetter { RssDataType.CLIEN.isOn { try { temp.clear() - val testUrl2 = arrayListOf("https://www.clien.net/service/group/community") + val testUrl2 = arrayListOf("https://www.clien.net/service/group/community","https://www.clien.net/service/board/park","https://www.clien.net/service/board/news","https://www.clien.net/service/board/useful","https://www.clien.net/service/board/pds") testUrl2.forEach { url -> Jsoup.connect(url) .userAgent(USAGT) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt index df80fcf1..f550da5f 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/DotaxGetter.kt @@ -23,7 +23,7 @@ class DotaxGetter : BaseGetter { temp.clear() val dotaxUrls = arrayListOf("https://m.cafe.daum.net/dotax", "https://m.cafe.daum.net/dotax/_rec?page=2", - "https://m.cafe.daum.net/dotax/_rec?page=3" +// "https://m.cafe.daum.net/dotax/_rec?page=3" ) dotaxUrls?.forEach { Jsoup.connect(it).userAgent(USAGT).get()?.let { dotax -> diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt index 3e6443c4..f8011b1e 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/TheQooGetter.kt @@ -48,7 +48,7 @@ class TheQooGetter : BaseGetter { override fun realWork(): Result { RssDataType.THEQOO.isOn { try { - val testUrl2 = arrayListOf("https://theqoo.net/hot") + val testUrl2 = arrayListOf("https://theqoo.net/hot","https://theqoo.net/hot/category/512000937","https://theqoo.net/hot/category/24784","https://theqoo.net/hot/category/24788") testUrl2.forEach { url -> Jsoup.connect(url) .userAgent(USAGT) diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt index 3fe8fbeb..0156af33 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt @@ -85,7 +85,9 @@ object WorkersDb { try { getRealm().writeBlocking { try { - this.copyToRealm(it, UpdatePolicy.ERROR) + if(query("chosung == $0",it.chosung).find().size == 0) { + this.copyToRealm(it, UpdatePolicy.ERROR) + } } catch (e : Exception) { } diff --git a/app/src/main/res/layout/behavior.xml b/app/src/main/res/layout/behavior.xml index 76b462a9..5777a03d 100644 --- a/app/src/main/res/layout/behavior.xml +++ b/app/src/main/res/layout/behavior.xml @@ -62,21 +62,21 @@ - + - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 72cda250..8929ea8c 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -88,4 +88,11 @@ sans-serif-light + + \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index 3917f87a..b11fde1f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,24 @@ plugins { tasks.register("clean") { delete(rootProject.buildDir) } + + +ext { +// minSdkVersion = 7 +// targetSdkVersion = 23 +// compileSdkVersion = 23 +// buildToolsVersion = '23.0.2' +// +// sourceCompatibility = JavaVersion.VERSION_1_7 +// targetCompatibility = JavaVersion.VERSION_1_7 +// +// versionCode = 1 +// versionName = '0.9.5' +// +// supportLibVersion = '23.3.0' +// playLibVersion = '8.4.0' +} + //repositories { // mavenCentral() // maven { diff --git a/gradle.properties b/gradle.properties index 6b082470..717fbe0d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,4 +23,5 @@ android.useAndroidX=true # thereby reducing the size of the R class for that library android.nonTransitiveRClass=true android.defaults.buildfeatures.buildconfig=true -android.nonFinalResIds=true \ No newline at end of file +android.nonFinalResIds=true +android.enableJetifier=true \ No newline at end of file diff --git a/library/.gitignore b/library/.gitignore new file mode 100644 index 00000000..e8fa30f8 --- /dev/null +++ b/library/.gitignore @@ -0,0 +1,2 @@ +/build +*.iml diff --git a/library/build.gradle b/library/build.gradle new file mode 100644 index 00000000..032f2128 --- /dev/null +++ b/library/build.gradle @@ -0,0 +1,42 @@ +apply plugin: 'com.android.library' +apply from: 'maven_publish.gradle' + +android { + compileSdk 34 + + defaultConfig { + minSdk 14 + //noinspection ExpiredTargetSdkVersion + targetSdk 31 + vectorDrawables.useSupportLibrary = true + consumerProguardFiles 'proguard-rules.pro' + } + namespace 'com.wuadam.awesomewebview' + lint { + abortOnError false + } + + // https://developer.android.com/build/publish-library/configure-pub-variants?hl=zh-cn#single-pub-var + // https://stackoverflow.com/a/71366104 + publishing { + singleVariant('release') { + withSourcesJar() + withJavadocJar() + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.annotation:annotation:1.7.0' + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:1.10.0' + implementation 'com.nineoldandroids:library:2.4.0' + implementation project(':utils') +// implementation("com.thefinestartist:utils:0.9.5") + + +// api ('com.yanzhenjie:permission:2.0.3') { +// exclude group: 'com.android.support' +// } +} diff --git a/library/maven_publish.gradle b/library/maven_publish.gradle new file mode 100644 index 00000000..241d750e --- /dev/null +++ b/library/maven_publish.gradle @@ -0,0 +1,16 @@ +apply plugin: 'maven-publish' + +// https://developer.android.com/build/publish-library/upload-library?hl=zh-cn#create-pub +publishing { + publications { + release(MavenPublication) { + groupId = 'com.github.hzw1199' + artifactId = 'AwesomeWebView-Android' + version = '2.1.0' + + afterEvaluate { + from components.release + } + } + } +} diff --git a/library/proguard-rules.pro b/library/proguard-rules.pro new file mode 100644 index 00000000..44913e03 --- /dev/null +++ b/library/proguard-rules.pro @@ -0,0 +1,25 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in /Users/Leonardo/Library/Android/sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# AndPermission +-dontwarn com.yanzhenjie.permission.** + +# JavascriptInterface +-keepclassmembers class com.wuadam.awesomewebview.helpers.VideoJsHelper$JavascriptInterface { + public *; +} \ No newline at end of file diff --git a/library/src/main/AndroidManifest.xml b/library/src/main/AndroidManifest.xml new file mode 100644 index 00000000..202eff76 --- /dev/null +++ b/library/src/main/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + \ No newline at end of file diff --git a/library/src/main/java/com/wuadam/awesomewebview/AwesomeWebView.java b/library/src/main/java/com/wuadam/awesomewebview/AwesomeWebView.java new file mode 100644 index 00000000..01cc529c --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/AwesomeWebView.java @@ -0,0 +1,1016 @@ +package com.wuadam.awesomewebview; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import androidx.annotation.AnimRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; +import android.webkit.WebSettings; + +import com.thefinestartist.Base; +import com.thefinestartist.utils.content.Ctx; +import com.thefinestartist.utils.content.Res; +import com.wuadam.awesomewebview.enums.Position; +import com.wuadam.awesomewebview.listeners.BroadCastManager; +import com.wuadam.awesomewebview.listeners.WebViewListener; +import com.wuadam.awesomewebview.objects.CustomMenu; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Created by Leonardo on 11/21/15. + */ +public class AwesomeWebView { + + public static class Builder implements Serializable { + + protected final transient Context context; + protected transient List listeners = new ArrayList<>(); + + protected Integer key; + + protected Boolean rtl; + protected Integer theme; + + protected Integer statusBarColor; + protected Boolean statusBarIconDark; + + protected Integer toolbarColor; + protected Boolean toolbarVisible; + + protected Integer iconDefaultColor; + protected Integer iconDisabledColor; + protected Integer iconPressedColor; + protected Integer iconSelector; + + protected Boolean showIconClose; + protected Boolean disableIconClose; + protected Boolean showIconBack; + protected Boolean disableIconBack; + protected Boolean showIconForward; + protected Boolean disableIconForward; + protected Boolean showIconMenu; + protected Boolean disableIconMenu; + + protected Boolean showDivider; + protected Boolean gradientDivider; + protected Integer dividerColor; + protected Float dividerHeight; + + protected Boolean showProgressBar; + protected Integer progressBarColor; + protected Float progressBarHeight; + protected Position progressBarPosition; + + protected String titleDefault; + protected Boolean updateTitleFromHtml; + protected Float titleSize; + protected String titleFont; + protected Integer titleColor; + + protected Boolean showUrl; + protected Float urlSize; + protected String urlFont; + protected Integer urlColor; + + protected Integer menuColor; + protected Integer menuDropShadowColor; + protected Float menuDropShadowSize; + protected Integer menuSelector; + + protected Float menuTextSize; + protected String menuTextFont; + protected Integer menuTextColor; + + protected Integer menuTextGravity; + protected Float menuTextPaddingLeft; + protected Float menuTextPaddingRight; + + protected Boolean showMenuRefresh; + protected Integer stringResRefresh; + protected Boolean showMenuFind; + protected Integer stringResFind; + protected Boolean showMenuShareVia; + protected Integer stringResShareVia; + protected Boolean showMenuCopyLink; + protected Integer stringResCopyLink; + protected Boolean showMenuOpenWith; + protected Integer stringResOpenWith; + protected Boolean showMenuSavePhoto; + protected Integer stringResSavePhoto; + protected Boolean showToastPhotoSavedOrFailed; + protected Integer stringResPhotoSavedTo; + protected Integer stringResPhotoSaveFailed; + protected Boolean fileChooserEnabled; + protected Integer stringResFileChooserTitle; + + protected List customMenus = new ArrayList<>(); + + protected Integer animationOpenEnter = R.anim.modal_activity_open_enter; + protected Integer animationOpenExit = R.anim.modal_activity_open_exit; + protected Integer animationCloseEnter; + protected Integer animationCloseExit; + + protected Boolean backPressToClose; + protected Integer stringResCopiedToClipboard; + + protected Boolean webViewSupportZoom; + protected Boolean webViewMediaPlaybackRequiresUserGesture; + protected Boolean webViewBuiltInZoomControls; + protected Boolean webViewDisplayZoomControls; + protected Boolean webViewAllowFileAccess; + protected Boolean webViewAllowContentAccess; + protected Boolean webViewLoadWithOverviewMode; + protected Boolean webViewSaveFormData; + protected Integer webViewTextZoom; + protected Boolean webViewUseWideViewPort; + protected Boolean webViewSupportMultipleWindows; + protected WebSettings.LayoutAlgorithm webViewLayoutAlgorithm; + protected String webViewStandardFontFamily; + protected String webViewFixedFontFamily; + protected String webViewSansSerifFontFamily; + protected String webViewSerifFontFamily; + protected String webViewCursiveFontFamily; + protected String webViewFantasyFontFamily; + protected Integer webViewMinimumFontSize; + protected Integer webViewMinimumLogicalFontSize; + protected Integer webViewDefaultFontSize; + protected Integer webViewDefaultFixedFontSize; + protected Boolean webViewLoadsImagesAutomatically; + protected Boolean webViewBlockNetworkImage; + protected Boolean webViewBlockNetworkLoads; + protected Boolean webViewJavaScriptEnabled; + protected Boolean webViewAllowUniversalAccessFromFileURLs; + protected Boolean webViewAllowFileAccessFromFileURLs; + protected String webViewGeolocationDatabasePath; + protected Boolean webViewAppCacheEnabled; + protected Boolean webViewDatabaseEnabled; + protected Boolean webViewDomStorageEnabled; + protected Boolean webViewGeolocationEnabled; + protected Boolean webViewJavaScriptCanOpenWindowsAutomatically; + protected String webViewDefaultTextEncodingName; + protected String webViewUserAgentString; + protected Boolean webViewUserAgentAppend; + protected Boolean webViewNeedInitialFocus; + protected Integer webViewCacheMode; + protected Integer webViewMixedContentMode; + protected Boolean webViewOffscreenPreRaster; + protected Boolean webViewAppJumpEnabled; + protected Boolean webViewCookieEnabled; + protected Boolean webViewCameraEnabled; + protected Boolean webViewAudioEnabled; + + protected String injectJavaScript; + protected Boolean injectJavaScriptMainPage; + + protected Map> injectCookies; + + protected String mimeType; + protected String encoding; + protected String data; + protected String url; + protected Map extraHeaders; + protected Boolean extraHeadersMainPage; + + public Builder(@NonNull Activity activity) { + this.context = activity; + Base.initialize(activity); + } + + /** + * If you use context instead of activity, AwesomeWebView won't be able to override activity + * animation. + * Try to create builder with Activity if it's possible. + */ + public Builder(@NonNull Context context) { + this.context = context; + Base.initialize(context); + } + + public Builder setWebViewListener(WebViewListener listener) { + listeners.clear(); + listeners.add(listener); + return this; + } + + public Builder addWebViewListener(WebViewListener listener) { + listeners.add(listener); + return this; + } + + public Builder removeWebViewListener(WebViewListener listener) { + listeners.remove(listener); + return this; + } + + public Builder rtl(boolean rtl) { + this.rtl = rtl; + return this; + } + + public Builder theme(@StyleRes int theme) { + this.theme = theme; + return this; + } + + public Builder statusBarColor(@ColorInt int color) { + this.statusBarColor = color; + return this; + } + + public Builder statusBarColorRes(@ColorRes int colorRes) { + this.statusBarColor = Res.getColor(colorRes); + return this; + } + + public Builder statusBarIconDark(boolean statusBarIconDark) { + this.statusBarIconDark = statusBarIconDark; + return this; + } + + public Builder toolbarColor(@ColorInt int color) { + this.toolbarColor = color; + return this; + } + + public Builder toolbarVisible(boolean toolbarVisible) { + this.toolbarVisible = toolbarVisible; + return this; + } + + public Builder toolbarColorRes(@ColorRes int colorRes) { + this.toolbarColor = Res.getColor(colorRes); + return this; + } + + public Builder iconDefaultColor(@ColorInt int color) { + this.iconDefaultColor = color; + return this; + } + + public Builder iconDefaultColorRes(@ColorRes int color) { + this.iconDefaultColor = Res.getColor(color); + return this; + } + + public Builder iconDisabledColor(@ColorInt int color) { + this.iconDisabledColor = color; + return this; + } + + public Builder iconDisabledColorRes(@ColorRes int colorRes) { + this.iconDisabledColor = Res.getColor(colorRes); + return this; + } + + public Builder iconPressedColor(@ColorInt int color) { + this.iconPressedColor = color; + return this; + } + + public Builder iconPressedColorRes(@ColorRes int colorRes) { + this.iconPressedColor = Res.getColor(colorRes); + return this; + } + + public Builder iconSelector(@DrawableRes int selectorRes) { + this.iconSelector = selectorRes; + return this; + } + + public Builder showIconClose(boolean showIconClose) { + this.showIconClose = showIconClose; + return this; + } + + public Builder disableIconClose(boolean disableIconClose) { + this.disableIconClose = disableIconClose; + return this; + } + + public Builder showIconBack(boolean showIconBack) { + this.showIconBack = showIconBack; + return this; + } + + public Builder disableIconBack(boolean disableIconBack) { + this.disableIconBack = disableIconBack; + return this; + } + + public Builder showIconForward(boolean showIconForward) { + this.showIconForward = showIconForward; + return this; + } + + public Builder disableIconForward(boolean disableIconForward) { + this.disableIconForward = disableIconForward; + return this; + } + + public Builder showIconMenu(boolean showIconMenu) { + this.showIconMenu = showIconMenu; + return this; + } + + public Builder disableIconMenu(boolean disableIconMenu) { + this.disableIconMenu = disableIconMenu; + return this; + } + + public Builder showDivider(boolean showDivider) { + this.showDivider = showDivider; + return this; + } + + public Builder gradientDivider(boolean gradientDivider) { + this.gradientDivider = gradientDivider; + return this; + } + + public Builder dividerColor(@ColorInt int color) { + this.dividerColor = color; + return this; + } + + public Builder dividerColorRes(@ColorRes int colorRes) { + this.dividerColor = Res.getColor(colorRes); + return this; + } + + public Builder dividerHeight(float height) { + this.dividerHeight = height; + return this; + } + + public Builder dividerHeight(int height) { + this.dividerHeight = (float) height; + return this; + } + + public Builder dividerHeightRes(@DimenRes int height) { + this.dividerHeight = Res.getDimension(height); + return this; + } + + public Builder showProgressBar(boolean showProgressBar) { + this.showProgressBar = showProgressBar; + return this; + } + + public Builder progressBarColor(@ColorInt int color) { + this.progressBarColor = color; + return this; + } + + public Builder progressBarColorRes(@ColorRes int colorRes) { + this.progressBarColor = Res.getColor(colorRes); + return this; + } + + public Builder progressBarHeight(float height) { + this.progressBarHeight = height; + return this; + } + + public Builder progressBarHeight(int height) { + this.progressBarHeight = (float) height; + return this; + } + + public Builder progressBarHeightRes(@DimenRes int height) { + this.progressBarHeight = Res.getDimension(height); + return this; + } + + public Builder progressBarPosition(@NonNull Position position) { + this.progressBarPosition = position; + return this; + } + + public Builder titleDefault(@NonNull String title) { + this.titleDefault = title; + return this; + } + + public Builder titleDefaultRes(@StringRes int stringRes) { + this.titleDefault = Res.getString(stringRes); + return this; + } + + public Builder updateTitleFromHtml(boolean updateTitleFromHtml) { + this.updateTitleFromHtml = updateTitleFromHtml; + return this; + } + + public Builder titleSize(float titleSize) { + this.titleSize = titleSize; + return this; + } + + public Builder titleSize(int titleSize) { + this.titleSize = (float) titleSize; + return this; + } + + public Builder titleSizeRes(@DimenRes int titleSize) { + this.titleSize = Res.getDimension(titleSize); + return this; + } + + public Builder titleFont(String titleFont) { + this.titleFont = titleFont; + return this; + } + + public Builder titleColor(@ColorInt int color) { + this.titleColor = color; + return this; + } + + public Builder titleColorRes(@ColorRes int colorRes) { + this.titleColor = Res.getColor(colorRes); + return this; + } + + public Builder showUrl(boolean showUrl) { + this.showUrl = showUrl; + return this; + } + + public Builder urlSize(float urlSize) { + this.urlSize = urlSize; + return this; + } + + public Builder urlSize(int urlSize) { + this.urlSize = (float) urlSize; + return this; + } + + public Builder urlSizeRes(@DimenRes int urlSize) { + this.urlSize = Res.getDimension(urlSize); + return this; + } + + public Builder urlFont(String urlFont) { + this.urlFont = urlFont; + return this; + } + + public Builder urlColor(@ColorInt int color) { + this.urlColor = color; + return this; + } + + public Builder urlColorRes(@ColorRes int colorRes) { + this.urlColor = Res.getColor(colorRes); + return this; + } + + public Builder menuColor(@ColorInt int color) { + this.menuColor = color; + return this; + } + + public Builder menuColorRes(@ColorRes int colorRes) { + this.menuColor = Res.getColor(colorRes); + return this; + } + + public Builder menuTextGravity(int gravity) { + this.menuTextGravity = gravity; + return this; + } + + public Builder menuTextPaddingLeft(float menuTextPaddingLeft) { + this.menuTextPaddingLeft = menuTextPaddingLeft; + return this; + } + + public Builder menuTextPaddingLeft(int menuTextPaddingLeft) { + this.menuTextPaddingLeft = (float) menuTextPaddingLeft; + return this; + } + + public Builder menuTextPaddingLeftRes(@DimenRes int menuTextPaddingLeft) { + this.menuTextPaddingLeft = Res.getDimension(menuTextPaddingLeft); + return this; + } + + public Builder menuTextPaddingRight(float menuTextPaddingRight) { + this.menuTextPaddingRight = menuTextPaddingRight; + return this; + } + + public Builder menuTextPaddingRight(int menuTextPaddingRight) { + this.menuTextPaddingRight = (float) menuTextPaddingRight; + return this; + } + + public Builder menuTextPaddingRightRes(@DimenRes int menuTextPaddingRight) { + this.menuTextPaddingRight = Res.getDimension(menuTextPaddingRight); + return this; + } + + public Builder menuDropShadowColor(@ColorInt int color) { + this.menuDropShadowColor = color; + return this; + } + + public Builder menuDropShadowColorRes(@ColorRes int colorRes) { + this.menuDropShadowColor = Res.getColor(colorRes); + return this; + } + + public Builder menuDropShadowSize(float menuDropShadowSize) { + this.menuDropShadowSize = menuDropShadowSize; + return this; + } + + public Builder menuDropShadowSize(int menuDropShadowSize) { + this.menuDropShadowSize = (float) menuDropShadowSize; + return this; + } + + public Builder menuDropShadowSizeRes(@DimenRes int menuDropShadowSize) { + this.menuDropShadowSize = Res.getDimension(menuDropShadowSize); + return this; + } + + public Builder menuSelector(@DrawableRes int selectorRes) { + this.menuSelector = selectorRes; + return this; + } + + public Builder menuTextSize(float menuTextSize) { + this.menuTextSize = menuTextSize; + return this; + } + + public Builder menuTextSize(int menuTextSize) { + this.menuTextSize = (float) menuTextSize; + return this; + } + + public Builder menuTextSizeRes(@DimenRes int menuTextSize) { + this.menuTextSize = Res.getDimension(menuTextSize); + return this; + } + + public Builder menuTextFont(String menuTextFont) { + this.menuTextFont = menuTextFont; + return this; + } + + public Builder menuTextColor(@ColorInt int color) { + this.menuTextColor = color; + return this; + } + + public Builder menuTextColorRes(@ColorRes int colorRes) { + this.menuTextColor = Res.getColor(colorRes); + return this; + } + + public Builder showMenuRefresh(boolean showMenuRefresh) { + this.showMenuRefresh = showMenuRefresh; + return this; + } + + public Builder stringResRefresh(@StringRes int stringResRefresh) { + this.stringResRefresh = stringResRefresh; + return this; + } + + public Builder showMenuFind(boolean showMenuFind) { + this.showMenuFind = showMenuFind; + return this; + } + + public Builder stringResFind(@StringRes int stringResFind) { + this.stringResFind = stringResFind; + return this; + } + + public Builder showMenuShareVia(boolean showMenuShareVia) { + this.showMenuShareVia = showMenuShareVia; + return this; + } + + public Builder stringResShareVia(@StringRes int stringResShareVia) { + this.stringResShareVia = stringResShareVia; + return this; + } + + public Builder showMenuCopyLink(boolean showMenuCopyLink) { + this.showMenuCopyLink = showMenuCopyLink; + return this; + } + + public Builder stringResCopyLink(@StringRes int stringResCopyLink) { + this.stringResCopyLink = stringResCopyLink; + return this; + } + + public Builder showMenuOpenWith(boolean showMenuOpenWith) { + this.showMenuOpenWith = showMenuOpenWith; + return this; + } + + public Builder stringResOpenWith(@StringRes int stringResOpenWith) { + this.stringResOpenWith = stringResOpenWith; + return this; + } + + public Builder showMenuSavePhoto(boolean showMenuSavePhoto) { + this.showMenuSavePhoto = showMenuSavePhoto; + return this; + } + + public Builder stringResSavePhoto(@StringRes int stringResSavePhoto) { + this.stringResSavePhoto = stringResSavePhoto; + return this; + } + + public Builder showToastPhotoSavedOrFailed(boolean showToastPhotoSavedOrFailed) { + this.showToastPhotoSavedOrFailed = showToastPhotoSavedOrFailed; + return this; + } + + public Builder stringResPhotoSavedTo(@StringRes int stringResPhotoSavedTo) { + this.stringResPhotoSavedTo = stringResPhotoSavedTo; + return this; + } + + public Builder stringResPhotoSaveFailed(@StringRes int stringResPhotoSaveFailed) { + this.stringResPhotoSaveFailed = stringResPhotoSaveFailed; + return this; + } + + public Builder fileChooserEnabled(boolean fileChooserEnabled) { + this.fileChooserEnabled = fileChooserEnabled; + return this; + } + + public Builder stringResFileChooserTitle(@StringRes int stringResFileChooserTitle) { + this.stringResFileChooserTitle = stringResFileChooserTitle; + return this; + } + + public Builder customMenus(@NonNull List customMenus) { + this.customMenus = customMenus; + return this; + } + + public Builder addCustomMenu(@NonNull CustomMenu customMenu) { + if (customMenus == null) { + customMenus = new ArrayList<>(1); + } + customMenus.add(customMenu); + return this; + } + + public Builder setCustomAnimations(@AnimRes int animationOpenEnter, + @AnimRes int animationOpenExit, @AnimRes int animationCloseEnter, + @AnimRes int animationCloseExit) { + this.animationOpenEnter = animationOpenEnter; + this.animationOpenExit = animationOpenExit; + this.animationCloseEnter = animationCloseEnter; + this.animationCloseExit = animationCloseExit; + return this; + } + + /** + * @deprecated As of release 1.0.1, replaced by {@link #setCustomAnimations(int, int, int, int)} + */ + public Builder setCloseAnimations(@AnimRes int animationCloseEnter, + @AnimRes int animationCloseExit) { + this.animationCloseEnter = animationCloseEnter; + this.animationCloseExit = animationCloseExit; + return this; + } + + public Builder backPressToClose(boolean backPressToClose) { + this.backPressToClose = backPressToClose; + return this; + } + + public Builder stringResCopiedToClipboard(@StringRes int stringResCopiedToClipboard) { + this.stringResCopiedToClipboard = stringResCopiedToClipboard; + return this; + } + + public Builder webViewSupportZoom(boolean webViewSupportZoom) { + this.webViewSupportZoom = webViewSupportZoom; + return this; + } + + public Builder webViewMediaPlaybackRequiresUserGesture( + boolean webViewMediaPlaybackRequiresUserGesture) { + this.webViewMediaPlaybackRequiresUserGesture = webViewMediaPlaybackRequiresUserGesture; + return this; + } + + public Builder webViewBuiltInZoomControls(boolean webViewBuiltInZoomControls) { + this.webViewBuiltInZoomControls = webViewBuiltInZoomControls; + return this; + } + + public Builder webViewDisplayZoomControls(boolean webViewDisplayZoomControls) { + this.webViewDisplayZoomControls = webViewDisplayZoomControls; + return this; + } + + public Builder webViewAllowFileAccess(boolean webViewAllowFileAccess) { + this.webViewAllowFileAccess = webViewAllowFileAccess; + return this; + } + + public Builder webViewAllowContentAccess(boolean webViewAllowContentAccess) { + this.webViewAllowContentAccess = webViewAllowContentAccess; + return this; + } + + public Builder webViewLoadWithOverviewMode(boolean webViewLoadWithOverviewMode) { + this.webViewLoadWithOverviewMode = webViewLoadWithOverviewMode; + return this; + } + + public Builder webViewSaveFormData(boolean webViewSaveFormData) { + this.webViewSaveFormData = webViewSaveFormData; + return this; + } + + public Builder webViewTextZoom(int webViewTextZoom) { + this.webViewTextZoom = webViewTextZoom; + return this; + } + + public Builder webViewUseWideViewPort(boolean webViewUseWideViewPort) { + this.webViewUseWideViewPort = webViewUseWideViewPort; + return this; + } + + public Builder webViewSupportMultipleWindows(boolean webViewSupportMultipleWindows) { + this.webViewSupportMultipleWindows = webViewSupportMultipleWindows; + return this; + } + + public Builder webViewLayoutAlgorithm(WebSettings.LayoutAlgorithm webViewLayoutAlgorithm) { + this.webViewLayoutAlgorithm = webViewLayoutAlgorithm; + return this; + } + + public Builder webViewStandardFontFamily(String webViewStandardFontFamily) { + this.webViewStandardFontFamily = webViewStandardFontFamily; + return this; + } + + public Builder webViewFixedFontFamily(String webViewFixedFontFamily) { + this.webViewFixedFontFamily = webViewFixedFontFamily; + return this; + } + + public Builder webViewSansSerifFontFamily(String webViewSansSerifFontFamily) { + this.webViewSansSerifFontFamily = webViewSansSerifFontFamily; + return this; + } + + public Builder webViewSerifFontFamily(String webViewSerifFontFamily) { + this.webViewSerifFontFamily = webViewSerifFontFamily; + return this; + } + + public Builder webViewCursiveFontFamily(String webViewCursiveFontFamily) { + this.webViewCursiveFontFamily = webViewCursiveFontFamily; + return this; + } + + public Builder webViewFantasyFontFamily(String webViewFantasyFontFamily) { + this.webViewFantasyFontFamily = webViewFantasyFontFamily; + return this; + } + + public Builder webViewMinimumFontSize(int webViewMinimumFontSize) { + this.webViewMinimumFontSize = webViewMinimumFontSize; + return this; + } + + public Builder webViewMinimumLogicalFontSize(int webViewMinimumLogicalFontSize) { + this.webViewMinimumLogicalFontSize = webViewMinimumLogicalFontSize; + return this; + } + + public Builder webViewDefaultFontSize(int webViewDefaultFontSize) { + this.webViewDefaultFontSize = webViewDefaultFontSize; + return this; + } + + public Builder webViewDefaultFixedFontSize(int webViewDefaultFixedFontSize) { + this.webViewDefaultFixedFontSize = webViewDefaultFixedFontSize; + return this; + } + + public Builder webViewLoadsImagesAutomatically(boolean webViewLoadsImagesAutomatically) { + this.webViewLoadsImagesAutomatically = webViewLoadsImagesAutomatically; + return this; + } + + public Builder webViewBlockNetworkImage(boolean webViewBlockNetworkImage) { + this.webViewBlockNetworkImage = webViewBlockNetworkImage; + return this; + } + + public Builder webViewBlockNetworkLoads(boolean webViewBlockNetworkLoads) { + this.webViewBlockNetworkLoads = webViewBlockNetworkLoads; + return this; + } + + public Builder webViewJavaScriptEnabled(boolean webViewJavaScriptEnabled) { + this.webViewJavaScriptEnabled = webViewJavaScriptEnabled; + return this; + } + + public Builder webViewAllowUniversalAccessFromFileURLs( + boolean webViewAllowUniversalAccessFromFileURLs) { + this.webViewAllowUniversalAccessFromFileURLs = webViewAllowUniversalAccessFromFileURLs; + return this; + } + + public Builder webViewAllowFileAccessFromFileURLs(boolean webViewAllowFileAccessFromFileURLs) { + this.webViewAllowFileAccessFromFileURLs = webViewAllowFileAccessFromFileURLs; + return this; + } + + public Builder webViewGeolocationDatabasePath(String webViewGeolocationDatabasePath) { + this.webViewGeolocationDatabasePath = webViewGeolocationDatabasePath; + return this; + } + + public Builder webViewAppCacheEnabled(boolean webViewAppCacheEnabled) { + this.webViewAppCacheEnabled = webViewAppCacheEnabled; + return this; + } + + public Builder webViewDatabaseEnabled(boolean webViewDatabaseEnabled) { + this.webViewDatabaseEnabled = webViewDatabaseEnabled; + return this; + } + + public Builder webViewDomStorageEnabled(boolean webViewDomStorageEnabled) { + this.webViewDomStorageEnabled = webViewDomStorageEnabled; + return this; + } + + public Builder webViewGeolocationEnabled(boolean webViewGeolocationEnabled) { + this.webViewGeolocationEnabled = webViewGeolocationEnabled; + return this; + } + + public Builder webViewJavaScriptCanOpenWindowsAutomatically( + boolean webViewJavaScriptCanOpenWindowsAutomatically) { + this.webViewJavaScriptCanOpenWindowsAutomatically = + webViewJavaScriptCanOpenWindowsAutomatically; + return this; + } + + public Builder webViewDefaultTextEncodingName(String webViewDefaultTextEncodingName) { + this.webViewDefaultTextEncodingName = webViewDefaultTextEncodingName; + return this; + } + + public Builder webViewUserAgentString(String webViewUserAgentString) { + this.webViewUserAgentString = webViewUserAgentString; + return this; + } + + public Builder webViewUserAgentAppend(boolean webViewUserAgentAppend) { + this.webViewUserAgentAppend = webViewUserAgentAppend; + return this; + } + + public Builder webViewNeedInitialFocus(boolean webViewNeedInitialFocus) { + this.webViewNeedInitialFocus = webViewNeedInitialFocus; + return this; + } + + public Builder webViewCacheMode(int webViewCacheMode) { + this.webViewCacheMode = webViewCacheMode; + return this; + } + + public Builder webViewMixedContentMode(int webViewMixedContentMode) { + this.webViewMixedContentMode = webViewMixedContentMode; + return this; + } + + public Builder webViewOffscreenPreRaster(boolean webViewOffscreenPreRaster) { + this.webViewOffscreenPreRaster = webViewOffscreenPreRaster; + return this; + } + + public Builder webViewAppJumpEnabled(boolean webViewAppJumpEnabled) { + this.webViewAppJumpEnabled = webViewAppJumpEnabled; + return this; + } + + public Builder webViewCookieEnabled(boolean webViewCookieEnabled) { + this.webViewCookieEnabled = webViewCookieEnabled; + return this; + } + + public Builder webViewCameraEnabled(boolean webViewCameraEnabled) { + this.webViewCameraEnabled = webViewCameraEnabled; + return this; + } + + public Builder webViewAudioEnabled(boolean webViewAudioEnabled) { + this.webViewAudioEnabled = webViewAudioEnabled; + return this; + } + + public Builder setHeader(Map extraHeaders) { + this.extraHeaders = extraHeaders; + return this; + } + + public Builder headersMainPage(boolean extraHeadersMainPage) { + this.extraHeadersMainPage = extraHeadersMainPage; + return this; + } + + /** + * @deprecated As of release 1.1.1, replaced by {@link #webViewUserAgentString(String)} + * Use setUserAgentString("Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 + * Firefox/4.0") instead + */ + public Builder webViewDesktopMode(boolean webViewDesktopMode) { + return webViewUserAgentString( + "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.4) Gecko/20100101 Firefox/4.0"); + } + + public Builder injectJavaScript(String injectJavaScript) { + this.injectJavaScript = injectJavaScript; + return this; + } + + public Builder injectJavaScriptMainPage(boolean injectJavaScriptMainPage) { + this.injectJavaScriptMainPage = injectJavaScriptMainPage; + return this; + } + + public Builder injectCookies(Map> injectCookies) { + this.injectCookies = injectCookies; + return this; + } + + public void load(@StringRes int dataRes) { + load(Res.getString(dataRes)); + } + + public void load(String data) { + load(data, "text/html", "UTF-8"); + } + + public void load(String data, String mimeType, String encoding) { + this.mimeType = mimeType; + this.encoding = encoding; + show(null, data); + } + + public void show(@StringRes int urlRes) { + show(Res.getString(urlRes)); + } + + public void show(@NonNull String url) { + show(url, null); + } + + protected void show(String url, String data) { + this.url = url; + this.data = data; + this.key = System.identityHashCode(this); + + if (!listeners.isEmpty()) new BroadCastManager(context, key, listeners); + + Intent intent = new Intent(context, AwesomeWebViewActivity.class); + intent.putExtra("builder", this); + + Ctx.startActivity(intent); + + if (context instanceof Activity) { + ((Activity) context).overridePendingTransition(animationOpenEnter, animationOpenExit); + } + } + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/AwesomeWebViewActivity.java b/library/src/main/java/com/wuadam/awesomewebview/AwesomeWebViewActivity.java new file mode 100644 index 00000000..693a21f6 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/AwesomeWebViewActivity.java @@ -0,0 +1,1748 @@ +package com.wuadam.awesomewebview; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.StateListDrawable; +import android.net.MailTo; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.provider.MediaStore; +import androidx.annotation.DrawableRes; +import androidx.annotation.RequiresApi; +import com.google.android.material.appbar.AppBarLayout; +import androidx.coordinatorlayout.widget.CoordinatorLayout; +import com.google.android.material.snackbar.Snackbar; +import androidx.core.content.ContextCompat; +import androidx.core.content.FileProvider; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatImageButton; +import androidx.appcompat.widget.Toolbar; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewConfiguration; +import android.view.ViewGroup; +import android.view.Window; +import android.view.WindowManager; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.webkit.DownloadListener; +import android.webkit.GeolocationPermissions; +import android.webkit.PermissionRequest; +import android.webkit.ValueCallback; +import android.webkit.WebChromeClient; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.widget.FrameLayout; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.ProgressBar; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import com.thefinestartist.converters.UnitConverter; +import com.thefinestartist.utils.etc.APILevel; +import com.thefinestartist.utils.service.ClipboardManagerUtil; +import com.thefinestartist.utils.ui.DisplayUtil; +import com.thefinestartist.utils.ui.ViewUtil; +import com.wuadam.awesomewebview.enums.Position; +import com.wuadam.awesomewebview.helpers.BitmapHelper; +import com.wuadam.awesomewebview.helpers.ColorHelper; +import com.wuadam.awesomewebview.helpers.DownPicUtil; +import com.wuadam.awesomewebview.helpers.PermissionHelper; +import com.wuadam.awesomewebview.helpers.TypefaceHelper; +import com.wuadam.awesomewebview.helpers.UrlParser; +import com.wuadam.awesomewebview.jsInterface.CommonJsHelper; +import com.wuadam.awesomewebview.listeners.BroadCastManager; +import com.wuadam.awesomewebview.objects.CustomMenu; +import com.wuadam.awesomewebview.views.ShadowLayout; +import com.wuadam.awesomewebview.views.VideoEnabledWebChromeClient; +import com.wuadam.awesomewebview.views.VideoEnabledWebView; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +//MailTo Imports + +/** + * Created by Leonardo on 11/14/15. + */ +public class AwesomeWebViewActivity extends AppCompatActivity + implements View.OnClickListener, Handler.Callback { + + protected int key; + + protected boolean rtl; + protected int theme; + + protected int statusBarColor; + protected boolean statusBarIconDark; + + protected int toolbarColor; + protected boolean toolbarVisible; + + protected int iconDefaultColor; + protected int iconDisabledColor; + protected int iconPressedColor; + protected int iconSelector; + + protected boolean showIconClose; + protected boolean disableIconClose; + protected boolean showIconBack; + protected boolean disableIconBack; + protected boolean showIconForward; + protected boolean disableIconForward; + protected boolean showIconMenu; + protected boolean disableIconMenu; + + protected boolean showDivider; + protected boolean gradientDivider; + protected int dividerColor; + protected float dividerHeight; + + protected boolean showProgressBar; + protected int progressBarColor; + protected float progressBarHeight; + protected Position progressBarPosition; + + protected String titleDefault; + protected boolean updateTitleFromHtml; + protected float titleSize; + protected String titleFont; + protected int titleColor; + + protected boolean showUrl; + protected float urlSize; + protected String urlFont; + protected int urlColor; + + protected int menuColor; + protected int menuDropShadowColor; + protected float menuDropShadowSize; + protected int menuSelector; + + protected float menuTextSize; + protected String menuTextFont; + protected int menuTextColor; + + protected int menuTextGravity; + protected float menuTextPaddingLeft; + protected float menuTextPaddingRight; + + protected boolean showMenuRefresh; + protected int stringResRefresh; + protected boolean showMenuFind; + protected int stringResFind; + protected boolean showMenuShareVia; + protected int stringResShareVia; + protected boolean showMenuCopyLink; + protected int stringResCopyLink; + protected boolean showMenuOpenWith; + protected int stringResOpenWith; + protected boolean showMenuSavePhoto; + protected int stringResSavePhoto; + protected boolean showToastPhotoSavedOrFailed; + protected int stringResPhotoSavedTo; + protected int stringResPhotoSaveFailed; + protected boolean fileChooserEnabled; + protected int stringResFileChooserTitle; + + protected List customMenus; + + protected int animationCloseEnter; + protected int animationCloseExit; + + protected boolean backPressToClose; + protected int stringResCopiedToClipboard; + + protected Boolean webViewSupportZoom; + protected Boolean webViewMediaPlaybackRequiresUserGesture; + protected Boolean webViewBuiltInZoomControls; + protected Boolean webViewDisplayZoomControls; + protected Boolean webViewAllowFileAccess; + protected Boolean webViewAllowContentAccess; + protected Boolean webViewLoadWithOverviewMode; + protected Boolean webViewSaveFormData; + protected Integer webViewTextZoom; + protected Boolean webViewUseWideViewPort; + protected Boolean webViewSupportMultipleWindows; + protected WebSettings.LayoutAlgorithm webViewLayoutAlgorithm; + protected String webViewStandardFontFamily; + protected String webViewFixedFontFamily; + protected String webViewSansSerifFontFamily; + protected String webViewSerifFontFamily; + protected String webViewCursiveFontFamily; + protected String webViewFantasyFontFamily; + protected Integer webViewMinimumFontSize; + protected Integer webViewMinimumLogicalFontSize; + protected Integer webViewDefaultFontSize; + protected Integer webViewDefaultFixedFontSize; + protected Boolean webViewLoadsImagesAutomatically; + protected Boolean webViewBlockNetworkImage; + protected Boolean webViewBlockNetworkLoads; + protected Boolean webViewJavaScriptEnabled; + protected Boolean webViewAllowUniversalAccessFromFileURLs; + protected Boolean webViewAllowFileAccessFromFileURLs; + protected String webViewGeolocationDatabasePath; + protected Boolean webViewAppCacheEnabled; + protected Boolean webViewDatabaseEnabled; + protected Boolean webViewDomStorageEnabled; + protected Boolean webViewGeolocationEnabled; + protected Boolean webViewJavaScriptCanOpenWindowsAutomatically; + protected String webViewDefaultTextEncodingName; + protected String webViewUserAgentString; + protected Boolean webViewUserAgentAppend; + protected Boolean webViewNeedInitialFocus; + protected Integer webViewCacheMode; + protected Integer webViewMixedContentMode; + protected Boolean webViewOffscreenPreRaster; + protected Boolean webViewAppJumpEnabled; + protected Boolean webViewCookieEnabled; + protected Boolean webViewCameraEnabled; + protected Boolean webViewAudioEnabled; + + protected String filePickerCamMessage; + protected ValueCallback filePickerFileMessage; + protected ValueCallback filePickerFilePath; + protected final static int FILE_PICKER_REQ_CODE = 1; + protected String FILE_TYPE = "*/*"; + + protected String injectJavaScript; + protected Boolean injectJavaScriptMainPage; + + protected Map> injectCookies; + + protected String mimeType; + protected String encoding; + protected String data; + protected String url; + protected Map extraHeaders; + protected Boolean extraHeadersMainPage; + protected CoordinatorLayout coordinatorLayout; + protected AppBarLayout appBar; + protected Toolbar toolbar; + protected RelativeLayout toolbarLayout; + protected TextView title; + protected TextView urlTv; + protected AppCompatImageButton close; + protected AppCompatImageButton back; + protected AppCompatImageButton forward; + protected AppCompatImageButton more; + protected WebView webView; + protected WebChromeClient webChromeClient; + protected WebViewClient webViewClient; + protected View gradient; + protected View divider; + protected ProgressBar progressBar; + protected RelativeLayout menuLayout; + protected ShadowLayout shadowLayout; + protected LinearLayout menuBackground; + protected LinearLayout menuRefresh; + protected TextView menuRefreshTv; + protected LinearLayout menuFind; + protected TextView menuFindTv; + protected LinearLayout menuShareVia; + protected TextView menuShareViaTv; + protected LinearLayout menuCopyLink; + protected TextView menuCopyLinkTv; + protected LinearLayout menuOpenWith; + protected TextView menuOpenWithTv; + protected FrameLayout webLayout; + DownloadListener downloadListener = new DownloadListener() { + @Override + public void onDownloadStart(String url, String userAgent, String contentDisposition, + String mimetype, long contentLength) { + BroadCastManager.onDownloadStart(AwesomeWebViewActivity.this, key, url, userAgent, + contentDisposition, mimetype, contentLength); + } + }; + + protected Handler handler = new Handler(this); + protected final int MSG_CLICK_ON_WEBVIEW = 1; + protected final int MSG_CLICK_ON_URL = 2; + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + protected void initializeOptions() { + Intent intent = getIntent(); + if (intent == null) return; + + AwesomeWebView.Builder builder = (AwesomeWebView.Builder) intent.getSerializableExtra("builder"); + + // set theme before resolving attributes depending on those + setTheme(builder.theme != null ? builder.theme : 0); + + // resolve themed attributes + TypedValue typedValue = new TypedValue(); + TypedArray typedArray = obtainStyledAttributes(typedValue.data, new int[]{ + android.R.attr.colorPrimaryDark, android.R.attr.colorPrimary, android.R.attr.colorAccent, + android.R.attr.textColorPrimary, android.R.attr.textColorSecondary, + android.R.attr.selectableItemBackground, android.R.attr.selectableItemBackgroundBorderless + }); + int colorPrimaryDark = typedArray.getColor(0, ContextCompat.getColor(this, R.color.finestGray)); + int colorPrimary = typedArray.getColor(1, ContextCompat.getColor(this, R.color.finestWhite)); + int colorAccent = typedArray.getColor(2, ContextCompat.getColor(this, R.color.finestBlack)); + int textColorPrimary = + typedArray.getColor(3, ContextCompat.getColor(this, R.color.finestBlack)); + int textColorSecondary = + typedArray.getColor(4, ContextCompat.getColor(this, R.color.finestSilver)); + int selectableItemBackground = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ? typedArray.getResourceId(5, 0) + : R.drawable.selector_light_theme; + int selectableItemBackgroundBorderless = + Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? typedArray.getResourceId(6, 0) + : R.drawable.selector_light_theme; + typedArray.recycle(); + + key = builder.key; + + rtl = builder.rtl != null ? builder.rtl : getResources().getBoolean(R.bool.is_right_to_left); + + statusBarColor = builder.statusBarColor != null ? builder.statusBarColor : colorPrimaryDark; + statusBarIconDark = builder.statusBarIconDark != null ? builder.statusBarIconDark : false; + + toolbarColor = builder.toolbarColor != null ? builder.toolbarColor : colorPrimary; + toolbarVisible = builder.toolbarVisible != null ? builder.toolbarVisible : true; + + iconDefaultColor = builder.iconDefaultColor != null ? builder.iconDefaultColor : colorAccent; + iconDisabledColor = builder.iconDisabledColor != null ? builder.iconDisabledColor + : ColorHelper.disableColor(iconDefaultColor); + iconPressedColor = + builder.iconPressedColor != null ? builder.iconPressedColor : iconDefaultColor; + iconSelector = + builder.iconSelector != null ? builder.iconSelector : selectableItemBackgroundBorderless; + + showIconClose = builder.showIconClose != null ? builder.showIconClose : true; + disableIconClose = builder.disableIconClose != null ? builder.disableIconClose : false; + showIconBack = builder.showIconBack != null ? builder.showIconBack : true; + disableIconBack = builder.disableIconBack != null ? builder.disableIconBack : false; + showIconForward = builder.showIconForward != null ? builder.showIconForward : true; + disableIconForward = builder.disableIconForward != null ? builder.disableIconForward : false; + showIconMenu = builder.showIconMenu != null ? builder.showIconMenu : true; + disableIconMenu = builder.disableIconMenu != null ? builder.disableIconMenu : false; + + showDivider = builder.showDivider != null ? builder.showDivider : true; + gradientDivider = builder.gradientDivider != null ? builder.gradientDivider : true; + dividerColor = builder.dividerColor != null ? builder.dividerColor + : ContextCompat.getColor(this, R.color.finestBlack10); + dividerHeight = builder.dividerHeight != null ? builder.dividerHeight + : getResources().getDimension(R.dimen.defaultDividerHeight); + + showProgressBar = builder.showProgressBar != null ? builder.showProgressBar : true; + progressBarColor = builder.progressBarColor != null ? builder.progressBarColor : colorAccent; + progressBarHeight = builder.progressBarHeight != null ? builder.progressBarHeight + : getResources().getDimension(R.dimen.defaultProgressBarHeight); + progressBarPosition = builder.progressBarPosition != null ? builder.progressBarPosition + : Position.BOTTOM_OF_TOOLBAR; + + titleDefault = builder.titleDefault; + updateTitleFromHtml = builder.updateTitleFromHtml != null ? builder.updateTitleFromHtml : true; + titleSize = builder.titleSize != null ? builder.titleSize + : getResources().getDimension(R.dimen.defaultTitleSize); + titleFont = builder.titleFont != null ? builder.titleFont : "Roboto-Medium.ttf"; + titleColor = builder.titleColor != null ? builder.titleColor : textColorPrimary; + + showUrl = builder.showUrl != null ? builder.showUrl : true; + urlSize = builder.urlSize != null ? builder.urlSize + : getResources().getDimension(R.dimen.defaultUrlSize); + urlFont = builder.urlFont != null ? builder.urlFont : "Roboto-Regular.ttf"; + urlColor = builder.urlColor != null ? builder.urlColor : textColorSecondary; + + menuColor = builder.menuColor != null ? builder.menuColor + : ContextCompat.getColor(this, R.color.finestWhite); + menuDropShadowColor = builder.menuDropShadowColor != null ? builder.menuDropShadowColor + : ContextCompat.getColor(this, R.color.finestBlack10); + menuDropShadowSize = builder.menuDropShadowSize != null ? builder.menuDropShadowSize + : getResources().getDimension(R.dimen.defaultMenuDropShadowSize); + menuSelector = builder.menuSelector != null ? builder.menuSelector : selectableItemBackground; + + menuTextSize = builder.menuTextSize != null ? builder.menuTextSize + : getResources().getDimension(R.dimen.defaultMenuTextSize); + menuTextFont = builder.menuTextFont != null ? builder.menuTextFont : "Roboto-Regular.ttf"; + menuTextColor = builder.menuTextColor != null ? builder.menuTextColor + : ContextCompat.getColor(this, R.color.finestBlack); + + menuTextGravity = builder.menuTextGravity != null ? builder.menuTextGravity + : Gravity.CENTER_VERTICAL | Gravity.START; + menuTextPaddingLeft = builder.menuTextPaddingLeft != null ? builder.menuTextPaddingLeft + : rtl ? getResources().getDimension(R.dimen.defaultMenuTextPaddingRight) + : getResources().getDimension(R.dimen.defaultMenuTextPaddingLeft); + menuTextPaddingRight = builder.menuTextPaddingRight != null ? builder.menuTextPaddingRight + : rtl ? getResources().getDimension(R.dimen.defaultMenuTextPaddingLeft) + : getResources().getDimension(R.dimen.defaultMenuTextPaddingRight); + + showMenuRefresh = builder.showMenuRefresh != null ? builder.showMenuRefresh : true; + stringResRefresh = + builder.stringResRefresh != null ? builder.stringResRefresh : R.string.refresh; + showMenuFind = builder.showMenuFind != null ? builder.showMenuFind : false; + stringResFind = builder.stringResFind != null ? builder.stringResFind : R.string.find; + showMenuShareVia = builder.showMenuShareVia != null ? builder.showMenuShareVia : true; + stringResShareVia = + builder.stringResShareVia != null ? builder.stringResShareVia : R.string.share_via; + showMenuCopyLink = builder.showMenuCopyLink != null ? builder.showMenuCopyLink : true; + stringResCopyLink = + builder.stringResCopyLink != null ? builder.stringResCopyLink : R.string.copy_link; + showMenuOpenWith = builder.showMenuOpenWith != null ? builder.showMenuOpenWith : true; + stringResOpenWith = + builder.stringResOpenWith != null ? builder.stringResOpenWith : R.string.open_with; + showMenuSavePhoto = builder.showMenuSavePhoto != null ? builder.showMenuSavePhoto : true; + stringResSavePhoto = + builder.stringResSavePhoto != null ? builder.stringResSavePhoto : R.string.save_photo; + showToastPhotoSavedOrFailed = builder.showToastPhotoSavedOrFailed != null ? builder.showToastPhotoSavedOrFailed : true; + stringResPhotoSavedTo = + builder.stringResPhotoSavedTo != null ? builder.stringResPhotoSavedTo : R.string.photo_saved_to; + stringResPhotoSaveFailed = + builder.stringResPhotoSaveFailed != null ? builder.stringResPhotoSaveFailed : R.string.photo_save_failed; + fileChooserEnabled = builder.fileChooserEnabled != null ? builder.fileChooserEnabled : true; + stringResFileChooserTitle = + builder.stringResFileChooserTitle != null ? builder.stringResFileChooserTitle : R.string.file_chooser; + + customMenus = builder.customMenus != null? builder.customMenus: new ArrayList(0); + + animationCloseEnter = builder.animationCloseEnter != null ? builder.animationCloseEnter + : R.anim.modal_activity_close_enter; + animationCloseExit = builder.animationCloseExit != null ? builder.animationCloseExit + : R.anim.modal_activity_close_exit; + + backPressToClose = builder.backPressToClose != null ? builder.backPressToClose : false; + stringResCopiedToClipboard = + builder.stringResCopiedToClipboard != null ? builder.stringResCopiedToClipboard + : R.string.copied_to_clipboard; + + webViewSupportZoom = builder.webViewSupportZoom; + webViewMediaPlaybackRequiresUserGesture = builder.webViewMediaPlaybackRequiresUserGesture; + webViewBuiltInZoomControls = + builder.webViewBuiltInZoomControls != null ? builder.webViewBuiltInZoomControls : false; + webViewDisplayZoomControls = + builder.webViewDisplayZoomControls != null ? builder.webViewDisplayZoomControls : false; + webViewAllowFileAccess = + builder.webViewAllowFileAccess != null ? builder.webViewAllowFileAccess : true; + webViewAllowContentAccess = builder.webViewAllowContentAccess; + webViewLoadWithOverviewMode = + builder.webViewLoadWithOverviewMode != null ? builder.webViewLoadWithOverviewMode : true; + webViewSaveFormData = builder.webViewSaveFormData; + webViewTextZoom = builder.webViewTextZoom; + webViewUseWideViewPort = builder.webViewUseWideViewPort; + webViewSupportMultipleWindows = builder.webViewSupportMultipleWindows; + webViewLayoutAlgorithm = builder.webViewLayoutAlgorithm; + webViewStandardFontFamily = builder.webViewStandardFontFamily; + webViewFixedFontFamily = builder.webViewFixedFontFamily; + webViewSansSerifFontFamily = builder.webViewSansSerifFontFamily; + webViewSerifFontFamily = builder.webViewSerifFontFamily; + webViewCursiveFontFamily = builder.webViewCursiveFontFamily; + webViewFantasyFontFamily = builder.webViewFantasyFontFamily; + webViewMinimumFontSize = builder.webViewMinimumFontSize; + webViewMinimumLogicalFontSize = builder.webViewMinimumLogicalFontSize; + webViewDefaultFontSize = builder.webViewDefaultFontSize; + webViewDefaultFixedFontSize = builder.webViewDefaultFixedFontSize; + webViewLoadsImagesAutomatically = builder.webViewLoadsImagesAutomatically; + webViewBlockNetworkImage = builder.webViewBlockNetworkImage; + webViewBlockNetworkLoads = builder.webViewBlockNetworkLoads; + webViewJavaScriptEnabled = + builder.webViewJavaScriptEnabled != null ? builder.webViewJavaScriptEnabled : true; + webViewAllowUniversalAccessFromFileURLs = builder.webViewAllowUniversalAccessFromFileURLs; + webViewAllowFileAccessFromFileURLs = builder.webViewAllowFileAccessFromFileURLs; + webViewGeolocationDatabasePath = builder.webViewGeolocationDatabasePath; + webViewAppCacheEnabled = + builder.webViewAppCacheEnabled != null ? builder.webViewAppCacheEnabled : true; + webViewDatabaseEnabled = builder.webViewDatabaseEnabled; + webViewDomStorageEnabled = + builder.webViewDomStorageEnabled != null ? builder.webViewDomStorageEnabled : true; + webViewGeolocationEnabled = builder.webViewGeolocationEnabled; + webViewJavaScriptCanOpenWindowsAutomatically = + builder.webViewJavaScriptCanOpenWindowsAutomatically; + webViewDefaultTextEncodingName = builder.webViewDefaultTextEncodingName; + webViewUserAgentString = builder.webViewUserAgentString; + webViewUserAgentAppend = builder.webViewUserAgentAppend; + webViewNeedInitialFocus = builder.webViewNeedInitialFocus; + webViewCacheMode = builder.webViewCacheMode; + webViewMixedContentMode = builder.webViewMixedContentMode; + webViewOffscreenPreRaster = builder.webViewOffscreenPreRaster; + webViewAppJumpEnabled = builder.webViewAppJumpEnabled != null ? builder.webViewAppJumpEnabled : true; + webViewCookieEnabled = builder.webViewCookieEnabled != null ? builder.webViewCookieEnabled : true; + webViewCameraEnabled = builder.webViewCameraEnabled != null ? builder.webViewCameraEnabled : true; + webViewAudioEnabled = builder.webViewAudioEnabled != null ? builder.webViewAudioEnabled : true; + + injectJavaScript = builder.injectJavaScript; + injectJavaScriptMainPage = builder.injectJavaScriptMainPage != null ? builder.injectJavaScriptMainPage : true; + extraHeadersMainPage = builder.extraHeadersMainPage != null ? builder.extraHeadersMainPage : true; + injectCookies = builder.injectCookies; + + mimeType = builder.mimeType; + encoding = builder.encoding; + data = builder.data; + url = builder.url; + extraHeaders = builder.extraHeaders; + } + + protected void bindViews() { + coordinatorLayout = findViewById(R.id.coordinatorLayout); + + appBar = findViewById(R.id.appBar); + toolbar = findViewById(R.id.toolbar); + toolbarLayout = findViewById(R.id.toolbarLayout); + + title = findViewById(R.id.title); + urlTv = findViewById(R.id.url); + + close = findViewById(R.id.close); + back = findViewById(R.id.back); + forward = findViewById(R.id.forward); + more = findViewById(R.id.more); + + close.setOnClickListener(this); + back.setOnClickListener(this); + forward.setOnClickListener(this); + more.setOnClickListener(this); + + gradient = findViewById(R.id.gradient); + divider = findViewById(R.id.divider); + progressBar = findViewById(R.id.progressBar); + + menuLayout = findViewById(R.id.menuLayout); + shadowLayout = findViewById(R.id.shadowLayout); + menuBackground = findViewById(R.id.menuBackground); + + menuRefresh = findViewById(R.id.menuRefresh); + menuRefreshTv = findViewById(R.id.menuRefreshTv); + menuFind = findViewById(R.id.menuFind); + menuFindTv = findViewById(R.id.menuFindTv); + menuShareVia = findViewById(R.id.menuShareVia); + menuShareViaTv = findViewById(R.id.menuShareViaTv); + menuCopyLink = findViewById(R.id.menuCopyLink); + menuCopyLinkTv = findViewById(R.id.menuCopyLinkTv); + menuOpenWith = findViewById(R.id.menuOpenWith); + menuOpenWithTv = findViewById(R.id.menuOpenWithTv); + + webLayout = findViewById(R.id.webLayout); + webView = buildWebView(); + webLayout.addView(webView); + } + + protected void layoutViews() { + if (!toolbarVisible) { + setSupportActionBar(toolbar); + toolbar.setVisibility(View.GONE); + } + + { // AppBar + float toolbarHeight = toolbarVisible? getResources().getDimension(R.dimen.toolbarHeight): 0; + if (!gradientDivider) toolbarHeight += dividerHeight; + CoordinatorLayout.LayoutParams params = + new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + (int) toolbarHeight); + appBar.setLayoutParams(params); + coordinatorLayout.requestLayout(); + } + + { // Toolbar + float toolbarHeight = toolbarVisible? getResources().getDimension(R.dimen.toolbarHeight): 0; + LinearLayout.LayoutParams params = + new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, (int) toolbarHeight); + toolbarLayout.setMinimumHeight((int) toolbarHeight); + toolbarLayout.setLayoutParams(params); + coordinatorLayout.requestLayout(); + } + + { // TextViews + int maxWidth = getMaxWidth(); + title.setMaxWidth(maxWidth); + urlTv.setMaxWidth(maxWidth); + requestCenterLayout(); + } + + { // Icons + updateIcon(close, rtl ? R.drawable.more : R.drawable.close); + updateIcon(back, R.drawable.back); + updateIcon(forward, R.drawable.forward); + updateIcon(more, rtl ? R.drawable.close : R.drawable.more); + } + + { // Divider + if (gradientDivider) { + float toolbarHeight = toolbarVisible? getResources().getDimension(R.dimen.toolbarHeight): 0; + CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) gradient.getLayoutParams(); + params.setMargins(0, (int) toolbarHeight, 0, 0); + gradient.setLayoutParams(params); + } + } + + { // ProgressBar + progressBar.setMinimumHeight((int) progressBarHeight); + CoordinatorLayout.LayoutParams params = + new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + (int) progressBarHeight); + float toolbarHeight = toolbarVisible? getResources().getDimension(R.dimen.toolbarHeight): 0; + switch (progressBarPosition) { + case TOP_OF_TOOLBAR: + params.setMargins(0, 0, 0, 0); + break; + case BOTTOM_OF_TOOLBAR: + params.setMargins(0, (int) toolbarHeight - (int) progressBarHeight, 0, 0); + break; + case TOP_OF_WEBVIEW: + params.setMargins(0, (int) toolbarHeight, 0, 0); + break; + case BOTTOM_OF_WEBVIEW: + params.setMargins(0, DisplayUtil.getHeight() - (int) progressBarHeight, 0, 0); + break; + } + progressBar.setLayoutParams(params); + } + + { // WebLayout + float toolbarHeight = toolbarVisible? getResources().getDimension(R.dimen.toolbarHeight): 0; + int statusBarHeight = toolbarVisible? DisplayUtil.getStatusBarHeight(): 0; + int screenHeight = DisplayUtil.getHeight(); + float webLayoutMinimumHeight = screenHeight - toolbarHeight - statusBarHeight; + if (showDivider && !gradientDivider) webLayoutMinimumHeight -= dividerHeight; + webLayout.setMinimumHeight((int) webLayoutMinimumHeight); + + CoordinatorLayout.LayoutParams params = + new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + params.setMargins(0, (int) toolbarHeight, 0, 0); + webLayout.setLayoutParams(params); + } + } + + @SuppressLint("SetJavaScriptEnabled") + protected void initializeViews() { + if (! toolbarVisible) { + setSupportActionBar(toolbar); + toolbar.setVisibility(View.GONE); + } + + { // StatusBar + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + Window window = getWindow(); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.setStatusBarColor(statusBarColor); + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Window window = getWindow(); + if (statusBarIconDark) { + window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR); + } else { + window.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE); + } + } + } + + { // Toolbar + toolbar.setBackgroundColor(toolbarColor); + } + + { // TextViews + title.setText(titleDefault); + title.setTextSize(TypedValue.COMPLEX_UNIT_PX, titleSize); + title.setTypeface(TypefaceHelper.get(this, titleFont)); + title.setTextColor(titleColor); + + urlTv.setVisibility(showUrl ? View.VISIBLE : View.GONE); + urlTv.setText(UrlParser.getHost(url)); + urlTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, urlSize); + urlTv.setTypeface(TypefaceHelper.get(this, urlFont)); + urlTv.setTextColor(urlColor); + + requestCenterLayout(); + } + + { // Icons + close.setBackgroundResource(iconSelector); + back.setBackgroundResource(iconSelector); + forward.setBackgroundResource(iconSelector); + more.setBackgroundResource(iconSelector); + + close.setVisibility(showIconClose ? View.VISIBLE : View.GONE); + close.setEnabled(!disableIconClose); + + if ((showMenuRefresh + || showMenuFind + || showMenuShareVia + || showMenuCopyLink + || showMenuOpenWith + || customMenus.size() > 0) && showIconMenu) { + more.setVisibility(View.VISIBLE); + } else { + more.setVisibility(View.GONE); + } + more.setEnabled(!disableIconMenu); + } + + { // Cookie + if (webViewCookieEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + CookieManager.getInstance().setAcceptThirdPartyCookies(webView, true); + } + } + + { // WebView + webChromeClient = buildWebChromeClient(); + webViewClient = buildWebViewClient(); + + webView.setWebChromeClient(webChromeClient); + webView.setWebViewClient(webViewClient); + webView.setDownloadListener(downloadListener); + + webView.setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + final WebView.HitTestResult hitTestResult = webView.getHitTestResult(); + // 如果是图片类型或者是带有图片链接的类型 + if (hitTestResult.getType() == WebView.HitTestResult.IMAGE_TYPE || + hitTestResult.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { + if (!showMenuSavePhoto) { + return false; + } + // 弹出保存图片的对话框 + AlertDialog.Builder builder = new AlertDialog.Builder(AwesomeWebViewActivity.this); + final String items[] = {getResources().getString(stringResSavePhoto)}; + builder.setItems(items, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + PermissionHelper.CheckPermissions(AwesomeWebViewActivity.this, new PermissionHelper.CheckPermissionListener() { + @Override + public void onAllGranted(boolean sync) { + String url = hitTestResult.getExtra(); + // 下载图片到本地 + CookieSyncManager.createInstance(AwesomeWebViewActivity.this); + CookieSyncManager.getInstance().sync(); + CookieManager cookieManager = CookieManager.getInstance(); + String cookie = cookieManager.getCookie(webView.getUrl()); + DownPicUtil.downPic(url, webView.getSettings().getUserAgentString(), webView.getUrl(), cookie, new DownPicUtil.DownFinishListener() { + + @Override + public void onDownFinish(String path) { + if (showToastPhotoSavedOrFailed) { + Toast.makeText(AwesomeWebViewActivity.this, getResources().getString(stringResPhotoSavedTo) + path, Toast.LENGTH_LONG).show(); + } + // 最后通知图库更新 + getApplicationContext().sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.parse("file://" + path))); + } + + @Override + public void onError() { + if (showToastPhotoSavedOrFailed) { + Toast.makeText(AwesomeWebViewActivity.this, getResources().getString(stringResPhotoSaveFailed), Toast.LENGTH_LONG).show(); + } + } + }); + } + + @Override + public void onPartlyGranted(List permissionsDenied, boolean sync) { + + } + }, Manifest.permission.WRITE_EXTERNAL_STORAGE); + } + }); + AlertDialog dialog = builder.create(); + dialog.show(); + return true; + } + return false; + } + }); + + webView.setOnTouchListener(new View.OnTouchListener() { + private float xDown, yDown; + private long timeDown; + @Override + public boolean onTouch(View v, MotionEvent event) { + if (v == webView && event.getAction() == MotionEvent.ACTION_DOWN) { + xDown = event.getX(); + yDown = event.getY(); + timeDown = System.currentTimeMillis(); + } else if (v == webView && event.getAction() == MotionEvent.ACTION_UP){ + if (Math.abs(xDown - event.getX()) < 50 && Math.abs(yDown - event.getY()) < 50 && System.currentTimeMillis() - timeDown < 200) { + // https://stackoverflow.com/a/5125620 + handler.sendEmptyMessageDelayed(MSG_CLICK_ON_WEBVIEW, 500); + } + } + return false; + } + }); + + WebSettings settings = webView.getSettings(); + + if (webViewSupportZoom != null) settings.setSupportZoom(webViewSupportZoom); + if (webViewMediaPlaybackRequiresUserGesture != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + settings.setMediaPlaybackRequiresUserGesture(webViewMediaPlaybackRequiresUserGesture); + } + if (webViewBuiltInZoomControls != null) { + settings.setBuiltInZoomControls(webViewBuiltInZoomControls); + + if (webViewBuiltInZoomControls) { + // Remove NestedScrollView to enable BuiltInZoomControls + ((ViewGroup) webView.getParent()).removeAllViews(); + } + } + if (webViewDisplayZoomControls != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + settings.setDisplayZoomControls(webViewDisplayZoomControls); + } + + if (webViewAllowFileAccess != null) settings.setAllowFileAccess(webViewAllowFileAccess); + if (webViewAllowContentAccess != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + settings.setAllowContentAccess(webViewAllowContentAccess); + } + if (webViewLoadWithOverviewMode != null) { + settings.setLoadWithOverviewMode(webViewLoadWithOverviewMode); + } + if (webViewSaveFormData != null) settings.setSaveFormData(webViewSaveFormData); + if (webViewTextZoom != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + settings.setTextZoom(webViewTextZoom); + } + if (webViewUseWideViewPort != null) settings.setUseWideViewPort(webViewUseWideViewPort); + if (webViewSupportMultipleWindows != null) { + settings.setSupportMultipleWindows(webViewSupportMultipleWindows); + } + if (webViewLayoutAlgorithm != null) settings.setLayoutAlgorithm(webViewLayoutAlgorithm); + if (webViewStandardFontFamily != null) { + settings.setStandardFontFamily(webViewStandardFontFamily); + } + if (webViewFixedFontFamily != null) settings.setFixedFontFamily(webViewFixedFontFamily); + if (webViewSansSerifFontFamily != null) { + settings.setSansSerifFontFamily(webViewSansSerifFontFamily); + } + if (webViewSerifFontFamily != null) settings.setSerifFontFamily(webViewSerifFontFamily); + if (webViewCursiveFontFamily != null) + settings.setCursiveFontFamily(webViewCursiveFontFamily); + if (webViewFantasyFontFamily != null) + settings.setFantasyFontFamily(webViewFantasyFontFamily); + if (webViewMinimumFontSize != null) settings.setMinimumFontSize(webViewMinimumFontSize); + if (webViewMinimumLogicalFontSize != null) { + settings.setMinimumLogicalFontSize(webViewMinimumLogicalFontSize); + } + if (webViewDefaultFontSize != null) settings.setDefaultFontSize(webViewDefaultFontSize); + if (webViewDefaultFixedFontSize != null) { + settings.setDefaultFixedFontSize(webViewDefaultFixedFontSize); + } + if (webViewLoadsImagesAutomatically != null) { + settings.setLoadsImagesAutomatically(webViewLoadsImagesAutomatically); + } + if (webViewBlockNetworkImage != null) + settings.setBlockNetworkImage(webViewBlockNetworkImage); + if (webViewBlockNetworkLoads != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { + settings.setBlockNetworkLoads(webViewBlockNetworkLoads); + } + if (webViewJavaScriptEnabled != null) + settings.setJavaScriptEnabled(webViewJavaScriptEnabled); + if (webViewAllowUniversalAccessFromFileURLs != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + settings.setAllowUniversalAccessFromFileURLs(webViewAllowUniversalAccessFromFileURLs); + } + if (webViewAllowFileAccessFromFileURLs != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + settings.setAllowFileAccessFromFileURLs(webViewAllowFileAccessFromFileURLs); + } + if (webViewGeolocationDatabasePath != null) { + settings.setGeolocationDatabasePath(webViewGeolocationDatabasePath); + } + if (webViewAppCacheEnabled != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (webViewAppCacheEnabled) { + settings.setCacheMode(WebSettings.LOAD_DEFAULT); + } else { + settings.setCacheMode(WebSettings.LOAD_NO_CACHE); + } + } + if (webViewDatabaseEnabled != null) settings.setDatabaseEnabled(webViewDatabaseEnabled); + if (webViewDomStorageEnabled != null) + settings.setDomStorageEnabled(webViewDomStorageEnabled); + if (webViewGeolocationEnabled != null) { + settings.setGeolocationEnabled(webViewGeolocationEnabled); + } + if (webViewJavaScriptCanOpenWindowsAutomatically != null) { + settings.setJavaScriptCanOpenWindowsAutomatically( + webViewJavaScriptCanOpenWindowsAutomatically); + } + if (webViewDefaultTextEncodingName != null) { + settings.setDefaultTextEncodingName(webViewDefaultTextEncodingName); + } + if (webViewUserAgentString != null) { + settings.setUserAgentString(webViewUserAgentAppend? settings.getUserAgentString() + " " + webViewUserAgentString: webViewUserAgentString); + } + if (webViewNeedInitialFocus != null) + settings.setNeedInitialFocus(webViewNeedInitialFocus); + if (webViewCacheMode != null) settings.setCacheMode(webViewCacheMode); + if (webViewMixedContentMode != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + settings.setMixedContentMode(webViewMixedContentMode); + } + if (webViewOffscreenPreRaster != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + settings.setOffscreenPreRaster(webViewOffscreenPreRaster); + } + + // // Other webview options + // webView.setScrollBarStyle(WebView.SCROLLBARS_OUTSIDE_OVERLAY); + // webView.setScrollbarFadingEnabled(false); + // //Additional Webview Properties + // webView.setSoundEffectsEnabled(true); + // webView.setHorizontalFadingEdgeEnabled(false); + // webView.setKeepScreenOn(true); + // webView.setScrollbarFadingEnabled(true); + // webView.setVerticalFadingEdgeEnabled(false); + } + + { // Divider + gradient.setVisibility(showDivider && gradientDivider ? View.VISIBLE : View.GONE); + divider.setVisibility(showDivider && !gradientDivider ? View.VISIBLE : View.GONE); + if (gradientDivider) { + int dividerWidth = DisplayUtil.getWidth(); + Bitmap bitmap = + BitmapHelper.getGradientBitmap(dividerWidth, (int) dividerHeight, dividerColor); + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + ViewUtil.setBackground(gradient, drawable); + + CoordinatorLayout.LayoutParams params = + (CoordinatorLayout.LayoutParams) gradient.getLayoutParams(); + params.height = (int) dividerHeight; + gradient.setLayoutParams(params); + } else { + divider.setBackgroundColor(dividerColor); + + LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) divider.getLayoutParams(); + params.height = (int) dividerHeight; + divider.setLayoutParams(params); + } + } + + { // ProgressBar + progressBar.setVisibility(showProgressBar ? View.VISIBLE : View.GONE); + progressBar.getProgressDrawable().setColorFilter(progressBarColor, PorterDuff.Mode.SRC_IN); + progressBar.setMinimumHeight((int) progressBarHeight); + CoordinatorLayout.LayoutParams params = + new CoordinatorLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + (int) progressBarHeight); + float toolbarHeight = toolbarVisible? getResources().getDimension(R.dimen.toolbarHeight): 0; + switch (progressBarPosition) { + case TOP_OF_TOOLBAR: + params.setMargins(0, 0, 0, 0); + break; + case BOTTOM_OF_TOOLBAR: + params.setMargins(0, (int) toolbarHeight - (int) progressBarHeight, 0, 0); + break; + case TOP_OF_WEBVIEW: + params.setMargins(0, (int) toolbarHeight, 0, 0); + break; + case BOTTOM_OF_WEBVIEW: + params.setMargins(0, DisplayUtil.getHeight() - (int) progressBarHeight, 0, 0); + break; + } + progressBar.setLayoutParams(params); + } + + { // Menu + GradientDrawable drawable = new GradientDrawable(); + drawable.setCornerRadius(getResources().getDimension(R.dimen.defaultMenuCornerRadius)); + drawable.setColor(menuColor); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + menuBackground.setBackground(drawable); + } else { + menuBackground.setBackgroundDrawable(drawable); + } + + shadowLayout.setShadowColor(menuDropShadowColor); + shadowLayout.setShadowSize(menuDropShadowSize); + + RelativeLayout.LayoutParams params = + new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + int margin = + (int) (getResources().getDimension(R.dimen.defaultMenuLayoutMargin) - menuDropShadowSize); + params.setMargins(0, margin, margin, 0); + params.addRule(RelativeLayout.ALIGN_PARENT_TOP); + params.addRule(rtl ? RelativeLayout.ALIGN_PARENT_LEFT : RelativeLayout.ALIGN_PARENT_RIGHT); + shadowLayout.setLayoutParams(params); + + menuRefresh.setVisibility(showMenuRefresh ? View.VISIBLE : View.GONE); + menuRefresh.setBackgroundResource(menuSelector); + menuRefresh.setGravity(menuTextGravity); + menuRefreshTv.setText(stringResRefresh); + menuRefreshTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize); + menuRefreshTv.setTypeface(TypefaceHelper.get(this, menuTextFont)); + menuRefreshTv.setTextColor(menuTextColor); + menuRefreshTv.setPadding((int) menuTextPaddingLeft, 0, (int) menuTextPaddingRight, 0); + + menuFind.setVisibility(showMenuFind ? View.VISIBLE : View.GONE); + menuFind.setBackgroundResource(menuSelector); + menuFind.setGravity(menuTextGravity); + menuFindTv.setText(stringResFind); + menuFindTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize); + menuFindTv.setTypeface(TypefaceHelper.get(this, menuTextFont)); + menuFindTv.setTextColor(menuTextColor); + menuFindTv.setPadding((int) menuTextPaddingLeft, 0, (int) menuTextPaddingRight, 0); + + menuShareVia.setVisibility(showMenuShareVia ? View.VISIBLE : View.GONE); + menuShareVia.setBackgroundResource(menuSelector); + menuShareVia.setGravity(menuTextGravity); + menuShareViaTv.setText(stringResShareVia); + menuShareViaTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize); + menuShareViaTv.setTypeface(TypefaceHelper.get(this, menuTextFont)); + menuShareViaTv.setTextColor(menuTextColor); + menuShareViaTv.setPadding((int) menuTextPaddingLeft, 0, (int) menuTextPaddingRight, 0); + + menuCopyLink.setVisibility(showMenuCopyLink ? View.VISIBLE : View.GONE); + menuCopyLink.setBackgroundResource(menuSelector); + menuCopyLink.setGravity(menuTextGravity); + menuCopyLinkTv.setText(stringResCopyLink); + menuCopyLinkTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize); + menuCopyLinkTv.setTypeface(TypefaceHelper.get(this, menuTextFont)); + menuCopyLinkTv.setTextColor(menuTextColor); + menuCopyLinkTv.setPadding((int) menuTextPaddingLeft, 0, (int) menuTextPaddingRight, 0); + + menuOpenWith.setVisibility(showMenuOpenWith ? View.VISIBLE : View.GONE); + menuOpenWith.setBackgroundResource(menuSelector); + menuOpenWith.setGravity(menuTextGravity); + menuOpenWithTv.setText(stringResOpenWith); + menuOpenWithTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize); + menuOpenWithTv.setTypeface(TypefaceHelper.get(this, menuTextFont)); + menuOpenWithTv.setTextColor(menuTextColor); + menuOpenWithTv.setPadding((int) menuTextPaddingLeft, 0, (int) menuTextPaddingRight, 0); + + for (final CustomMenu customMenu: customMenus) { + View customMenuRoot = LayoutInflater.from(AwesomeWebViewActivity.this).inflate(R.layout.view_custom_menu, null, false); + LinearLayout customMenuLayout = customMenuRoot.findViewById(R.id.customMenuLayout); + TextView customMenuTv = customMenuLayout.findViewById(R.id.customMenu); + customMenuLayout.setBackgroundResource(menuSelector); + customMenuLayout.setGravity(menuTextGravity); + customMenuTv.setText(customMenu.getTitleRes()); + customMenuTv.setTextSize(TypedValue.COMPLEX_UNIT_PX, menuTextSize); + customMenuTv.setTypeface(TypefaceHelper.get(this, menuTextFont)); + customMenuTv.setTextColor(menuTextColor); + customMenuTv.setPadding((int) menuTextPaddingLeft, 0, (int) menuTextPaddingRight, 0); + + customMenuLayout.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + BroadCastManager.onCustomMenuClick(AwesomeWebViewActivity.this, key, customMenu.getCode()); + hideMenu(); + } + }); + + menuBackground.addView(customMenuRoot, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); + } + } + } + + protected WebView buildWebView() { + return new VideoEnabledWebView(this); + } + + protected WebChromeClient buildWebChromeClient() { + // Initialize the VideoEnabledWebChromeClient and set event handlers + View nonVideoLayout = webLayout; // Your own view, read class comments + ViewGroup videoLayout = (ViewGroup) findViewById(R.id.videoLayout); // Your own view, read class comments + //noinspection all + View loadingView = getLayoutInflater().inflate(R.layout.view_loading_video, null); // Your own view, read class comments + MyWebChromeClient webChromeClient = new MyWebChromeClient(nonVideoLayout, videoLayout, loadingView, webView); + webChromeClient.setOnToggledFullscreen(new VideoEnabledWebChromeClient.ToggledFullscreenCallback() { + @Override + public void toggledFullscreen(boolean fullscreen) { + // Your code to handle the full-screen change, for example showing and hiding the title bar. Example: + if (fullscreen) { + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.flags |= WindowManager.LayoutParams.FLAG_FULLSCREEN; + attrs.flags |= WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + getWindow().setAttributes(attrs); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + //noinspection all + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE); + } + } else { + WindowManager.LayoutParams attrs = getWindow().getAttributes(); + attrs.flags &= ~WindowManager.LayoutParams.FLAG_FULLSCREEN; + attrs.flags &= ~WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; + getWindow().setAttributes(attrs); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + //noinspection all + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE); + } + } + + } + }); + return webChromeClient; + } + + protected WebViewClient buildWebViewClient() { + return new MyWebViewClient(); + } + + protected void injectCookie() { + if (injectCookies != null && injectCookies.size() > 0) { + // https://blog.csdn.net/juhua2012/article/details/52249720 + CookieSyncManager.createInstance(this); + CookieSyncManager.getInstance().sync(); + + CookieManager cookieManager = CookieManager.getInstance(); + cookieManager.setAcceptCookie(true); + for (String url: injectCookies.keySet()) { + Map cookie = injectCookies.get(url); + if (cookie == null || cookie.size() == 0) { + continue; + } + for (String key: cookie.keySet()) { + String value = cookie.get(key); + String cookieStr = key + "=" + value; + cookieManager.setCookie(url, cookieStr); + } + } + CookieSyncManager.getInstance().sync(); + } + } + + protected void addJavascriptInterface() { + CommonJsHelper.getInstance().addJavascriptInterface(webView); + } + + protected void load() { + if (data != null) { + webView.loadData(data, mimeType, encoding); + } else if (url != null) { + if (extraHeaders == null) { + webView.loadUrl(url); + } else { + webView.loadUrl(url, extraHeaders); + } + } + } + + protected int getMaxWidth() { + if (forward.getVisibility() == View.VISIBLE) { + return DisplayUtil.getWidth() - UnitConverter.dpToPx(100); + } else { + return DisplayUtil.getWidth() - UnitConverter.dpToPx(52); + } + } + + protected void updateIcon(ImageButton icon, @DrawableRes int drawableRes) { + StateListDrawable states = new StateListDrawable(); + { + Bitmap bitmap = BitmapHelper.getColoredBitmap(this, drawableRes, iconPressedColor); + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + states.addState(new int[]{android.R.attr.state_pressed}, drawable); + } + { + Bitmap bitmap = BitmapHelper.getColoredBitmap(this, drawableRes, iconDisabledColor); + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + states.addState(new int[]{-android.R.attr.state_enabled}, drawable); + } + { + Bitmap bitmap = BitmapHelper.getColoredBitmap(this, drawableRes, iconDefaultColor); + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + states.addState(new int[]{}, drawable); + } + icon.setImageDrawable(states); + + // int[][] states = new int[][]{ + // new int[]{-android.R.attr.state_enabled}, // disabled + // new int[]{android.R.attr.state_pressed}, // pressed + // new int[]{} // default + // }; + // + // int[] colors = new int[]{ + // iconDisabledColor, + // iconPressedColor, + // iconDefaultColor + // }; + // + // ColorStateList colorStateList = new ColorStateList(states, colors); + // + // Drawable drawable = ContextCompat.getDrawable(this, drawableRes); + // if (APILevel.require(21)) { + // VectorDrawable vectorDrawable = (VectorDrawable) drawable; + // vectorDrawable.setTintList(colorStateList); + // icon.setImageDrawable(vectorDrawable); + // } else { + // VectorDrawableCompat vectorDrawable = (VectorDrawableCompat) drawable; + // vectorDrawable.setTintList(colorStateList); + // icon.setImageDrawable(vectorDrawable); + // } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + initializeOptions(); + + setContentView(R.layout.awesome_web_view); + bindViews(); + layoutViews(); + initializeViews(); + injectCookie(); + addJavascriptInterface(); + load(); + } + + @Override + public void onBackPressed() { + if (webChromeClient instanceof MyWebChromeClient && ((MyWebChromeClient)webChromeClient).onBackPressed()) { + return; + } + if (menuLayout.getVisibility() == View.VISIBLE) { + hideMenu(); + } else if (backPressToClose || !webView.canGoBack()) { + exitActivity(); + } else { + webView.goBack(); + } + } + + @Override + public void onClick(View v) { + int viewId = v.getId(); + if (viewId == R.id.close) { + if (rtl) { + showMenu(); + } else { + exitActivity(); + } + } else if (viewId == R.id.back) { + if (rtl) { + webView.goForward(); + } else { + webView.goBack(); + } + } else if (viewId == R.id.forward) { + if (rtl) { + webView.goBack(); + } else { + webView.goForward(); + } + } else if (viewId == R.id.more) { + if (rtl) { + exitActivity(); + } else { + showMenu(); + } + } else if (viewId == R.id.menuLayout) { + hideMenu(); + } else if (viewId == R.id.menuRefresh) { + webView.reload(); + hideMenu(); + } else if (viewId == R.id.menuFind) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) + webView.showFindDialog("", true); + hideMenu(); + } else if (viewId == R.id.menuShareVia) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, webView.getUrl()); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, getResources().getString(stringResShareVia))); + + hideMenu(); + } else if (viewId == R.id.menuCopyLink) { + ClipboardManagerUtil.setText(webView.getUrl()); + + Snackbar snackbar = Snackbar.make(coordinatorLayout, getString(stringResCopiedToClipboard), + Snackbar.LENGTH_LONG); + View snackbarView = snackbar.getView(); + snackbarView.setBackgroundColor(toolbarColor); + if (snackbarView instanceof ViewGroup) updateChildTextView((ViewGroup) snackbarView); + snackbar.show(); + + hideMenu(); + } else if (viewId == R.id.menuOpenWith) { + Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(webView.getUrl())); + startActivity(browserIntent); + + hideMenu(); + } + } + + protected void updateChildTextView(ViewGroup viewGroup) { + if (viewGroup == null || viewGroup.getChildCount() == 0) return; + + for (int i = 0; i < viewGroup.getChildCount(); i++) { + View view = viewGroup.getChildAt(i); + if (view instanceof TextView) { + TextView textView = (TextView) view; + textView.setTextColor(titleColor); + textView.setTypeface(TypefaceHelper.get(this, titleFont)); + textView.setLineSpacing(0, 1.1f); + textView.setIncludeFontPadding(false); + } + + if (view instanceof ViewGroup) updateChildTextView((ViewGroup) view); + } + } + + protected void showMenu() { + menuLayout.setVisibility(View.VISIBLE); + Animation popupAnim = AnimationUtils.loadAnimation(this, R.anim.popup_flyout_show); + shadowLayout.startAnimation(popupAnim); + } + + protected void hideMenu() { + Animation popupAnim = AnimationUtils.loadAnimation(this, R.anim.popup_flyout_hide); + shadowLayout.startAnimation(popupAnim); + popupAnim.setAnimationListener(new Animation.AnimationListener() { + @Override + public void onAnimationStart(Animation animation) { + } + + @Override + public void onAnimationEnd(Animation animation) { + menuLayout.setVisibility(View.GONE); + } + + @Override + public void onAnimationRepeat(Animation animation) { + } + }); + } + + protected void exitActivity() { + super.onBackPressed(); + overridePendingTransition(animationCloseEnter, animationCloseExit); + } + + protected void requestCenterLayout() { + int maxWidth; + if (webView.canGoBack() || webView.canGoForward()) { + maxWidth = DisplayUtil.getWidth() - UnitConverter.dpToPx(48) * 4; + } else { + maxWidth = DisplayUtil.getWidth() - UnitConverter.dpToPx(48) * 2; + } + + title.setMaxWidth(maxWidth); + urlTv.setMaxWidth(maxWidth); + title.requestLayout(); + urlTv.requestLayout(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + layoutViews(); + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { + layoutViews(); + } + } + + @Override + protected void onDestroy() { + super.onDestroy(); + BroadCastManager.unregister(AwesomeWebViewActivity.this, key); + if (webView == null) return; + if (APILevel.require(11)) webView.onPause(); + destroyWebView(); + } + + // Wait for zoom control to fade away + // https://code.google.com/p/android/issues/detail?id=15694 + // http://stackoverflow.com/a/5966151/1797648 + protected void destroyWebView() { + new Handler().postDelayed(new Runnable() { + @Override + public void run() { + if (webView != null) webView.destroy(); + } + }, ViewConfiguration.getZoomControlsTimeout() + 1000L); + } + + @Override + public boolean handleMessage(Message msg) { + if (msg.what == MSG_CLICK_ON_URL){ + handler.removeMessages(MSG_CLICK_ON_WEBVIEW); + return true; + } + if (msg.what == MSG_CLICK_ON_WEBVIEW){ + final WebView.HitTestResult hitTestResult = webView.getHitTestResult(); + // 如果是图片类型或者是带有图片链接的类型 + if (hitTestResult.getType() == WebView.HitTestResult.IMAGE_TYPE || + hitTestResult.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) { + BroadCastManager.onClickImage(AwesomeWebViewActivity.this, key, hitTestResult.getExtra()); + } + return true; + } + return false; + } + + public class MyWebChromeClient extends VideoEnabledWebChromeClient { + + public MyWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView, WebView webView) { + super(activityNonVideoView, activityVideoView, loadingView, webView); + } + + @Override + public void onProgressChanged(WebView view, int progress) { + BroadCastManager.onProgressChanged(AwesomeWebViewActivity.this, key, progress); + + if (progress == 100) progress = 0; + progressBar.setProgress(progress); + } + + @Override + public void onReceivedTitle(WebView view, String title) { + BroadCastManager.onReceivedTitle(AwesomeWebViewActivity.this, key, title); + } + + @Override + public void onReceivedTouchIconUrl(WebView view, String url, boolean precomposed) { + BroadCastManager.onReceivedTouchIconUrl(AwesomeWebViewActivity.this, key, url, precomposed); + } + + @Override + public void onGeolocationPermissionsShowPrompt(final String origin, final GeolocationPermissions.Callback callback) { + PermissionHelper.CheckPermissions(AwesomeWebViewActivity.this, new PermissionHelper.CheckPermissionListener() { + @Override + public void onAllGranted(boolean sync) { + callback.invoke(origin, true, true); + } + + @Override + public void onPartlyGranted(List permissionsDenied, boolean sync) { + callback.invoke(origin, false, false); + } + }, Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION); + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + @Override + public void onPermissionRequest(final PermissionRequest request) { + for (String res : request.getResources()) { + if (res.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) { + if (!webViewAudioEnabled) { + request.deny(); + return; + } + } + if (res.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) { + if (!webViewCameraEnabled) { + request.deny(); + return; + } + } + } + + PermissionHelper.CheckPermissions(AwesomeWebViewActivity.this, new PermissionHelper.CheckPermissionListener() { + @Override + public void onAllGranted(boolean sync) { + request.grant(request.getResources()); + } + + @Override + public void onPartlyGranted(List permissionsDenied, boolean sync) { + request.deny(); + } + }, parsePermission(request.getResources())); + } + + //Handling input[type="file"] requests for android API 16+ + public void openFileChooser(ValueCallback uploadMsg, String acceptType, String capture) { + handler.sendEmptyMessage(MSG_CLICK_ON_URL); + + if (!fileChooserEnabled) { + uploadMsg.onReceiveValue(null); + return; + } + filePickerFileMessage = uploadMsg; + Intent i = new Intent(Intent.ACTION_GET_CONTENT); + i.addCategory(Intent.CATEGORY_OPENABLE); + i.setType(acceptType); + startActivityForResult(Intent.createChooser(i, getResources().getString(stringResFileChooserTitle)), FILE_PICKER_REQ_CODE); + } + + //Handling input[type="file"] requests for android API 21+ + @Override + public boolean onShowFileChooser(WebView webView, ValueCallback filePathCallback, final FileChooserParams fileChooserParams) { + handler.sendEmptyMessage(MSG_CLICK_ON_URL); + + if (!fileChooserEnabled) { + filePathCallback.onReceiveValue(null); + return true; + } + if (filePickerFilePath != null) { + filePickerFilePath.onReceiveValue(null); + } + filePickerFilePath = filePathCallback; + + //Checking permission for storage and camera for writing and uploading images + String[] perms = {Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA}; + PermissionHelper.CheckPermissions(AwesomeWebViewActivity.this, new PermissionHelper.CheckPermissionListener() { + @Override + public void onAllGranted(boolean sync) { + final Intent takePictureIntent = createCameraCaptureIntent(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP? fileChooserParams.getAcceptTypes(): null); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && fileChooserParams.isCaptureEnabled() && fileChooserParams.getMode() == FileChooserParams.MODE_OPEN) { + // capture="camera" and without multiple + if (takePictureIntent != null) { + startActivityForResult(takePictureIntent, FILE_PICKER_REQ_CODE); + } else { + if (filePickerFilePath != null) { + filePickerFilePath.onReceiveValue(null); + filePickerFilePath = null; + } + } + return; + } + final Intent contentSelectionIntent = new Intent(Intent.ACTION_GET_CONTENT); + contentSelectionIntent.addCategory(Intent.CATEGORY_OPENABLE); + contentSelectionIntent.setType(FILE_TYPE); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + contentSelectionIntent.putExtra(Intent.EXTRA_MIME_TYPES, fileChooserParams.getAcceptTypes()); + } + Intent[] intentArray; + if (takePictureIntent != null) { + intentArray = new Intent[]{takePictureIntent}; + } else { + intentArray = new Intent[0]; + } + Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER); + chooserIntent.putExtra(Intent.EXTRA_INTENT, contentSelectionIntent); + chooserIntent.putExtra(Intent.EXTRA_TITLE, getResources().getString(stringResFileChooserTitle)); + chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray); + startActivityForResult(chooserIntent, FILE_PICKER_REQ_CODE); + } + + @Override + public void onPartlyGranted(List permissionsDenied, boolean sync) { + if (filePickerFilePath != null) { + filePickerFilePath.onReceiveValue(null); + filePickerFilePath = null; + } + } + }, perms); + return true; + } + } + + private Intent createCameraCaptureIntent(String[] mimeTypes) { + boolean isVideo = false; + if (mimeTypes != null && mimeTypes.length == 1 && mimeTypes[0] != null && mimeTypes[0].startsWith("video")) { + isVideo = true; + } + Intent takePictureIntent = new Intent(isVideo? MediaStore.ACTION_VIDEO_CAPTURE: MediaStore.ACTION_IMAGE_CAPTURE); + if (takePictureIntent.resolveActivity(AwesomeWebViewActivity.this.getPackageManager()) != null) { + File imageVideoFile = null; + try { + imageVideoFile = createImageOrVideo(isVideo); + } catch (IOException ex) { +// Log.e("", "Image file creation failed", ex); + ex.printStackTrace(); + } + if (imageVideoFile != null) { + filePickerCamMessage = "file:" + imageVideoFile.getAbsolutePath(); + + Uri photoUri = FileProvider.getUriForFile( + this, + getPackageName() + ".awesome_web_view.file_provider", + imageVideoFile); + + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); + } else { + takePictureIntent = null; + } + } + return takePictureIntent; + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { +// getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); +// getWindow().setStatusBarColor(getResources().getColor(R.color.colorPrimary)); + Uri[] results = null; + if (resultCode == Activity.RESULT_OK) { + if (requestCode == FILE_PICKER_REQ_CODE) { + if (null == filePickerFilePath) { + return; + } + if (intent == null || intent.getDataString() == null) { + if (filePickerCamMessage != null) { + results = new Uri[]{Uri.parse(filePickerCamMessage)}; + } + } else { + String dataString = intent.getDataString(); + if (dataString != null) { + results = new Uri[]{Uri.parse(dataString)}; + } + } + } + } + filePickerFilePath.onReceiveValue(results); + filePickerFilePath = null; + } else { + if (requestCode == FILE_PICKER_REQ_CODE) { + if (null == filePickerFileMessage) return; + Uri result = intent == null || resultCode != RESULT_OK ? null : intent.getData(); + filePickerFileMessage.onReceiveValue(result); + filePickerFileMessage = null; + } + } + } + + //Creating image or video file for upload + protected File createImageOrVideo(boolean isVideo) throws IOException { + @SuppressLint("SimpleDateFormat") + String file_name = new SimpleDateFormat("yyyy_mm_ss").format(new Date()); + String new_name = "file_" + file_name + "_"; + File sd_directory = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + return File.createTempFile(new_name, isVideo? ".mp4": ".jpg", sd_directory); + } + + public class MyWebViewClient extends WebViewClient { + + @Override + public void onPageStarted(WebView view, String url, Bitmap favicon) { + BroadCastManager.onPageStarted(AwesomeWebViewActivity.this, key, url); + if (!url.contains("docs.google.com") && url.endsWith(".pdf")) { + webView.loadUrl("http://docs.google.com/gview?embedded=true&url=" + url); + } + } + + @Override + public void onPageFinished(WebView view, String url) { + BroadCastManager.onPageFinished(AwesomeWebViewActivity.this, key, url); + + if (updateTitleFromHtml) title.setText(view.getTitle()); + urlTv.setText(UrlParser.getHost(url)); + requestCenterLayout(); + + if (view.canGoBack() || view.canGoForward()) { + back.setVisibility(showIconBack ? View.VISIBLE : View.GONE); + forward.setVisibility(showIconForward ? View.VISIBLE : View.GONE); + back.setEnabled(!disableIconBack && (rtl ? view.canGoForward() : view.canGoBack())); + forward.setEnabled(!disableIconForward && (rtl ? view.canGoBack() : view.canGoForward())); + } else { + back.setVisibility(View.GONE); + forward.setVisibility(View.GONE); + } + + if (injectJavaScript != null) { + if (injectJavaScriptMainPage && !url.equals(AwesomeWebViewActivity.this.url)) { + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + webView.evaluateJavascript(injectJavaScript, null); + } else { + webView.loadUrl(injectJavaScript); + } + } + } + + @Override + public boolean shouldOverrideUrlLoading(WebView view, String url) { + handler.sendEmptyMessage(MSG_CLICK_ON_URL); + + if (url.endsWith(".mp4")) { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setDataAndType(Uri.parse(url), "video/*"); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + view.getContext().startActivity(intent); + // If we return true, onPageStarted, onPageFinished won't be called. + return true; + } else if (url.startsWith("tel:") || url.startsWith("sms:") || url.startsWith("smsto:") || url + .startsWith("mms:") || url.startsWith("mmsto:")) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + view.getContext().startActivity(intent); + return true; // If we return true, onPageStarted, onPageFinished won't be called. + } + /******************************************************* + * Added in support for mailto: + *******************************************************/ + else if (url.startsWith("mailto:")) { + + MailTo mt = MailTo.parse(url); + + Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); + + emailIntent.setType("text/html"); + emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{mt.getTo()}); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, mt.getSubject()); + emailIntent.putExtra(Intent.EXTRA_CC, mt.getCc()); + emailIntent.putExtra(Intent.EXTRA_TEXT, mt.getBody()); + + startActivity(emailIntent); + + return true; + } else if (url.startsWith("http") || url.startsWith("https") || url.startsWith("ftp")) { + if (extraHeaders == null || extraHeadersMainPage && !url.equals(AwesomeWebViewActivity.this.url)) { + return super.shouldOverrideUrlLoading(view, url); + } else { + view.loadUrl(url, extraHeaders); + return true; + } + } else { + if (webViewAppJumpEnabled) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + view.getContext().startActivity(intent); + return true; // If we return true, onPageStarted, onPageFinished won't be called. + } catch (Exception exception) { + exception.printStackTrace(); + return true; + } + } else { + return super.shouldOverrideUrlLoading(view, url); + } + } + } + + @Override + public void onLoadResource(WebView view, String url) { + BroadCastManager.onLoadResource(AwesomeWebViewActivity.this, key, url); + } + + @Override + public void onPageCommitVisible(WebView view, String url) { + BroadCastManager.onPageCommitVisible(AwesomeWebViewActivity.this, key, url); + } + } + + protected String[] parsePermission(String[] resource) { + List permissions = new ArrayList<>(); + for (String res : resource) { + if (res.equals(PermissionRequest.RESOURCE_AUDIO_CAPTURE)) { + permissions.add(Manifest.permission.RECORD_AUDIO); + } + if (res.equals(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) { + permissions.add(Manifest.permission.CAMERA); + } + } + + String[] result = new String[permissions.size()]; + for (int i = 0; i < permissions.size(); i++) { + result[i] = permissions.get(i); + } + return result; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/enums/Position.java b/library/src/main/java/com/wuadam/awesomewebview/enums/Position.java new file mode 100644 index 00000000..62ad266c --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/enums/Position.java @@ -0,0 +1,11 @@ +package com.wuadam.awesomewebview.enums; + +/** + * Created by Leonardo on 11/14/15. + */ +public enum Position { + TOP_OF_TOOLBAR, + BOTTOM_OF_TOOLBAR, + TOP_OF_WEBVIEW, + BOTTOM_OF_WEBVIEW; +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/Base64ImgHelper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/Base64ImgHelper.java new file mode 100644 index 00000000..76af518b --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/Base64ImgHelper.java @@ -0,0 +1,25 @@ +package com.wuadam.awesomewebview.helpers; + +import android.text.TextUtils; +import android.util.Base64; + +public class Base64ImgHelper { + private String src; + + public Base64ImgHelper(String src) { + this.src = src; + } + + public boolean isBase64Img() { + if (!TextUtils.isEmpty(src) && src.startsWith("data:image")) { + return true; + } + return false; + } + + public byte[] decode() { + src = src.substring(src.indexOf(",")); + byte[] imageAsBytes = Base64.decode(src.getBytes(), 0); + return imageAsBytes; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/BitmapHelper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/BitmapHelper.java new file mode 100644 index 00000000..c1ad26c5 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/BitmapHelper.java @@ -0,0 +1,69 @@ +package com.wuadam.awesomewebview.helpers; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Color; +import androidx.annotation.ColorInt; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; + +/** + * Created by Leonardo on 11/21/15. + */ +public class BitmapHelper { + + private BitmapHelper() { + } + + public static Bitmap getColoredBitmap(@NonNull Bitmap bitmap, @ColorInt int color) { + int alpha = Color.alpha(color); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + + int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()]; + bitmap.getPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); + for (int i = 0; i < pixels.length; i++) { + int pixel = pixels[i]; + int pixelAlpha = Color.alpha(pixel); + if (pixelAlpha != 0) { + pixels[i] = Color.argb((int) (pixelAlpha * alpha / 256f), red, green, blue); + } + } + + Bitmap coloredBitmap = bitmap.copy(Bitmap.Config.ARGB_8888, true); + coloredBitmap.setPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), + bitmap.getHeight()); + return coloredBitmap; + } + + public static Bitmap getColoredBitmap(@NonNull Context context, @DrawableRes int drawableRes, + @ColorInt int color) { + Bitmap bitmap = BitmapFactory.decodeResource(context.getResources(), drawableRes); + return getColoredBitmap(bitmap, color); + } + + public static Bitmap getGradientBitmap(int width, int height, @ColorInt int color) { + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + int alpha = Color.alpha(color); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + + int[] pixels = new int[width * height]; + bitmap.getPixels(pixels, 0, width, 0, 0, width, height); + for (int y = 0; y < height; y++) { + int gradientAlpha = (int) ((float) alpha * (float) (height - y) * (float) (height - y) + / (float) height + / (float) height); + for (int x = 0; x < width; x++) { + pixels[x + y * width] = Color.argb(gradientAlpha, red, green, blue); + } + } + + bitmap.setPixels(pixels, 0, bitmap.getWidth(), 0, 0, bitmap.getWidth(), bitmap.getHeight()); + return bitmap; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/ColorHelper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/ColorHelper.java new file mode 100644 index 00000000..47331b36 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/ColorHelper.java @@ -0,0 +1,21 @@ +package com.wuadam.awesomewebview.helpers; + +import android.graphics.Color; + +/** + * Created by Leonardo on 11/28/15. + */ +public class ColorHelper { + + private ColorHelper() { + } + + public static int disableColor(int color) { + int alpha = Color.alpha(color); + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + + return Color.argb((int) (alpha * 0.2f), red, green, blue); + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/DownPicUtil.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/DownPicUtil.java new file mode 100644 index 00000000..b2cee876 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/DownPicUtil.java @@ -0,0 +1,248 @@ +package com.wuadam.awesomewebview.helpers; + +/** + * Created by wuzongheng on 2018/2/18. + */ + +import android.os.AsyncTask; +import android.os.Environment; +import android.text.TextUtils; + +import java.io.ByteArrayInputStream; +import java.io.File; +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; + +/** + * 图片下载的工具类 + */ +public class DownPicUtil { + + /** + * Download the pic + * @param url + */ + public static void downPic(String url, DownFinishListener downFinishListener){ + downPic(url, null, null, null, downFinishListener); + } + + /** + * Download the pic + * @param url + */ + public static void downPic(String url, String userAgent, String referer, String cookie, DownFinishListener downFinishListener){ + // 获取存储卡的目录 + String filePath = Environment.getExternalStorageDirectory().getPath(); + File file = new File(filePath + File.separator + Environment.DIRECTORY_DOWNLOADS); + if(!file.exists()){ + file.mkdirs(); + } + + loadPic(file.getPath(), url, userAgent, referer, cookie, downFinishListener); + + } + + private static void loadPic(final String filePath, final String url, final String userAgent, final String referer, final String cookie, final DownFinishListener downFinishListener) { + new AsyncTask(){ + String fileName; + InputStream is; + OutputStream out; + + @Override + protected String doInBackground(Void... voids) { + + // 原文件名 + String[] split = url.split("/"); + fileName = split[split.length - 1]; + + // 创建目标文件,使用时间戳作为临时文件名,确保可以不重复 + String now = String.valueOf(System.currentTimeMillis()); + File picFile = new File(filePath + File.separator + now); + if(! picFile.exists()) { + // if file exists, do not download again + try { + Base64ImgHelper base64ImgHelper = new Base64ImgHelper(url); + if (base64ImgHelper.isBase64Img()) { + fileName = now; + byte[] image = base64ImgHelper.decode(); + is = new ByteArrayInputStream(image); //处理服务器的响应结果 + } else { + URL picUrl = new URL(url); + //通过图片的链接打开输入流 + HttpURLConnection httpURLConnection = (HttpURLConnection) picUrl.openConnection(); + httpURLConnection.setConnectTimeout(10000); //设置连接超时时间 + httpURLConnection.setReadTimeout(30000); + httpURLConnection.setDoInput(true); //打开输入流,以便从服务器获取数据 + httpURLConnection.setDoOutput(false); //Get请求不需要DoOutPut + httpURLConnection.setRequestMethod("GET"); //设置以Get方式请求数据 + httpURLConnection.setUseCaches(false); //不使用缓存 + //设置请求体的类型是文本类型 + httpURLConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + if (!TextUtils.isEmpty(userAgent)) { + httpURLConnection.setRequestProperty("User-Agent", userAgent); + } + if (!TextUtils.isEmpty(referer)) { + httpURLConnection.setRequestProperty("Referer", referer); + } + if (!TextUtils.isEmpty(cookie)) { + httpURLConnection.setRequestProperty("Cookie", cookie); + } + httpURLConnection.connect(); + + int response = httpURLConnection.getResponseCode(); //获得服务器的响应码 + if (response == HttpURLConnection.HTTP_OK || response == HttpURLConnection.HTTP_NOT_MODIFIED) { + is = httpURLConnection.getInputStream(); //处理服务器的响应结果 + if (is == null) { + return null; + } + } else { + return null; + } + } + out = new FileOutputStream(picFile); + byte[] b = new byte[1024]; + int end; + while ((end = is.read(b)) != -1) { + out.write(b, 0, end); + } + + if (is != null) { + is.close(); + } + + if (out != null) { + out.close(); + } + + } catch (FileNotFoundException e) { + e.printStackTrace(); + return null; + } catch (IOException e) { + e.printStackTrace(); + return null; + } + } + + // 提取文件格式真实拓展名 + String extension = FormatHelper.getExtension(picFile); + + // 提取不包含拓展名的原文件名 + String[] extensions = fileName.split("\\."); + int splitLength = extensions.length; + String newFileNameNoExtension; + if (splitLength > 1) { + newFileNameNoExtension = fileName.substring(0, fileName.length() - extensions[splitLength - 1].length() - 1); + } else { + newFileNameNoExtension = fileName; + } + + // 重命名文件 + if (extension == null) { + // 不支持解析的格式,使用原文件名、原拓展名 + if (splitLength > 1) { + // 有拓展名,在原文件名基础上递增重命名 + return renamePic(picFile, filePath, newFileNameNoExtension, extensions[splitLength - 1], MODE.MODE_INCREMENT); + } else { + // 无拓展名,整个文件名递增重命名 + return renamePic(picFile, filePath, newFileNameNoExtension, null, MODE.MODE_INCREMENT); + } + } + // 支持解析的格式,使用md5文件名、真实拓展名 + String md5 = Md5Helper.getFileMD5ToString(picFile); + if (TextUtils.isEmpty(md5)) { + return renamePic(picFile, filePath, newFileNameNoExtension, extension, MODE.MODE_INCREMENT); + } else { + return renamePic(picFile, filePath, md5.substring(0, 16), extension, MODE.MODE_IGNORE); + } + } + + @Override + protected void onPostExecute(String path) { + super.onPostExecute(path); + if(path!=null){ + downFinishListener.onDownFinish(path); + } else { + downFinishListener.onError(); + } + } + }.execute(); + } + + private enum MODE{ + /** + * 已有文件名一致的文件,文件名后加上地增量(n) + */ + MODE_INCREMENT, + /** + * 已有文件名一致的文件,不执行 + */ + MODE_IGNORE, + /** + * 已有文件名一致的文件,覆盖 + */ + MODE_OVERRIDE + } + + private static String renamePic(File picFile, String filePath, String newFileNameNoExtension, String extension, MODE mode) { + String extensionWithPoint = TextUtils.isEmpty(extension)? "": "." + extension; + String newFileName = newFileNameNoExtension + extensionWithPoint; + File newFile = new File(filePath + File.separator + newFileName); + switch (mode) { + + case MODE_INCREMENT: + int count = 1; + while (newFile.exists()) { + // if file of new name exists, add (count) in filename; + newFileName = newFileNameNoExtension + "(" + count + ")" + extensionWithPoint; + newFile = new File(filePath + File.separator + newFileName); + count ++; + } + break; + case MODE_IGNORE: + if (newFile.exists()) { + return newFile.getPath(); + } + break; + case MODE_OVERRIDE: + if (newFile.exists()) { + if (!newFile.delete()) { + return null; + } + } + break; + } + + if (rename(picFile, newFileName)) { + return newFile.getPath(); + } else { + return null; + } + } + + private 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 (TextUtils.isEmpty(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); + } + + + //下载完成回调的接口 + public interface DownFinishListener{ + void onDownFinish(String path); + void onError(); + } +} \ No newline at end of file diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/FileProvider4WebView.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/FileProvider4WebView.java new file mode 100644 index 00000000..adba2715 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/FileProvider4WebView.java @@ -0,0 +1,9 @@ +package com.wuadam.awesomewebview.helpers; + + +import androidx.annotation.Keep; +import androidx.core.content.FileProvider; + +@Keep +public final class FileProvider4WebView extends FileProvider { +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/FormatHelper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/FormatHelper.java new file mode 100644 index 00000000..b282ad3b --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/FormatHelper.java @@ -0,0 +1,94 @@ +package com.wuadam.awesomewebview.helpers; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +public class FormatHelper { + + private FormatHelper() { + } + + public static String getExtension(File file) { + try { +// long fileLength = file.length(); + FileInputStream fileInputStream = new FileInputStream(file); + BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream); + bufferedInputStream.mark(Integer.MAX_VALUE); + byte[] start; + byte[] end; + + // https://en.wikipedia.org/wiki/JPEG + start = new byte[2]; +// end = new byte[2]; + bufferedInputStream.read(start); +// bufferedInputStream.skip(fileLength - start.length - end.length); +// bufferedInputStream.read(end); + if (start[0] == (byte) 0xFF && + start[1] == (byte) 0xD8 +// && +// end[0] == (byte) 0xFF && +// end[1] == (byte) 0xD9 + ) { + return "jpg"; + } + bufferedInputStream.reset(); + + // https://en.wikipedia.org/wiki/Portable_Network_Graphics + start = new byte[8]; + bufferedInputStream.read(start); + if (start[0] == (byte) 0x89 && + start[1] == (byte) 0x50 && + start[2] == (byte) 0x4E && + start[3] == (byte) 0x47 && + start[4] == (byte) 0x0D && + start[5] == (byte) 0x0A && + start[6] == (byte) 0x1A && + start[7] == (byte) 0x0A) { + return "png"; + } + bufferedInputStream.reset(); + + // https://developers.google.com/speed/webp/docs/riff_container + // https://en.wikipedia.org/wiki/WebP + start = new byte[4]; + end = new byte[4]; + bufferedInputStream.read(start); + bufferedInputStream.skip(4); + bufferedInputStream.read(end); + if (start[0] == (byte) 0x52 && + start[1] == (byte) 0x49 && + start[2] == (byte) 0x46 && + start[3] == (byte) 0x46 && + end[0] == (byte) 0x57 && + end[1] == (byte) 0x45 && + end[2] == (byte) 0x42 && + end[3] == (byte) 0x50) { + return "webp"; + } + bufferedInputStream.reset(); + + // https://en.wikipedia.org/wiki/File:Empty_GIF_hex.png + start = new byte[6]; + bufferedInputStream.read(start); + if (start[0] == (byte) 0x47 && + start[1] == (byte) 0x49 && + start[2] == (byte) 0x46 && + start[3] == (byte) 0x38 && + start[4] == (byte) 0x39 && + start[5] == (byte) 0x61) { + return "gif"; + } + + bufferedInputStream.close(); + fileInputStream.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/Md5Helper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/Md5Helper.java new file mode 100644 index 00000000..924b0ac3 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/Md5Helper.java @@ -0,0 +1,69 @@ +package com.wuadam.awesomewebview.helpers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.security.DigestInputStream; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +public class Md5Helper { + + /** + * 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 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; + } + + 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); + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/PermissionHelper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/PermissionHelper.java new file mode 100644 index 00000000..19ad39fb --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/PermissionHelper.java @@ -0,0 +1,78 @@ +package com.wuadam.awesomewebview.helpers; + +import android.content.Context; +import android.content.pm.PackageManager; +import androidx.core.app.ActivityCompat; + +//import com.yanzhenjie.permission.Action; +//import com.yanzhenjie.permission.AndPermission; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by wuzongheng on 2016/11/5. + */ + +public class PermissionHelper { + + public static boolean hasPermissions(Context context, String... permissionName) { + for (String permission : permissionName) { + if (ActivityCompat.checkSelfPermission(context, permission) != PackageManager.PERMISSION_GRANTED) { + return false; + } + } + return true; + } + + public static List getGrantedPermissions(Context context, String... permissionName) { + List permissionsGranted = new ArrayList<>(); + for (String permission : permissionName) { + if (ActivityCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED) { + permissionsGranted.add(permission); + } + } + return permissionsGranted; + } + + public interface CheckPermissionListener { + void onAllGranted(boolean sync); + + /** + * Partly granted(deniedPermissions.size() >= 0) or all denied + */ + void onPartlyGranted(List permissionsDenied, boolean sync); + } + + public static void CheckPermissions(final Context context, final CheckPermissionListener checkPermissionListener, String... permissionName) { + + if (hasPermissions(context, permissionName)) { + if (checkPermissionListener != null) { + checkPermissionListener.onAllGranted(true); + } + }else { +// AndPermission.with(context) +// .runtime() +// .permission(permissionName) +// .onGranted(new Action>() { +// @Override +// public void onAction(List permissions) { +// // 权限申请成功回调。 +// if (checkPermissionListener != null) { +// checkPermissionListener.onAllGranted(false); +// } +// } +// }) +// .onDenied(new Action>() { +// @Override +// public void onAction(List permissions) { +// if (checkPermissionListener != null) { +// checkPermissionListener.onPartlyGranted(permissions, false); +// } +// } +// }) +// .start(); + } + } + +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/TypefaceHelper.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/TypefaceHelper.java new file mode 100644 index 00000000..7d33cecc --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/TypefaceHelper.java @@ -0,0 +1,48 @@ +package com.wuadam.awesomewebview.helpers; + +import android.content.Context; +import android.graphics.Typeface; +import androidx.collection.SimpleArrayMap; + +/** + * Created by Leonardo on 11/14/15. + */ + +/* + Each call to Typeface.createFromAsset will load a new instance of the typeface into memory, + and this memory is not consistently get garbage collected + http://code.google.com/p/android/issues/detail?id=9904 + (It states released but even on Lollipop you can see the typefaces accumulate even after + multiple GC passes) + You can detect this by running: + adb shell dumpsys meminfo com.your.packagenage + You will see output like: + Asset Allocations + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Regular.ttf: 123K + zip:/data/app/com.your.packagenage-1.apk:/assets/Roboto-Medium.ttf: 125K +*/ +public class TypefaceHelper { + + private static final SimpleArrayMap cache = new SimpleArrayMap<>(); + + private TypefaceHelper() { + } + + public static Typeface get(Context c, String name) { + synchronized (cache) { + if (!cache.containsKey(name)) { + try { + Typeface t = Typeface.createFromAsset(c.getAssets(), String.format("fonts/%s", name)); + cache.put(name, t); + return t; + } catch (RuntimeException e) { + return null; + } + } + return cache.get(name); + } + } +} \ No newline at end of file diff --git a/library/src/main/java/com/wuadam/awesomewebview/helpers/UrlParser.java b/library/src/main/java/com/wuadam/awesomewebview/helpers/UrlParser.java new file mode 100644 index 00000000..34e3396b --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/helpers/UrlParser.java @@ -0,0 +1,22 @@ +package com.wuadam.awesomewebview.helpers; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Created by Leonardo on 11/23/15. + */ +public class UrlParser { + + private UrlParser() { + } + + public static String getHost(String url) { + try { + return new URL(url).getHost(); + } catch (MalformedURLException e) { + e.printStackTrace(); + } + return url; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/jsInterface/BaseJsInterface.java b/library/src/main/java/com/wuadam/awesomewebview/jsInterface/BaseJsInterface.java new file mode 100644 index 00000000..b3c2b2fa --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/jsInterface/BaseJsInterface.java @@ -0,0 +1,19 @@ +package com.wuadam.awesomewebview.jsInterface; + +import android.webkit.JavascriptInterface; + +/** + * @Description: Base class for JS interface + * @Author: zongheng.wu + * @Date: 2/23/21 4:10 PM + */ +public abstract class BaseJsInterface { + + public BaseJsInterface() { + } + + @JavascriptInterface + public String getSimpleName() { + return getClass().getSimpleName(); + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/jsInterface/CommonJsHelper.java b/library/src/main/java/com/wuadam/awesomewebview/jsInterface/CommonJsHelper.java new file mode 100644 index 00000000..7c276c5a --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/jsInterface/CommonJsHelper.java @@ -0,0 +1,61 @@ +package com.wuadam.awesomewebview.jsInterface; + +import android.text.TextUtils; +import android.util.Pair; +import android.webkit.WebView; + +import java.util.ArrayList; +import java.util.List; + +/** + * @Description: JS interface handler + * @Author: zongheng.wu + * @Date: 2/23/21 3:36 PM + */ +public class CommonJsHelper { + private static CommonJsHelper commonJsHelper; + + public static CommonJsHelper getInstance() { + if (commonJsHelper == null) { + synchronized (CommonJsHelper.class) { + if (commonJsHelper == null) { + commonJsHelper = new CommonJsHelper(); + } + } + } + return commonJsHelper; + } + + private CommonJsHelper() { + } + + private final List, String>> interfacesInternal = new ArrayList<>(0); + + /** + * add new JS interface + * @param ifc + * @param bridge + */ + public void addJavascriptInterface(Class ifc, String bridge) { + interfacesInternal.add(Pair.create(ifc, bridge)); + } + + /** + * do not call, only for internal + * @param webView + */ + public void addJavascriptInterface(WebView webView) { + for (Pair, String> pair: interfacesInternal) { + if (pair == null || pair.first == null || TextUtils.isEmpty(pair.second)) { + continue; + } + try { + webView.addJavascriptInterface(pair.first.newInstance(), pair.second); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InstantiationException e) { + e.printStackTrace(); + } + } + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/jsInterface/VideoJsHelper.java b/library/src/main/java/com/wuadam/awesomewebview/jsInterface/VideoJsHelper.java new file mode 100644 index 00000000..77cb4b72 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/jsInterface/VideoJsHelper.java @@ -0,0 +1,67 @@ +package com.wuadam.awesomewebview.jsInterface; + +import android.annotation.SuppressLint; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.webkit.WebChromeClient; +import android.webkit.WebView; + +import com.wuadam.awesomewebview.views.VideoEnabledWebChromeClient; + +public class VideoJsHelper { + private VideoEnabledWebChromeClient videoEnabledWebChromeClient; + private boolean addedJavascriptInterface; + + public class JavascriptInterface + { + @android.webkit.JavascriptInterface @SuppressWarnings("unused") + public void notifyVideoEnd() // Must match Javascript interface method of VideoEnabledWebChromeClient + { + Log.d("___", "GOT IT"); + if (videoEnabledWebChromeClient != null) { + // This code is not executed in the UI thread, so we must force that to happen + new Handler(Looper.getMainLooper()).post(new Runnable() { + @Override + public void run() { + videoEnabledWebChromeClient.onHideCustomView(); + } + }); + } + } + } + + /** + * Indicates if the video is being displayed using a custom view (typically full-screen) + * @return true it the video is being displayed using a custom view (typically full-screen) + */ + @SuppressWarnings("unused") + public boolean isVideoFullscreen() + { + return videoEnabledWebChromeClient != null && videoEnabledWebChromeClient.isVideoFullscreen(); + } + + /** + * Pass only a VideoEnabledWebChromeClient instance. + */ + @SuppressLint("SetJavaScriptEnabled") + public void setWebChromeClient(WebChromeClient client) + { + if (client instanceof VideoEnabledWebChromeClient) + { + this.videoEnabledWebChromeClient = (VideoEnabledWebChromeClient) client; + } + } + + public void addJavascriptInterface(WebView webView) + { + if (!addedJavascriptInterface) + { + // Add javascript interface to be called when the video ends (must be done before page load) + //noinspection all + webView.addJavascriptInterface(new JavascriptInterface(), "_VideoEnabledWebView"); // Must match Javascript interface name of VideoEnabledWebChromeClient + + addedJavascriptInterface = true; + } + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/listeners/BroadCastManager.java b/library/src/main/java/com/wuadam/awesomewebview/listeners/BroadCastManager.java new file mode 100644 index 00000000..8c61cd8c --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/listeners/BroadCastManager.java @@ -0,0 +1,224 @@ +package com.wuadam.awesomewebview.listeners; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; + +import java.util.List; + +/** + * Created by TheFinestArtist on 1/26/16. + */ +public class BroadCastManager { + + static final String WEBVIEW_EVENT = "WEBVIEW_EVENT"; + static final String EXTRA_KEY = "EXTRA_KEY"; + static final String EXTRA_TYPE = "EXTRA_TYPE"; + static final String EXTRA_URL = "EXTRA_URL"; + static final String EXTRA_TITLE = "EXTRA_TITLE"; + static final String EXTRA_PROGESS = "EXTRA_PROGESS"; + static final String EXTRA_PRECOMPOSED = "EXTRA_PRECOMPOSED"; + static final String EXTRA_USER_AGENT = "EXTRA_USER_AGENT"; + static final String EXTRA_CONTENT_DISPOSITION = "EXTRA_CONTENT_DISPOSITION"; + static final String EXTRA_MIME_TYPE = "EXTRA_MIME_TYPE"; + static final String EXTRA_CONTENT_LENGTH = "EXTRA_CONTENT_LENGTH"; + static final String EXTRA_MENU_CODE = "EXTRA_MENU_CODE"; + static final String EXTRA_IMAGE_URL = "EXTRA_IMAGE_URL"; + + protected int key; + protected List listeners; + protected LocalBroadcastManager manager; + protected BroadcastReceiver receiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (context == null || intent == null) return; + int key = intent.getIntExtra(EXTRA_KEY, Integer.MIN_VALUE); + if (BroadCastManager.this.key == key) handleIntent(intent); + } + }; + + public BroadCastManager(Context context, int key, @NonNull List listeners) { + this.key = key; + this.listeners = listeners; + manager = LocalBroadcastManager.getInstance(context); + manager.registerReceiver(receiver, new IntentFilter(WEBVIEW_EVENT)); + } + + // Base Static Methods + private static Intent getBaseIntent(int key, Type type) { + return new Intent(BroadCastManager.WEBVIEW_EVENT).putExtra(EXTRA_KEY, key) + .putExtra(EXTRA_TYPE, type); + } + + private static void sendBroadCast(Context context, Intent intent) { + LocalBroadcastManager.getInstance(context).sendBroadcast(intent); + } + + // Handle Each Event Type + public static void onProgressChanged(Context context, int key, int progress) { + Intent intent = getBaseIntent(key, Type.PROGRESS_CHANGED).putExtra(EXTRA_PROGESS, progress); + sendBroadCast(context, intent); + } + + public static void onReceivedTitle(Context context, int key, String title) { + Intent intent = getBaseIntent(key, Type.RECEIVED_TITLE).putExtra(EXTRA_TITLE, title); + sendBroadCast(context, intent); + } + + public static void onReceivedTouchIconUrl(Context context, int key, String url, + boolean precomposed) { + Intent intent = getBaseIntent(key, Type.RECEIVED_TOUCH_ICON_URL).putExtra(EXTRA_URL, url) + .putExtra(EXTRA_PRECOMPOSED, precomposed); + sendBroadCast(context, intent); + } + + public static void onPageStarted(Context context, int key, String url) { + Intent intent = getBaseIntent(key, Type.PAGE_STARTED).putExtra(EXTRA_URL, url); + sendBroadCast(context, intent); + } + + public static void onPageFinished(Context context, int key, String url) { + Intent intent = getBaseIntent(key, Type.PAGE_FINISHED).putExtra(EXTRA_URL, url); + sendBroadCast(context, intent); + } + + public static void onLoadResource(Context context, int key, String url) { + Intent intent = getBaseIntent(key, Type.LOAD_RESOURCE).putExtra(EXTRA_URL, url); + sendBroadCast(context, intent); + } + + public static void onPageCommitVisible(Context context, int key, String url) { + Intent intent = getBaseIntent(key, Type.PAGE_COMMIT_VISIBLE).putExtra(EXTRA_URL, url); + sendBroadCast(context, intent); + } + + public static void onDownloadStart(Context context, int key, String url, String userAgent, + String contentDisposition, String mimeType, long contentLength) { + Intent intent = getBaseIntent(key, Type.DOWNLOADED_START).putExtra(EXTRA_URL, url) + .putExtra(EXTRA_USER_AGENT, userAgent) + .putExtra(EXTRA_CONTENT_DISPOSITION, contentDisposition) + .putExtra(EXTRA_MIME_TYPE, mimeType) + .putExtra(EXTRA_CONTENT_LENGTH, contentLength); + sendBroadCast(context, intent); + } + + public static void onCustomMenuClick(Context context, int key, String menuCode) { + Intent intent = getBaseIntent(key, Type.CUSTOM_MENU_CLICK).putExtra(EXTRA_MENU_CODE, menuCode); + sendBroadCast(context, intent); + } + + public static void onClickImage(Context context, int key, String imageUrl) { + Intent intent = getBaseIntent(key, Type.CLICK_IMAGE).putExtra(EXTRA_IMAGE_URL, imageUrl); + sendBroadCast(context, intent); + } + + public static void unregister(Context context, int key) { + Intent intent = getBaseIntent(key, Type.UNREGISTER); + sendBroadCast(context, intent); + } + + private void handleIntent(@NonNull Intent intent) { + Type type = (Type) intent.getSerializableExtra(EXTRA_TYPE); + switch (type) { + case PROGRESS_CHANGED: + onProgressChanged(intent); + break; + case RECEIVED_TITLE: + onReceivedTitle(intent); + break; + case RECEIVED_TOUCH_ICON_URL: + onReceivedTouchIconUrl(intent); + break; + case PAGE_STARTED: + onPageStarted(intent); + break; + case PAGE_FINISHED: + onPageFinished(intent); + break; + case LOAD_RESOURCE: + onLoadResource(intent); + break; + case PAGE_COMMIT_VISIBLE: + onPageCommitVisible(intent); + break; + case DOWNLOADED_START: + onDownloadStart(intent); + break; + case CUSTOM_MENU_CLICK: + onCustomMenuClick(intent); + break; + case CLICK_IMAGE: + onClickImage(intent); + break; + case UNREGISTER: + unregister(); + break; + } + } + + public void onProgressChanged(Intent intent) { + for (WebViewListener listener : listeners) + listener.onProgressChanged(intent.getIntExtra(EXTRA_PROGESS, 0)); + } + + public void onReceivedTitle(Intent intent) { + for (WebViewListener listener : listeners) + listener.onReceivedTitle(intent.getStringExtra(EXTRA_TITLE)); + } + + public void onReceivedTouchIconUrl(Intent intent) { + for (WebViewListener listener : listeners) + listener.onReceivedTouchIconUrl(intent.getStringExtra(EXTRA_URL), + intent.getBooleanExtra(EXTRA_PRECOMPOSED, false)); + } + + public void onPageStarted(Intent intent) { + for (WebViewListener listener : listeners) + listener.onPageStarted(intent.getStringExtra(EXTRA_URL)); + } + + public void onPageFinished(Intent intent) { + for (WebViewListener listener : listeners) + listener.onPageFinished(intent.getStringExtra(EXTRA_URL)); + } + + public void onLoadResource(Intent intent) { + for (WebViewListener listener : listeners) + listener.onLoadResource(intent.getStringExtra(EXTRA_URL)); + } + + public void onPageCommitVisible(Intent intent) { + for (WebViewListener listener : listeners) + listener.onPageCommitVisible(intent.getStringExtra(EXTRA_URL)); + } + + public void onDownloadStart(Intent intent) { + for (WebViewListener listener : listeners) + listener.onDownloadStart(intent.getStringExtra(EXTRA_URL), + intent.getStringExtra(EXTRA_USER_AGENT), intent.getStringExtra(EXTRA_CONTENT_DISPOSITION), + intent.getStringExtra(EXTRA_MIME_TYPE), intent.getLongExtra(EXTRA_CONTENT_LENGTH, 0l)); + } + + public void onCustomMenuClick(Intent intent) { + for (WebViewListener listener: listeners) { + listener.onCustomMenuClick(intent.getStringExtra(EXTRA_MENU_CODE)); + } + } + + public void onClickImage(Intent intent) { + for (WebViewListener listener: listeners) { + listener.onClickImage(intent.getStringExtra(EXTRA_IMAGE_URL)); + } + } + + private void unregister() { + if (manager != null && receiver != null) manager.unregisterReceiver(receiver); + } + + public enum Type { + PROGRESS_CHANGED, RECEIVED_TITLE, RECEIVED_TOUCH_ICON_URL, PAGE_STARTED, PAGE_FINISHED, LOAD_RESOURCE, PAGE_COMMIT_VISIBLE, DOWNLOADED_START, CUSTOM_MENU_CLICK, CLICK_IMAGE, UNREGISTER + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/listeners/WebViewListener.java b/library/src/main/java/com/wuadam/awesomewebview/listeners/WebViewListener.java new file mode 100644 index 00000000..292997a1 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/listeners/WebViewListener.java @@ -0,0 +1,38 @@ +package com.wuadam.awesomewebview.listeners; + +/** + * Created by TheFinestArtist on 1/26/16. + */ +public abstract class WebViewListener { + + public void onProgressChanged(int progress) { + } + + public void onReceivedTitle(String title) { + } + + public void onReceivedTouchIconUrl(String url, boolean precomposed) { + } + + public void onPageStarted(String url) { + } + + public void onPageFinished(String url) { + } + + public void onLoadResource(String url) { + } + + public void onPageCommitVisible(String url) { + } + + public void onDownloadStart(String url, String userAgent, String contentDisposition, + String mimeType, long contentLength) { + } + + public void onCustomMenuClick(String menuCode) { + } + + public void onClickImage(String imageUrl) { + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/objects/CustomMenu.java b/library/src/main/java/com/wuadam/awesomewebview/objects/CustomMenu.java new file mode 100644 index 00000000..f44c5bcd --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/objects/CustomMenu.java @@ -0,0 +1,23 @@ +package com.wuadam.awesomewebview.objects; + +import androidx.annotation.StringRes; + +import java.io.Serializable; + +public class CustomMenu implements Serializable { + private int titleRes; + private String code; + + public CustomMenu(@StringRes int titleRes, String code) { + this.titleRes = titleRes; + this.code = code; + } + + public int getTitleRes() { + return titleRes; + } + + public String getCode() { + return code; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/views/ShadowLayout.java b/library/src/main/java/com/wuadam/awesomewebview/views/ShadowLayout.java new file mode 100644 index 00000000..334a9501 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/views/ShadowLayout.java @@ -0,0 +1,187 @@ +package com.wuadam.awesomewebview.views; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; +import android.graphics.drawable.BitmapDrawable; +import android.os.Build; +import androidx.core.content.ContextCompat; +import android.util.AttributeSet; +import android.widget.FrameLayout; + +import com.wuadam.awesomewebview.R; + + +/** + * Created by Leonardo on 11/26/15. + */ + +public class ShadowLayout extends FrameLayout { + + private int shadowColor; + private float shadowSize; + private float cornerRadius; + private float dx; + private float dy; + + public ShadowLayout(Context context) { + super(context); + setWillNotDraw(false); + initAttributes(null); + setPadding(); + } + + public ShadowLayout(Context context, AttributeSet attrs) { + super(context, attrs); + setWillNotDraw(false); + initAttributes(attrs); + setPadding(); + } + + public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + setWillNotDraw(false); + initAttributes(attrs); + setPadding(); + } + + private void initAttributes(AttributeSet attrs) { + TypedArray attr = getContext().obtainStyledAttributes(attrs, R.styleable.ShadowLayout, 0, 0); + if (attr == null) return; + + try { + cornerRadius = attr.getDimension(R.styleable.ShadowLayout_slCornerRadius, + getResources().getDimension(R.dimen.defaultMenuDropShadowCornerRadius)); + shadowSize = attr.getDimension(R.styleable.ShadowLayout_slShadowSize, + getResources().getDimension(R.dimen.defaultMenuDropShadowSize)); + dx = attr.getDimension(R.styleable.ShadowLayout_slDx, 0); + dy = attr.getDimension(R.styleable.ShadowLayout_slDy, 0); + shadowColor = attr.getColor(R.styleable.ShadowLayout_slShadowColor, + ContextCompat.getColor(getContext(), R.color.finestBlack10)); + } finally { + attr.recycle(); + } + } + + private void setPadding() { + int xPadding = (int) (shadowSize + Math.abs(dx)); + int yPadding = (int) (shadowSize + Math.abs(dy)); + setPadding(xPadding, yPadding, xPadding, yPadding); + } + + public void setShadowColor(int shadowColor) { + this.shadowColor = shadowColor; + invalidate(); + } + + public void setShadowSize(float shadowSize) { + this.shadowSize = shadowSize; + setPadding(); + } + + public void setCornerRadius(float cornerRadius) { + this.cornerRadius = cornerRadius; + invalidate(); + } + + public void setDx(float dx) { + this.dx = dx; + setPadding(); + } + + public void setDy(float dy) { + this.dy = dy; + setPadding(); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + // RoundRectShape rss = new RoundRectShape(new float[]{12f, 12f, 12f, + // 12f, 12f, 12f, 12f, 12f}, null, null); + // ShapeDrawable sds = new ShapeDrawable(rss); + // sds.setShaderFactory(new ShapeDrawable.ShaderFactory() { + // + // @Override + // public Shader resize(int width, int height) { + // LinearGradient lg = new LinearGradient(0, 0, 0, height, + // new int[]{Color.parseColor("#e5e5e5"), + // Color.parseColor("#e5e5e5"), + // Color.parseColor("#e5e5e5"), + // Color.parseColor("#e5e5e5")}, new float[]{0, + // 0.50f, 0.50f, 1}, Shader.TileMode.REPEAT); + // return lg; + // } + // }); + // + // LayerDrawable ld = new LayerDrawable(new Drawable[]{sds, sds}); + // ld.setLayerInset(0, 5, 5, 0, 0); // inset the shadow so it doesn't start right at the left/top + // ld.setLayerInset(1, 0, 0, 5, 5); // inset the top drawable so we can leave a bit of space for the shadow to use + + setBackgroundCompat(canvas.getWidth(), canvas.getHeight()); + } + + @SuppressWarnings("deprecation") + private void setBackgroundCompat(int w, int h) { + Bitmap bitmap = + createShadowBitmap(w, h, cornerRadius, shadowSize, dx, dy, shadowColor, Color.TRANSPARENT); + // Bitmap coloredBitmap = BitmapHelper.getColoredBitmap(getContext(), bitmap, shadowColor); + BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { + setBackgroundDrawable(drawable); + } else { + setBackground(drawable); + } + } + + private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight, float cornerRadius, + float shadowSize, float dx, float dy, int shadowColor, int fillColor) { + + Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ALPHA_8); + Canvas canvas = new Canvas(output); + + RectF shadowRect = + new RectF(shadowSize, shadowSize, shadowWidth - shadowSize, shadowHeight - shadowSize); + + if (dy > 0) { + shadowRect.top += dy; + shadowRect.bottom -= dy; + } else if (dy < 0) { + shadowRect.top += Math.abs(dy); + shadowRect.bottom -= Math.abs(dy); + } + + if (dx > 0) { + shadowRect.left += dx; + shadowRect.right -= dx; + } else if (dx < 0) { + shadowRect.left += Math.abs(dx); + shadowRect.right -= Math.abs(dx); + } + + Paint shadowPaint = new Paint(); + shadowPaint.setAntiAlias(true); + shadowPaint.setColor(fillColor); + shadowPaint.setStyle(Paint.Style.FILL); + shadowPaint.setShadowLayer(shadowSize, dx, dy, shadowColor); + + canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, shadowPaint); + + return output; + } + + @Override + protected int getSuggestedMinimumWidth() { + return 0; + } + + @Override + protected int getSuggestedMinimumHeight() { + return 0; + } +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebChromeClient.java b/library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebChromeClient.java new file mode 100644 index 00000000..c4667742 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebChromeClient.java @@ -0,0 +1,283 @@ +package com.wuadam.awesomewebview.views; + +import android.media.MediaPlayer; +import android.view.SurfaceView; +import android.view.View; +import android.view.ViewGroup; +import android.webkit.WebChromeClient; +import android.webkit.WebView; +import android.widget.FrameLayout; + +/** + * This class serves as a WebChromeClient to be set to a WebView, allowing it to play video. + * Video will play differently depending on target API level (in-line, fullscreen, or both). + * + * It has been tested with the following video classes: + * - android.widget.VideoView (typically API level <11) + * - android.webkit.HTML5VideoFullScreen$VideoSurfaceView/VideoTextureView (typically API level 11-18) + * - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView (typically API level 19+) + * + * Important notes: + * - For API level 11+, android:hardwareAccelerated="true" must be set in the application manifest. + * - The invoking activity must call VideoEnabledWebChromeClient's onBackPressed() inside of its own onBackPressed(). + * - Tested in Android API levels 8-19. Only tested on http://m.youtube.com. + * + * @author Cristian Perez (http://cpr.name) + * + */ +public class VideoEnabledWebChromeClient extends WebChromeClient implements MediaPlayer.OnPreparedListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnErrorListener +{ + public interface ToggledFullscreenCallback + { + void toggledFullscreen(boolean fullscreen); + } + + private View activityNonVideoView; + private ViewGroup activityVideoView; + private View loadingView; + private WebView webView; + + private boolean isVideoFullscreen; // Indicates if the video is being displayed using a custom view (typically full-screen) + private FrameLayout videoViewContainer; + private CustomViewCallback videoViewCallback; + + private ToggledFullscreenCallback toggledFullscreenCallback; + + /** + * Never use this constructor alone. + * This constructor allows this class to be defined as an inline inner class in which the user can override methods + */ + @SuppressWarnings("unused") + public VideoEnabledWebChromeClient() + { + } + + /** + * Builds a video enabled WebChromeClient. + * @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen. + * @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout. + */ + @SuppressWarnings("unused") + public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView) + { + this.activityNonVideoView = activityNonVideoView; + this.activityVideoView = activityVideoView; + this.loadingView = null; + this.webView = null; + this.isVideoFullscreen = false; + } + + /** + * Builds a video enabled WebChromeClient. + * @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen. + * @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout. + * @param loadingView A View to be shown while the video is loading (typically only used in API level <11). Must be already inflated and not attached to a parent view. + */ + @SuppressWarnings("unused") + public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView) + { + this.activityNonVideoView = activityNonVideoView; + this.activityVideoView = activityVideoView; + this.loadingView = loadingView; + this.webView = null; + this.isVideoFullscreen = false; + } + + /** + * Builds a video enabled WebChromeClient. + * @param activityNonVideoView A View in the activity's layout that contains every other view that should be hidden when the video goes full-screen. + * @param activityVideoView A ViewGroup in the activity's layout that will display the video. Typically you would like this to fill the whole layout. + * @param loadingView A View to be shown while the video is loading (typically only used in API level <11). Must be already inflated and not attached to a parent view. + * @param webView The owner VideoEnabledWebView. Passing it will enable the VideoEnabledWebChromeClient to detect the HTML5 video ended event and exit full-screen. + * Note: The web page must only contain one video tag in order for the HTML5 video ended event to work. This could be improved if needed (see Javascript code). + */ + @SuppressWarnings("unused") + public VideoEnabledWebChromeClient(View activityNonVideoView, ViewGroup activityVideoView, View loadingView, WebView webView) + { + this.activityNonVideoView = activityNonVideoView; + this.activityVideoView = activityVideoView; + this.loadingView = loadingView; + this.webView = webView; + this.isVideoFullscreen = false; + } + + /** + * Indicates if the video is being displayed using a custom view (typically full-screen) + * @return true it the video is being displayed using a custom view (typically full-screen) + */ + public boolean isVideoFullscreen() + { + return isVideoFullscreen; + } + + /** + * Set a callback that will be fired when the video starts or finishes displaying using a custom view (typically full-screen) + * @param callback A VideoEnabledWebChromeClient.ToggledFullscreenCallback callback + */ + @SuppressWarnings("unused") + public void setOnToggledFullscreen(ToggledFullscreenCallback callback) + { + this.toggledFullscreenCallback = callback; + } + + @Override + public void onShowCustomView(View view, CustomViewCallback callback) + { + if (view instanceof FrameLayout) + { + // A video wants to be shown + FrameLayout frameLayout = (FrameLayout) view; + View focusedChild = frameLayout.getFocusedChild(); + + // Save video related variables + this.isVideoFullscreen = true; + this.videoViewContainer = frameLayout; + this.videoViewCallback = callback; + + // Hide the non-video view, add the video view, and show it + activityNonVideoView.setVisibility(View.INVISIBLE); + activityVideoView.setVisibility(View.VISIBLE); + activityVideoView.addView(videoViewContainer, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); + activityVideoView.setVisibility(View.VISIBLE); + + if (focusedChild instanceof android.widget.VideoView) + { + // android.widget.VideoView (typically API level <11) + android.widget.VideoView videoView = (android.widget.VideoView) focusedChild; + + // Handle all the required events + videoView.setOnPreparedListener(this); + videoView.setOnCompletionListener(this); + videoView.setOnErrorListener(this); + } + else + { + // Other classes, including: + // - android.webkit.HTML5VideoFullScreen$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 11-18) + // - android.webkit.HTML5VideoFullScreen$VideoTextureView, which inherits from android.view.TextureView (typically API level 11-18) + // - com.android.org.chromium.content.browser.ContentVideoView$VideoSurfaceView, which inherits from android.view.SurfaceView (typically API level 19+) + + // Handle HTML5 video ended event only if the class is a SurfaceView + // Test case: TextureView of Sony Xperia T API level 16 doesn't work fullscreen when loading the javascript below + if (webView != null && webView.getSettings().getJavaScriptEnabled() && focusedChild instanceof SurfaceView) + { + // Run javascript code that detects the video end and notifies the Javascript interface + String js = "javascript:"; + js += "var _ytrp_html5_video_last;"; + js += "var _ytrp_html5_video = document.getElementsByTagName('video')[0];"; + js += "if (_ytrp_html5_video != undefined && _ytrp_html5_video != _ytrp_html5_video_last) {"; + { + js += "_ytrp_html5_video_last = _ytrp_html5_video;"; + js += "function _ytrp_html5_video_ended() {"; + { + js += "_VideoEnabledWebView.notifyVideoEnd();"; // Must match Javascript interface name and method of VideoEnableWebView + } + js += "}"; + js += "_ytrp_html5_video.addEventListener('ended', _ytrp_html5_video_ended);"; + } + js += "}"; + webView.loadUrl(js); + } + } + + // Notify full-screen change + if (toggledFullscreenCallback != null) + { + toggledFullscreenCallback.toggledFullscreen(true); + } + } + } + + @Override @SuppressWarnings("deprecation") + public void onShowCustomView(View view, int requestedOrientation, CustomViewCallback callback) // Available in API level 14+, deprecated in API level 18+ + { + onShowCustomView(view, callback); + } + + @Override + public void onHideCustomView() + { + // This method should be manually called on video end in all cases because it's not always called automatically. + // This method must be manually called on back key press (from this class' onBackPressed() method). + + if (isVideoFullscreen) + { + // Hide the video view, remove it, and show the non-video view + activityVideoView.setVisibility(View.INVISIBLE); + activityVideoView.removeView(videoViewContainer); + activityNonVideoView.setVisibility(View.VISIBLE); + + // Call back (only in API level <19, because in API level 19+ with chromium webview it crashes) + if (videoViewCallback != null && !videoViewCallback.getClass().getName().contains(".chromium.")) + { + videoViewCallback.onCustomViewHidden(); + } + + // Reset video related variables + isVideoFullscreen = false; + videoViewContainer = null; + videoViewCallback = null; + + // Notify full-screen change + if (toggledFullscreenCallback != null) + { + toggledFullscreenCallback.toggledFullscreen(false); + } + } + } + + @Override + public View getVideoLoadingProgressView() // Video will start loading + { + if (loadingView != null) + { + loadingView.setVisibility(View.VISIBLE); + return loadingView; + } + else + { + return super.getVideoLoadingProgressView(); + } + } + + @Override + public void onPrepared(MediaPlayer mp) // Video will start playing, only called in the case of android.widget.VideoView (typically API level <11) + { + if (loadingView != null) + { + loadingView.setVisibility(View.GONE); + } + } + + @Override + public void onCompletion(MediaPlayer mp) // Video finished playing, only called in the case of android.widget.VideoView (typically API level <11) + { + onHideCustomView(); + } + + @Override + public boolean onError(MediaPlayer mp, int what, int extra) // Error while playing video, only called in the case of android.widget.VideoView (typically API level <11) + { + return false; // By returning false, onCompletion() will be called + } + + /** + * Notifies the class that the back key has been pressed by the user. + * This must be called from the Activity's onBackPressed(), and if it returns false, the activity itself should handle it. Otherwise don't do anything. + * @return Returns true if the event was handled, and false if was not (video view is not visible) + */ + @SuppressWarnings("unused") + public boolean onBackPressed() + { + if (isVideoFullscreen) + { + onHideCustomView(); + return true; + } + else + { + return false; + } + } + +} diff --git a/library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebView.java b/library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebView.java new file mode 100644 index 00000000..63f4b662 --- /dev/null +++ b/library/src/main/java/com/wuadam/awesomewebview/views/VideoEnabledWebView.java @@ -0,0 +1,101 @@ +package com.wuadam.awesomewebview.views; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.util.AttributeSet; +import android.webkit.WebChromeClient; +import android.webkit.WebView; + +import com.wuadam.awesomewebview.jsInterface.VideoJsHelper; + +import java.util.Map; + +/** + * This class serves as a WebView to be used in conjunction with a VideoEnabledWebChromeClient. + * It makes possible: + * - To detect the HTML5 video ended event so that the VideoEnabledWebChromeClient can exit full-screen. + * + * Important notes: + * - Javascript is enabled by default and must not be disabled with getSettings().setJavaScriptEnabled(false). + * - setWebChromeClient() must be called before any loadData(), loadDataWithBaseURL() or loadUrl() method. + * + * @author Cristian Perez (http://cpr.name) + * + */ +public class VideoEnabledWebView extends WebView +{ + private VideoJsHelper videoJsHelper; + + @SuppressWarnings("unused") + public VideoEnabledWebView(Context context) + { + super(context); + videoJsHelper = new VideoJsHelper(); + } + + @SuppressWarnings("unused") + public VideoEnabledWebView(Context context, AttributeSet attrs) + { + super(context, attrs); + videoJsHelper = new VideoJsHelper(); + } + + @SuppressWarnings("unused") + public VideoEnabledWebView(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + videoJsHelper = new VideoJsHelper(); + } + + /** + * Indicates if the video is being displayed using a custom view (typically full-screen) + * @return true it the video is being displayed using a custom view (typically full-screen) + */ + @SuppressWarnings("unused") + public boolean isVideoFullscreen() + { + return videoJsHelper.isVideoFullscreen(); + } + + /** + * Pass a VideoEnabledWebChromeClient instance to enable video full-screen. + */ + @Override @SuppressLint("SetJavaScriptEnabled") + public void setWebChromeClient(WebChromeClient client) + { + getSettings().setJavaScriptEnabled(true); + + videoJsHelper.setWebChromeClient(client); + + super.setWebChromeClient(client); + } + + @Override + public void loadData(String data, String mimeType, String encoding) + { + videoJsHelper.addJavascriptInterface(this); + super.loadData(data, mimeType, encoding); + } + + @Override + public void loadDataWithBaseURL(String baseUrl, String data, String mimeType, String encoding, String historyUrl) + { + videoJsHelper.addJavascriptInterface(this); + super.loadDataWithBaseURL(baseUrl, data, mimeType, encoding, historyUrl); + } + + @Override + public void loadUrl(String url) + { + videoJsHelper.addJavascriptInterface(this); + super.loadUrl(url); + } + + @Override + public void loadUrl(String url, Map additionalHttpHeaders) + { + videoJsHelper.addJavascriptInterface(this); + super.loadUrl(url, additionalHttpHeaders); + } + +} diff --git a/library/src/main/res/anim/accelerate_cubic.xml b/library/src/main/res/anim/accelerate_cubic.xml new file mode 100755 index 00000000..f5b6affb --- /dev/null +++ b/library/src/main/res/anim/accelerate_cubic.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/accelerate_quart.xml b/library/src/main/res/anim/accelerate_quart.xml new file mode 100755 index 00000000..37f44f9b --- /dev/null +++ b/library/src/main/res/anim/accelerate_quart.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/accelerate_quint.xml b/library/src/main/res/anim/accelerate_quint.xml new file mode 100755 index 00000000..06c40402 --- /dev/null +++ b/library/src/main/res/anim/accelerate_quint.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/activity_close_enter.xml b/library/src/main/res/anim/activity_close_enter.xml new file mode 100755 index 00000000..8dfddbf8 --- /dev/null +++ b/library/src/main/res/anim/activity_close_enter.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/activity_close_exit.xml b/library/src/main/res/anim/activity_close_exit.xml new file mode 100755 index 00000000..50c60cc7 --- /dev/null +++ b/library/src/main/res/anim/activity_close_exit.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/activity_open_enter.xml b/library/src/main/res/anim/activity_open_enter.xml new file mode 100755 index 00000000..1cc5fcbe --- /dev/null +++ b/library/src/main/res/anim/activity_open_enter.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/activity_open_exit.xml b/library/src/main/res/anim/activity_open_exit.xml new file mode 100755 index 00000000..fd61ee41 --- /dev/null +++ b/library/src/main/res/anim/activity_open_exit.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/decelerate_cubic.xml b/library/src/main/res/anim/decelerate_cubic.xml new file mode 100755 index 00000000..348044f6 --- /dev/null +++ b/library/src/main/res/anim/decelerate_cubic.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/decelerate_quart.xml b/library/src/main/res/anim/decelerate_quart.xml new file mode 100755 index 00000000..4cdfac09 --- /dev/null +++ b/library/src/main/res/anim/decelerate_quart.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/decelerate_quint.xml b/library/src/main/res/anim/decelerate_quint.xml new file mode 100755 index 00000000..c47ce53f --- /dev/null +++ b/library/src/main/res/anim/decelerate_quint.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/fade_in_fast.xml b/library/src/main/res/anim/fade_in_fast.xml new file mode 100644 index 00000000..dc922c2a --- /dev/null +++ b/library/src/main/res/anim/fade_in_fast.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fade_in_medium.xml b/library/src/main/res/anim/fade_in_medium.xml new file mode 100644 index 00000000..98e4ccf9 --- /dev/null +++ b/library/src/main/res/anim/fade_in_medium.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fade_out_fast.xml b/library/src/main/res/anim/fade_out_fast.xml new file mode 100644 index 00000000..17409c3e --- /dev/null +++ b/library/src/main/res/anim/fade_out_fast.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fade_out_medium.xml b/library/src/main/res/anim/fade_out_medium.xml new file mode 100644 index 00000000..f8893b87 --- /dev/null +++ b/library/src/main/res/anim/fade_out_medium.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fragment_close_enter.xml b/library/src/main/res/anim/fragment_close_enter.xml new file mode 100755 index 00000000..ef981be6 --- /dev/null +++ b/library/src/main/res/anim/fragment_close_enter.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fragment_close_exit.xml b/library/src/main/res/anim/fragment_close_exit.xml new file mode 100755 index 00000000..c43fd197 --- /dev/null +++ b/library/src/main/res/anim/fragment_close_exit.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fragment_open_enter.xml b/library/src/main/res/anim/fragment_open_enter.xml new file mode 100755 index 00000000..6f897b32 --- /dev/null +++ b/library/src/main/res/anim/fragment_open_enter.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/fragment_open_exit.xml b/library/src/main/res/anim/fragment_open_exit.xml new file mode 100755 index 00000000..d90bc087 --- /dev/null +++ b/library/src/main/res/anim/fragment_open_exit.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/hold.xml b/library/src/main/res/anim/hold.xml new file mode 100644 index 00000000..637e548e --- /dev/null +++ b/library/src/main/res/anim/hold.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/modal_activity_close_enter.xml b/library/src/main/res/anim/modal_activity_close_enter.xml new file mode 100755 index 00000000..8dfddbf8 --- /dev/null +++ b/library/src/main/res/anim/modal_activity_close_enter.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/modal_activity_close_exit.xml b/library/src/main/res/anim/modal_activity_close_exit.xml new file mode 100755 index 00000000..d6c960d4 --- /dev/null +++ b/library/src/main/res/anim/modal_activity_close_exit.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/modal_activity_open_enter.xml b/library/src/main/res/anim/modal_activity_open_enter.xml new file mode 100755 index 00000000..ca2d5518 --- /dev/null +++ b/library/src/main/res/anim/modal_activity_open_enter.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/modal_activity_open_exit.xml b/library/src/main/res/anim/modal_activity_open_exit.xml new file mode 100755 index 00000000..fd61ee41 --- /dev/null +++ b/library/src/main/res/anim/modal_activity_open_exit.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/none.xml b/library/src/main/res/anim/none.xml new file mode 100644 index 00000000..ec54181d --- /dev/null +++ b/library/src/main/res/anim/none.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/library/src/main/res/anim/popup_flyout_hide.xml b/library/src/main/res/anim/popup_flyout_hide.xml new file mode 100644 index 00000000..cd74091b --- /dev/null +++ b/library/src/main/res/anim/popup_flyout_hide.xml @@ -0,0 +1,14 @@ + + + + + diff --git a/library/src/main/res/anim/popup_flyout_show.xml b/library/src/main/res/anim/popup_flyout_show.xml new file mode 100644 index 00000000..1f1782c8 --- /dev/null +++ b/library/src/main/res/anim/popup_flyout_show.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/library/src/main/res/anim/slide_down.xml b/library/src/main/res/anim/slide_down.xml new file mode 100644 index 00000000..80ee47f4 --- /dev/null +++ b/library/src/main/res/anim/slide_down.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/slide_left_in.xml b/library/src/main/res/anim/slide_left_in.xml new file mode 100644 index 00000000..761339b8 --- /dev/null +++ b/library/src/main/res/anim/slide_left_in.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/slide_right_out.xml b/library/src/main/res/anim/slide_right_out.xml new file mode 100644 index 00000000..06d05a82 --- /dev/null +++ b/library/src/main/res/anim/slide_right_out.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/anim/slide_up.xml b/library/src/main/res/anim/slide_up.xml new file mode 100644 index 00000000..aca5d38c --- /dev/null +++ b/library/src/main/res/anim/slide_up.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable-hdpi/back.png b/library/src/main/res/drawable-hdpi/back.png new file mode 100755 index 0000000000000000000000000000000000000000..992037edc65c3b286df5bda92329f62c6bf83d02 GIT binary patch literal 3304 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 literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-hdpi/close.png b/library/src/main/res/drawable-hdpi/close.png new file mode 100755 index 0000000000000000000000000000000000000000..0b92400e63e481d988cd638b7e899670f16f8af8 GIT binary patch literal 3176 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 Jtktj$|2Nl%)zbg~ literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-hdpi/forward.png b/library/src/main/res/drawable-hdpi/forward.png new file mode 100755 index 0000000000000000000000000000000000000000..bce266dea85416ab9d7a506b601a7483142dd75e GIT binary patch literal 3319 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 B6vY4l literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-hdpi/more.png b/library/src/main/res/drawable-hdpi/more.png new file mode 100755 index 0000000000000000000000000000000000000000..2d9096e427a3acb8c6fb9aabe6422b22581b6430 GIT binary patch literal 3039 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 literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-mdpi/close.png b/library/src/main/res/drawable-mdpi/close.png new file mode 100755 index 0000000000000000000000000000000000000000..099995874e06aa4c80328f43fd6ced22e36c6158 GIT binary patch literal 3170 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 IM6N<$f}_~vga7~l literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-mdpi/forward.png b/library/src/main/res/drawable-mdpi/forward.png new file mode 100755 index 0000000000000000000000000000000000000000..1b2f54f676514517d5d33d2d5618ac59ae3997b0 GIT binary patch literal 3111 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 B#?Sx& literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-mdpi/more.png b/library/src/main/res/drawable-mdpi/more.png new file mode 100755 index 0000000000000000000000000000000000000000..11e44d38e36641e4115c60a20dd27d8a267a7adf GIT binary patch literal 2911 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 JPDHLkV1hA3Sc(7u literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-v21/selector_dark_theme.xml b/library/src/main/res/drawable-v21/selector_dark_theme.xml new file mode 100644 index 00000000..12fed938 --- /dev/null +++ b/library/src/main/res/drawable-v21/selector_dark_theme.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable-v21/selector_dark_theme_holo.xml b/library/src/main/res/drawable-v21/selector_dark_theme_holo.xml new file mode 100644 index 00000000..6156a62f --- /dev/null +++ b/library/src/main/res/drawable-v21/selector_dark_theme_holo.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable-v21/selector_light_theme.xml b/library/src/main/res/drawable-v21/selector_light_theme.xml new file mode 100644 index 00000000..7729d282 --- /dev/null +++ b/library/src/main/res/drawable-v21/selector_light_theme.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable-v21/selector_light_theme_holo.xml b/library/src/main/res/drawable-v21/selector_light_theme_holo.xml new file mode 100644 index 00000000..3ef98bf7 --- /dev/null +++ b/library/src/main/res/drawable-v21/selector_light_theme_holo.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable-xhdpi/back.png b/library/src/main/res/drawable-xhdpi/back.png new file mode 100755 index 0000000000000000000000000000000000000000..542f8831a3f57c8dd15d74d5f459f0ecd1948377 GIT binary patch literal 3480 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 literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-xhdpi/forward.png b/library/src/main/res/drawable-xhdpi/forward.png new file mode 100755 index 0000000000000000000000000000000000000000..4231572a15d4b5f019a74676c4b7a33bf0cd89f3 GIT binary patch literal 3499 zcma)8Ra6vgwEgJr7EwAzI;CUi6c{9x5(Y#hM28TZq3b6^nn6(M6r`kv5Re#3atLVw z0VRZAVlMyVeYk7ghkf=s>#Xx|_St8x6L-&4kB0IFB>;fNKwsP9nw|awIq7v=%|hJ0 zCJJAD+h+h!G5rSukdwy_0Hv8H6ngKThfjddGY=nMZUZQk+t=U6-SdeX0D<#pOE}DO zn^pC6=~&As7Wu}=$AX2N+d?Y=$($+(;h~^6jO8huV=?by(9t3094Lq-N=iZ|vY3lf z$5YOdt@FG~ig+IzKX|?p^4@E@?R0bGs$oWLtMUk4KT3unr_RuqF_%M9mucN%_#V;U z+qbeVu7RTD_XYIi^^bU-UGfotvoIBvJG>aO4nPn%NktAYCb<~Nn=yF09j$B!0%Rls zCLr~$5jm0>Xapx|6$1@j0wg+1&=M371E(QZmo;!t3^)nB+MEPP^jUTY0kD6=!$MGy z3b>ivVzq&XGN>HAAFl&!BmuL#QLiGH6$6q6*6#YCwh3Uc^i*|#oEk`)$3}?&;t=38 zz{eK|BC`Rr?xD5H?>p7>Taas|vg=e@_!V_f_T*xIWY*T=w}i0+G zzkyUHMS5So6WAyAct}<7{4~M6j!vTi6l~91ANl^LjZR@o%iP??`ueP4ua>>@pmpdK zyxS3Defi|yFqMnblkXj?{3z)MC_Un{@7=@4#zi+rUsFW7ENmw0T{KbQFSsUodktJ# zY#_{Q=8S$x+Bu0w(nY*+TCYXE2u$D~tuBk66R9oBf!fDv1R<$nr4L+*{ul|-+XP@% zPXRb=_38R1OiqOKh*%j#;1AR<^$G+4(%s;-9{>-v`Nd&__3HiP0B9GWAhnvD$6f5A z7}A?vBnw?s7f$k#TKv7;TJ&0!kB~S0okS|bwS=QbYPldz65lkqWHII*(P_T4;@wuw zw5q;zf1SzmyM)_N6vUbXIP^_slts&-FH-Dj{>A}l35FK z$NWFtP)(FC+#0L!pqo!tgZ4{eez23t^gfXk@9WX}cvqAn&2{J_ZzXj_-;LV)l|-#^ zC$6I(5yXrr9Sns$F+z*m`Ie@>jj@gCtbQr?O@3L1c?tyzqDb>@A>Oo7qfdgL8UF2M zcafHgvC$Xhn`TUAlAsTXh4w1&GsZ!sg<4Vw4L*K=O)yUAPCT{X+ZHX$mDRt&m-co3 zkyjm=;u}%!I|EVc==Gj;nRU)}o%pXL@2x^*WmWcjXxG+a9I~hBi1HoRCWn9cLnZVHxlT?t}&DzSQ%l5Idfiyy1 zvZM}DrW>XUrgK6hU}fdSD;}rT5>G6Fq7h(t2usQXDmwUFD0M zpN|TcL_hAgIrKq~2DHdI-8pk5GPPw~<^7cIKe}{@o_!8${A=EQ-hJ-{Nd5^W{m0@O3GZegGi%-H;BGtNT zrnadFQGV)tz^3*`&HwG1+M0^OlJt_TipOK%XG#^%=~fmt73)bG zbuaA4^cb1*rgK717`0GTOq6xBdXjIf&5d!XOgY5(0`%Fk_FwG`?WJ6}l$m0Un4ug* ztz7)E%Dlp7+0W{I;_kM?Q#NQDgC|Be6~!Ud@=u?1EMM#$#jK)Nsn$Z+vLpr`_y!eX zyN{zvSj)s2MU^r%jAKpjzWJp|4=H6$6gXr;2qaMkb#ryYc_qf>$75%q4v<@>hx!nc z;MYG>+vI}yzsMAcZnQ)Sm8G;9(-<%2D&@20yx{hhR#j@3ig=xx;-^*4W0KYg-+^zs zzZj}?i>kZ&@-(4I;#NA@SFie%N)lT!%0{i?A22)N83&)t~cEMm1FVw@X!SJpI0vE6R8obbOyPr0tLF&ncXJ+v%m+ z*AAQ&ftAUW`;^z%T2M945c^a#K^7>Z!e6@qtw@)dSPjAsE&A&(tsazqOy#0a;&L#0!E9Wpr zH`g-f+}_3hv)QoP`1H4HN%&!3p=S zF=WdXyZo5W=ioDaz{-RUK4D9_O&RNO*c@M ztJ=)k9FH~jH0Pj;F;nXHR|~TT3$Gqy5Cuo$3wo!dQ4yn8sVB*FP?F^Ai(D87bVcqN27xIY*+8DhN{_ECBn|8c4x5TS>uJ7hnZ=e-Y<(TcQCsgHMqX+ zzFQq%17)Nh9^XIT>q0CbxDMVg)0wTm*&H&MhtiT zZ*sfpTbKY4EC2v90)T(<*SrnDb4dVpoB&YD0)Wluwc{6E00^fIv^8OY^FMLQ9xzSz z=oN&55x2;@K!S11Z6RLP_*wSaF^79p45G3&9-kOoT%GwmY8f~wX0@5`@bQMf3y-Jo zN-2n9m8X>7=ZTmPvvib6F+1@mn?n{a66Z2a6D&e^EJj^^Y5;zdkIk7PuG zH-NZ+m>UQH0$~Dj4GIll2ng@}pQW$XB8TcTbK~P=4g?v5Td2a}^Ufs%3$TUo;jMs$aSoeuyb0~fD#&8Zvtt1yo-S-YvOelOBf*f#LvPOAhcAY$xe+^`d zF>1VwL@0JAIC)Y@?hj{BajSLf6|e;|ih2_YqHSBjD&I?z7lf`|6Q6H7|8t!UgH{x- zYvQp}ceXeEb?WbK5^DR}9du}4cePz&0 zOuY`RSzeBP7|B+R4f-Q%x&Eqog4`0}F5tv|S8X)mHvvH}R~VBo!(B$vP@)%)meiRv z+Ex^Z-eazuwwcCqgYb1fLQ45nF7AKVy_(VvJwWXbpDT#`eE<5#FC4N}!>krY5-feN zTwIrF7~bPZs6s4HW>kah7(S;H*(=GL{bdc9j;Qg9wQ>zBLL}dZ6I-aHcO!~^U!GIoO8J-^q>hC+u0yqI+dk8^AT28>F6lXus{>6|+oYEPbl_ zDl`*?x>%(qjGZwl5Nq~fXCIarUL^Gyq48Go-{>1^%opLq o_>{2Q-2Xp+{4eXIgmsdEWs~Z`v6a!o>uUf8I;PsyT2A5r1JI~m=>Px# literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-xhdpi/more.png b/library/src/main/res/drawable-xhdpi/more.png new file mode 100755 index 0000000000000000000000000000000000000000..a92dd363a4cc6ec00747fe76310c10a99dd26f41 GIT binary patch literal 3154 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 M+9p~kO~=Up0TJ2_>;M1& literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable-xxhdpi/close.png b/library/src/main/res/drawable-xxhdpi/close.png new file mode 100755 index 0000000000000000000000000000000000000000..30d8b78db6cab878c95c64c6265ded0e60b6e0a6 GIT binary patch literal 4039 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 Ms=6v=P^*{!4O&+MQUCw| literal 0 HcmV?d00001 diff --git a/library/src/main/res/drawable/back_vector.xml b/library/src/main/res/drawable/back_vector.xml new file mode 100644 index 00000000..b6f19de5 --- /dev/null +++ b/library/src/main/res/drawable/back_vector.xml @@ -0,0 +1,11 @@ + + + + diff --git a/library/src/main/res/drawable/close_vector.xml b/library/src/main/res/drawable/close_vector.xml new file mode 100644 index 00000000..48f0bd82 --- /dev/null +++ b/library/src/main/res/drawable/close_vector.xml @@ -0,0 +1,11 @@ + + + + diff --git a/library/src/main/res/drawable/forward_vector.xml b/library/src/main/res/drawable/forward_vector.xml new file mode 100644 index 00000000..3c783119 --- /dev/null +++ b/library/src/main/res/drawable/forward_vector.xml @@ -0,0 +1,11 @@ + + + + diff --git a/library/src/main/res/drawable/more_vector.xml b/library/src/main/res/drawable/more_vector.xml new file mode 100644 index 00000000..e8c38cc9 --- /dev/null +++ b/library/src/main/res/drawable/more_vector.xml @@ -0,0 +1,17 @@ + + + + + + diff --git a/library/src/main/res/drawable/progress_drawable.xml b/library/src/main/res/drawable/progress_drawable.xml new file mode 100644 index 00000000..d1097cef --- /dev/null +++ b/library/src/main/res/drawable/progress_drawable.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/selector_dark_theme.xml b/library/src/main/res/drawable/selector_dark_theme.xml new file mode 100644 index 00000000..ff8f2957 --- /dev/null +++ b/library/src/main/res/drawable/selector_dark_theme.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/drawable/selector_light_theme.xml b/library/src/main/res/drawable/selector_light_theme.xml new file mode 100644 index 00000000..bd954554 --- /dev/null +++ b/library/src/main/res/drawable/selector_light_theme.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/awesome_web_view.xml b/library/src/main/res/layout/awesome_web_view.xml new file mode 100644 index 00000000..026f8c57 --- /dev/null +++ b/library/src/main/res/layout/awesome_web_view.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/layout/menus.xml b/library/src/main/res/layout/menus.xml new file mode 100644 index 00000000..3cbadf8e --- /dev/null +++ b/library/src/main/res/layout/menus.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/toolbar_content.xml b/library/src/main/res/layout/toolbar_content.xml new file mode 100644 index 00000000..9f2fc137 --- /dev/null +++ b/library/src/main/res/layout/toolbar_content.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/layout/view_custom_menu.xml b/library/src/main/res/layout/view_custom_menu.xml new file mode 100644 index 00000000..ac535141 --- /dev/null +++ b/library/src/main/res/layout/view_custom_menu.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/library/src/main/res/layout/view_loading_video.xml b/library/src/main/res/layout/view_loading_video.xml new file mode 100644 index 00000000..442f8082 --- /dev/null +++ b/library/src/main/res/layout/view_loading_video.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + diff --git a/library/src/main/res/values-es/strings.xml b/library/src/main/res/values-es/strings.xml new file mode 100644 index 00000000..f308a6dd --- /dev/null +++ b/library/src/main/res/values-es/strings.xml @@ -0,0 +1,9 @@ + + + Recargar + Encontrar + Compartir + Copiar enlace + Abrir con + Copiado al portapapeles + \ No newline at end of file diff --git a/library/src/main/res/values-ko/strings.xml b/library/src/main/res/values-ko/strings.xml new file mode 100644 index 00000000..21299d57 --- /dev/null +++ b/library/src/main/res/values-ko/strings.xml @@ -0,0 +1,9 @@ + + + 새로고침 + 찾기 + 주소 공유하기 + 주소 복사하기 + 다른 앱으로 열기 + 주소가 복사 되었습니다 + \ No newline at end of file diff --git a/library/src/main/res/values-land/dimens.xml b/library/src/main/res/values-land/dimens.xml new file mode 100644 index 00000000..df5957c5 --- /dev/null +++ b/library/src/main/res/values-land/dimens.xml @@ -0,0 +1,5 @@ + + + 48dp + 224dp + \ No newline at end of file diff --git a/library/src/main/res/values-ldrtl/bools.xml b/library/src/main/res/values-ldrtl/bools.xml new file mode 100644 index 00000000..42cdd7b0 --- /dev/null +++ b/library/src/main/res/values-ldrtl/bools.xml @@ -0,0 +1,4 @@ + + + true + \ No newline at end of file diff --git a/library/src/main/res/values-pt-rBR/strings.xml b/library/src/main/res/values-pt-rBR/strings.xml new file mode 100644 index 00000000..4f419eb0 --- /dev/null +++ b/library/src/main/res/values-pt-rBR/strings.xml @@ -0,0 +1,9 @@ + + + Atualizar + Procurar + Compartilhar + Copiar link + Abrir com + Copiado para a área de transferência + diff --git a/library/src/main/res/values/bools.xml b/library/src/main/res/values/bools.xml new file mode 100644 index 00000000..86ac49be --- /dev/null +++ b/library/src/main/res/values/bools.xml @@ -0,0 +1,4 @@ + + + false + \ No newline at end of file diff --git a/library/src/main/res/values/colors.xml b/library/src/main/res/values/colors.xml new file mode 100644 index 00000000..6a701fda --- /dev/null +++ b/library/src/main/res/values/colors.xml @@ -0,0 +1,20 @@ + + + #1AFFFFFF + #33FFFFFF + #4DFFFFFF + + #1AD2D2D2 + #33D2D2D2 + + #0D202020 + #1A202020 + #33202020 + #4D202020 + #66202020 + + #FFFFFF + #000000 + #A5A5A5 + #E7E7E7 + diff --git a/library/src/main/res/values/dimens.xml b/library/src/main/res/values/dimens.xml new file mode 100644 index 00000000..6dc8ffdd --- /dev/null +++ b/library/src/main/res/values/dimens.xml @@ -0,0 +1,17 @@ + + + 56dp + 168dp + + 4dp + 1dp + 14dp + 10dp + 2dp + 4dp + 16dp + 16dp + 32dp + 4dp + 2dp + \ No newline at end of file diff --git a/library/src/main/res/values/strings.xml b/library/src/main/res/values/strings.xml new file mode 100644 index 00000000..0fd3b64a --- /dev/null +++ b/library/src/main/res/values/strings.xml @@ -0,0 +1,14 @@ + + + Refresh + Find + Share via + Copy Link + Open with + Save Photo + Photo has been saved to + Photo save failed + File Chooser + Copied to clipboard + Loading video… + \ No newline at end of file diff --git a/library/src/main/res/values/styles.xml b/library/src/main/res/values/styles.xml new file mode 100644 index 00000000..c2d1d078 --- /dev/null +++ b/library/src/main/res/values/styles.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + diff --git a/library/src/main/res/xml/provider_paths.xml b/library/src/main/res/xml/provider_paths.xml new file mode 100644 index 00000000..791000e4 --- /dev/null +++ b/library/src/main/res/xml/provider_paths.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index b96280e6..bdf93856 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,4 +17,5 @@ dependencyResolutionManagement { } rootProject.name = "LunarLauncher" -include ("app") +include ("app","library","utils") +//annotations diff --git a/utils/.gitignore b/utils/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/utils/.gitignore @@ -0,0 +1 @@ +/build diff --git a/utils/build.gradle b/utils/build.gradle new file mode 100644 index 00000000..f9da1dc8 --- /dev/null +++ b/utils/build.gradle @@ -0,0 +1,46 @@ +apply plugin: 'com.android.library' +//apply plugin: 'com.novoda.bintray-release' + + +android { + namespace "com.thefinestartist.helpers" + compileSdkVersion 34 +// buildToolsVersion '23.0.2' + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + + } + + lintOptions { + abortOnError false + } + + defaultConfig { + minSdkVersion 7 + targetSdkVersion 33 + versionCode 1 + versionName '0.9.5' + vectorDrawables.useSupportLibrary = true + } +} + +dependencies { + +// compile "com.thefinestartist:annotations:${rootProject.ext.versionName}" + implementation "com.android.support:appcompat-v7:28.0.0" + implementation "com.android.support:support-annotations:28.0.0" + implementation 'androidx.annotation:annotation-jvm:1.9.1' +// implementation project(':annotations') +// testCompile 'junit:junit:4.12' +} + +//publish { +// userOrg = 'thefinestartist' +// groupId = 'com.thefinestartist' +// artifactId = 'utils' +// publishVersion = rootProject.ext.versionName +// desc = 'Context free and basic utils to build Android project conveniently.' +// website = 'https://github.com/TheFinestArtist/AndroidBaseUtils' +//} diff --git a/utils/src/androidTest/java/com/thefinestartist/utils/ApplicationTest.java b/utils/src/androidTest/java/com/thefinestartist/utils/ApplicationTest.java new file mode 100644 index 00000000..89235ec2 --- /dev/null +++ b/utils/src/androidTest/java/com/thefinestartist/utils/ApplicationTest.java @@ -0,0 +1,13 @@ +package com.thefinestartist.utils; + +import android.app.Application; +import android.test.ApplicationTestCase; + +/** + * Testing Fundamentals + */ +public class ApplicationTest extends ApplicationTestCase { + public ApplicationTest() { + super(Application.class); + } +} \ No newline at end of file diff --git a/utils/src/androidTest/java/com/thefinestartist/utils/etc/PreferencesUtilTest.java b/utils/src/androidTest/java/com/thefinestartist/utils/etc/PreferencesUtilTest.java new file mode 100755 index 00000000..9cd9921a --- /dev/null +++ b/utils/src/androidTest/java/com/thefinestartist/utils/etc/PreferencesUtilTest.java @@ -0,0 +1,278 @@ +package com.thefinestartist.utils.etc; + +import android.test.AndroidTestCase; +import android.test.suitebuilder.annotation.MediumTest; +import android.test.suitebuilder.annotation.SmallTest; + +import com.thefinestartist.Base; +import com.thefinestartist.utils.preferences.PreferencesUtil; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Set; + +/** + * Tests of the {@link PreferencesUtil} class. + * + * @author Robin Gustafsson + */ +public class PreferencesUtilTest extends AndroidTestCase { + + @Override + public void setUp() throws Exception { + super.setUp(); + Base.initialize(getContext()); + } + + @SmallTest + public void testSetGetDefaultName() { + final String expected = "TEST_DEFAULT_NAME"; + + PreferencesUtil.setDefaultName(expected); + String actual = PreferencesUtil.getDefaultName(); + assertEquals(expected, actual); + } + + @SmallTest + public void testDifferentNames() { + final String name1 = "TEST_DIFFERENTNAMES_NAME1"; + final String name2 = "TEST_DIFFERENTNAMES_NAME2"; + final String key = "TEST_DIFFERENTNAMES_KEY"; + final boolean value = true; + final boolean expected = false; + + PreferencesUtil.put(name1, key, value); + boolean actual = PreferencesUtil.get(name2, key, expected); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreBoolean() { + final String key = "TEST_BOOLEAN"; + final boolean expected = true; + final boolean defValue = false; + + PreferencesUtil.put(key, expected); + boolean actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreBooleanNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_BOOLEAN"; + final boolean expected = true; + final boolean defValue = false; + + PreferencesUtil.put(name, key, expected); + boolean actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreInt() { + final String key = "TEST_INT"; + final int expected = 321; + final int defValue = 0; + + PreferencesUtil.put(key, expected); + int actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreIntNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_INT"; + final int expected = 321; + final int defValue = 0; + + PreferencesUtil.put(name, key, expected); + int actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreFloat() { + final String key = "TEST_FLOAT"; + final float expected = 12.3f; + final float defValue = 0.0f; + + PreferencesUtil.put(key, expected); + float actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreFloatNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_FLOAT"; + final float expected = 12.3f; + final float defValue = 0.0f; + + PreferencesUtil.put(name, key, expected); + float actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreLong() { + final String key = "TEST_LONG"; + final long expected = 321L; + final long defValue = 0L; + + PreferencesUtil.put(key, expected); + long actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreLongNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_LONG"; + final long expected = 321L; + final long defValue = 0L; + + PreferencesUtil.put(name, key, expected); + long actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreString() { + final String key = "TEST_STRING"; + final String expected = "Lorem ipsum"; + final String defValue = null; + + PreferencesUtil.put(key, expected); + String actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreStringNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_STRING"; + final String expected = "Lorem ipsum"; + final String defValue = null; + + PreferencesUtil.put(name, key, expected); + String actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreStringSet() { + final String key = "TEST_STRINGSET"; + final Set expected = new HashSet<>(); + expected.add("Lorem ipsum"); + expected.add("dolor sit amet"); + expected.add("consectetur adipiscing elit"); + final Set defValue = null; + + PreferencesUtil.put(key, expected); + Set actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testStoreStringSetNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_STRINGSET"; + final Set expected = new HashSet<>(); + expected.add("Lorem ipsum"); + expected.add("dolor sit amet"); + expected.add("consectetur adipiscing elit"); + final Set defValue = null; + + PreferencesUtil.put(name, key, expected); + Set actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @MediumTest + public void testStoreSerializable() { + final String key = "TEST_SERIALIZABLE"; + final ArrayList expected = new ArrayList<>(); + expected.add("Lorem ipsum"); + expected.add("dolor sit amet"); + expected.add("consectetur adipiscing elit"); + final ArrayList defValue = new ArrayList<>(); + defValue.add("Proin mollis dictum"); + + PreferencesUtil.put(key, expected); + ArrayList actual = PreferencesUtil.get(key, defValue); + assertEquals(expected, actual); + } + + @MediumTest + public void testStoreSerializableNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_SERIALIZABLE"; + final ArrayList expected = new ArrayList<>(); + expected.add("Lorem ipsum"); + expected.add("dolor sit amet"); + expected.add("consectetur adipiscing elit"); + final ArrayList defValue = new ArrayList<>(); + defValue.add("Proin mollis dictum"); + + PreferencesUtil.put(name, key, expected); + ArrayList actual = PreferencesUtil.get(name, key, defValue); + assertEquals(expected, actual); + } + + @SmallTest + public void testRemove() { + final String key = "TEST_REMOVE"; + final String expected = null; + + PreferencesUtil.put(key, "Lorem ipsum"); + PreferencesUtil.remove(key); + String actual = PreferencesUtil.get(key, expected); + assertEquals(expected, actual); + } + + @SmallTest + public void testRemoveNamed() { + final String name = "TEST_NAMED"; + final String key = "TEST_REMOVE"; + final String expected = null; + + PreferencesUtil.put(name, key, "Lorem ipsum"); + PreferencesUtil.remove(name, key); + String actual = PreferencesUtil.get(name, key, expected); + assertEquals(expected, actual); + } + + @SmallTest + public void testClear() { + final String[] keys = {"TEST_REMOVE_1", "TEST_REMOVE_2", "TEST_REMOVE_2"}; + final String expected = null; + + for (String key : keys) { + PreferencesUtil.put(key, "Lorem ipsum"); + } + PreferencesUtil.clear(); + for (String key : keys) { + String actual = PreferencesUtil.get(key, expected); + assertEquals(expected, actual); + } + } + + @SmallTest + public void testClearNamed() { + final String name = "TEST_NAMED"; + final String[] keys = {"TEST_REMOVE_1", "TEST_REMOVE_2", "TEST_REMOVE_2"}; + final String expected = null; + + for (String key : keys) { + PreferencesUtil.put(name, key, "Lorem ipsum"); + } + PreferencesUtil.clear(name); + for (String key : keys) { + String actual = PreferencesUtil.get(name, key, expected); + assertEquals(expected, actual); + } + } + +} diff --git a/utils/src/main/AndroidManifest.xml b/utils/src/main/AndroidManifest.xml new file mode 100644 index 00000000..5618ce25 --- /dev/null +++ b/utils/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/Base.java b/utils/src/main/java/com/thefinestartist/Base.java new file mode 100644 index 00000000..f897b788 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/Base.java @@ -0,0 +1,57 @@ +package com.thefinestartist; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.util.DisplayMetrics; + +import androidx.annotation.NonNull; + +/** + * Base helps to get {@link Context}, {@link Resources}, {@link AssetManager}, {@link Configuration} and {@link DisplayMetrics} in any class. + * + * @author Leonardo Taehwan Kim + */ +public class Base { + + private static Context context; + + public static void initialize(@NonNull Context context) { + Base.context = context; + } + + public static Context getContext() { + synchronized (Base.class) { + if (Base.context == null) + throw new NullPointerException("Call Base.initialize(context) within your Application onCreate() method."); + + return Base.context.getApplicationContext(); + } + } + + public static Resources getResources() { + return Base.getContext().getResources(); + } + + public static Resources.Theme getTheme() { + return Base.getContext().getTheme(); + } + + public static AssetManager getAssets() { + return Base.getContext().getAssets(); + } + + public static Configuration getConfiguration() { + return Base.getResources().getConfiguration(); + } + + public static DisplayMetrics getDisplayMetrics() { + return Base.getResources().getDisplayMetrics(); + } +} +// TODO: Thread safety +// TODO: ripple, bitmap, time, contact list, picture list, video list, connectivity, wake lock, screen lock/off/on, get attributes, cookie, audio +// TODO: keystore +// TODO: http://jo.centis1504.net/?p=1189 +// TODO: Test codes \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/annotations/Extra.java b/utils/src/main/java/com/thefinestartist/annotations/Extra.java new file mode 100644 index 00000000..e7a510a3 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/annotations/Extra.java @@ -0,0 +1,19 @@ +package com.thefinestartist.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.CLASS; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +/** + * Bind a field to the bundle data for the specific key. + *

+ * {@literal @}Extra(EXTRA_TITLE) String title;
+ * 
+ */ +@Retention(CLASS) +@Target(FIELD) +public @interface Extra { + String value() default ""; +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/binders/ExtrasBinder.java b/utils/src/main/java/com/thefinestartist/binders/ExtrasBinder.java new file mode 100644 index 00000000..0ba8d77f --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/binders/ExtrasBinder.java @@ -0,0 +1,56 @@ +package com.thefinestartist.binders; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; + + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; + +import java.lang.reflect.Method; + +/** + * ExtrasBinder binds data from {@link Intent} or {@link Bundle} to matching variable. + * + * @author Leonardo Taehwan Kim + */ +public class ExtrasBinder { + + static final String SUFFIX = "$$ExtraBinder"; + + public static void bind(Activity activity) { + if (activity == null) + return; + + bindObject(activity); + } + + public static void bind(Fragment fragment) { + if (fragment == null) + return; + + bindObject(fragment); + } + + public static void bind(android.app.Fragment fragment) { + if (fragment == null) + return; + + bindObject(fragment); + } + + private static void bindObject(@NonNull Object object) { + try { + Class binder = Class.forName(object.getClass().getName() + SUFFIX); + Method bind = binder.getMethod("bind", object.getClass()); + bind.invoke(null, object); + } catch (ClassNotFoundException e) { + e.printStackTrace(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("Unable to bind extras for " + object, e); + } + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/builders/ActivityBuilder.java b/utils/src/main/java/com/thefinestartist/builders/ActivityBuilder.java new file mode 100644 index 00000000..5aa98179 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/builders/ActivityBuilder.java @@ -0,0 +1,82 @@ +package com.thefinestartist.builders; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.thefinestartist.Base; +import com.thefinestartist.utils.content.ContextUtil; + +import java.io.Serializable; +import java.util.ArrayList; + +/** + * ActivityBuilder helps to build {@link Activity} {@link Intent} and start {@link Activity}. + * + * @author Leonardo Taehwan Kim + */ +public class ActivityBuilder { + + final Intent intent; + + public ActivityBuilder(@NonNull Class clazz) { + intent = new Intent(Base.getContext(), clazz); + } + + public ActivityBuilder set(@NonNull String key, T value) { + intent.putExtra(key, value); + return this; + } + + public ActivityBuilder set(@NonNull String key, Parcelable value) { + intent.putExtra(key, value); + return this; + } + + public ActivityBuilder set(@NonNull String key, Parcelable[] value) { + intent.putExtra(key, value); + return this; + } + + public ActivityBuilder set(@NonNull String key, ArrayList value) { + intent.putExtra(key, value); + return this; + } + + public ActivityBuilder remove(@NonNull String key) { + intent.removeExtra(key); + return this; + } + + public ActivityBuilder setFlags(int flags) { + intent.setFlags(flags); + return this; + } + + public ActivityBuilder addFlags(int flags) { + intent.addFlags(flags); + return this; + } + + public Intent buildIntent() { + return intent; + } + + public void start() { + ContextUtil.startActivity(intent); + } + + public void startForResult(@NonNull Activity activity, int requestCode) { + activity.startActivityForResult(intent, requestCode); + } + + @TargetApi(16) + public void startForResult(@NonNull Activity activity, int requestCode, @Nullable Bundle options) { + activity.startActivityForResult(intent, requestCode, options); + } +} diff --git a/utils/src/main/java/com/thefinestartist/builders/BundleBuilder.java b/utils/src/main/java/com/thefinestartist/builders/BundleBuilder.java new file mode 100644 index 00000000..fab1c707 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/builders/BundleBuilder.java @@ -0,0 +1,34 @@ +package com.thefinestartist.builders; + +import android.os.Bundle; +import android.os.Parcelable; + +import java.io.Serializable; + +/** + * BundleBuilder helps to build {@link Bundle} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class BundleBuilder { + + final Bundle bundle = new Bundle(); + + public BundleBuilder set(String key, T value) { + bundle.putSerializable(key, value); + return this; + } + + public BundleBuilder set(String key, Parcelable value) { + bundle.putParcelable(key, value); + return this; + } + + public T get(String key) { + return (T) bundle.getSerializable(key); + } + + public Bundle build() { + return bundle; + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/converters/Unit.java b/utils/src/main/java/com/thefinestartist/converters/Unit.java new file mode 100644 index 00000000..9a1e3d64 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/converters/Unit.java @@ -0,0 +1,9 @@ +package com.thefinestartist.converters; + +/** + * Unit is abbreviation class of {@link UnitConverter}. + * + * @author Leonardo Taehwan Kim + */ +public class Unit extends UnitConverter { +} diff --git a/utils/src/main/java/com/thefinestartist/converters/UnitConverter.java b/utils/src/main/java/com/thefinestartist/converters/UnitConverter.java new file mode 100644 index 00000000..a662034a --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/converters/UnitConverter.java @@ -0,0 +1,43 @@ +package com.thefinestartist.converters; + +import com.thefinestartist.Base; + +/** + * UnitConverter helps to convert dp or sp size into pixel. + * + * @author Leonardo Taehwan Kim + */ +public class UnitConverter { + + public static float dpToPx(float dp) { + return dp * Base.getDisplayMetrics().density; + } + + public static int dpToPx(int dp) { + return (int) (dp * Base.getDisplayMetrics().density + 0.5f); + } + + public static float pxToDp(float px) { + return px / Base.getDisplayMetrics().density; + } + + public static int pxToDp(int px) { + return (int) (px / Base.getDisplayMetrics().density + 0.5f); + } + + public static float spToPx(float sp) { + return sp * Base.getDisplayMetrics().scaledDensity; + } + + public static int spToPx(int sp) { + return (int) (sp * Base.getDisplayMetrics().scaledDensity + 0.5f); + } + + public static float pxToSp(float px) { + return px / Base.getDisplayMetrics().scaledDensity; + } + + public static int pxToSp(int px) { + return (int) (px / Base.getDisplayMetrics().scaledDensity + 0.5f); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/enums/LogLevel.java b/utils/src/main/java/com/thefinestartist/enums/LogLevel.java new file mode 100644 index 00000000..1482c543 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/enums/LogLevel.java @@ -0,0 +1,19 @@ +package com.thefinestartist.enums; + +import com.thefinestartist.utils.log.LogUtil; + +/** + * Enum class associated with {@link LogUtil}. + * + * @author Leonardo Taehwan Kim + */ +public enum LogLevel { + FULL, + VERBOSE, + DEBUG, + INFO, + WARN, + ERROR, + ASSERT, + NONE +} diff --git a/utils/src/main/java/com/thefinestartist/enums/Rotation.java b/utils/src/main/java/com/thefinestartist/enums/Rotation.java new file mode 100644 index 00000000..fbed6053 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/enums/Rotation.java @@ -0,0 +1,29 @@ +package com.thefinestartist.enums; + +import com.thefinestartist.utils.ui.DisplayUtil; + +/** + * Enum class associated with {@link DisplayUtil}. + * + * @author Leonardo Taehwan Kim + */ +public enum Rotation { + DEGREES_0(0), + DEGREES_90(1), + DEGREES_180(2), + DEGREES_270(3); + + int value; + + Rotation(int value) { + this.value = value; + } + + public static Rotation fromValue(int value) { + for (Rotation rotation : values()) + if (rotation.value == value) + return rotation; + + return DEGREES_0; + } +} diff --git a/utils/src/main/java/com/thefinestartist/listeners/KeyboardStateListener.java b/utils/src/main/java/com/thefinestartist/listeners/KeyboardStateListener.java new file mode 100644 index 00000000..bc0cc691 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/listeners/KeyboardStateListener.java @@ -0,0 +1,14 @@ +package com.thefinestartist.listeners; + +import com.thefinestartist.utils.ui.KeyboardUtil; + +/** + * Listener class associated with {@link KeyboardUtil}. + * + * @author Leonardo Taehwan Kim + */ +public abstract class KeyboardStateListener { + + public void onStateChanged(int keyboardHeight, boolean opened) { + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/content/ContextUtil.java b/utils/src/main/java/com/thefinestartist/utils/content/ContextUtil.java new file mode 100644 index 00000000..7069e2e6 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/content/ContextUtil.java @@ -0,0 +1,502 @@ +package com.thefinestartist.utils.content; + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.ComponentCallbacks; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.IntentSender; +import android.content.ServiceConnection; +import android.content.SharedPreferences; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.database.DatabaseErrorHandler; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.util.AttributeSet; + +import androidx.annotation.AttrRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.annotation.StyleRes; +import androidx.annotation.StyleableRes; +import androidx.core.content.ContextCompat; + +import com.thefinestartist.Base; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * ContextUtil helps to manage {@link Context} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ContextUtil { + + public static boolean bindService(Intent service, ServiceConnection conn, int flags) { + return Base.getContext().bindService(service, conn, flags); + } + + public static int checkCallingOrSelfPermission(String permission) { + return Base.getContext().checkCallingOrSelfPermission(permission); + } + + public static int checkCallingOrSelfUriPermission(Uri uri, int modeFlags) { + return Base.getContext().checkCallingOrSelfUriPermission(uri, modeFlags); + } + + public static int checkCallingPermission(String permission) { + return Base.getContext().checkCallingPermission(permission); + } + + public static int checkCallingUriPermission(Uri uri, int modeFlags) { + return Base.getContext().checkCallingUriPermission(uri, modeFlags); + } + + public static int checkPermission(String permission, int pid, int uid) { + return Base.getContext().checkPermission(permission, pid, uid); + } + + public static int checkSelfPermission(@NonNull String permission) { + return ContextCompat.checkSelfPermission(Base.getContext(), permission); + } + + public static int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { + return Base.getContext().checkUriPermission(uri, pid, uid, modeFlags); + } + + public static int checkUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags) { + return Base.getContext().checkUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags); + } + + public static Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException { + return Base.getContext().createPackageContext(packageName, flags); + } + + public static String[] databaseList() { + return Base.getContext().databaseList(); + } + + public static boolean deleteDatabase(String name) { + return Base.getContext().deleteDatabase(name); + } + + public static boolean deleteFile(String name) { + return Base.getContext().deleteFile(name); + } + + public static void enforceCallingOrSelfPermission(String permission, String message) { + Base.getContext().enforceCallingOrSelfPermission(permission, message); + } + + public static void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message) { + Base.getContext().enforceCallingOrSelfUriPermission(uri, modeFlags, message); + } + + public static void enforceCallingPermission(String permission, String message) { + Base.getContext().enforceCallingPermission(permission, message); + } + + public static void enforceCallingUriPermission(Uri uri, int modeFlags, String message) { + Base.getContext().enforceCallingUriPermission(uri, modeFlags, message); + } + + public static void enforcePermission(String permission, int pid, int uid, String message) { + Base.getContext().enforcePermission(permission, pid, uid, message); + } + + public static void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message) { + Base.getContext().enforceUriPermission(uri, pid, uid, modeFlags, message); + } + + public static void enforceUriPermission(Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message) { + Base.getContext().enforceUriPermission(uri, readPermission, writePermission, pid, uid, modeFlags, message); + } + + public static String[] fileList() { + return Base.getContext().fileList(); + } + + public static Context getApplicationContext() { + return Base.getContext().getApplicationContext(); + } + + public static ApplicationInfo getApplicationInfo() { + return Base.getContext().getApplicationInfo(); + } + + public static AssetManager getAssets() { + return Base.getContext().getAssets(); + } + + public static File getCacheDir() { + return Base.getContext().getCacheDir(); + } + + public static ClassLoader getClassLoader() { + return Base.getContext().getClassLoader(); + } + + public static File getCodeCacheDir() { + return ContextCompat.getCodeCacheDir(Base.getContext()); + } + + @ColorInt + public static int getColor(@ColorRes int colorRes) { + return ContextCompat.getColor(Base.getContext(), colorRes); + } + + public static ColorStateList getColorStateList(@ColorRes int colorRes) { + return ContextCompat.getColorStateList(Base.getContext(), colorRes); + } + + public static ContentResolver getContentResolver() { + return Base.getContext().getContentResolver(); + } + + public static File getDatabasePath(String name) { + return Base.getContext().getDatabasePath(name); + } + + public static File getDir(String name, int mode) { + return Base.getContext().getDir(name, mode); + } + + public static Drawable getDrawable(@DrawableRes int drawableRes) { + return ContextCompat.getDrawable(Base.getContext(), drawableRes); + } + + @Nullable + @TargetApi(8) + public static File getExternalCacheDir() { + return Base.getContext().getExternalCacheDir(); + } + + public static File[] getExternalCacheDirs() { + return ContextCompat.getExternalCacheDirs(Base.getContext()); + } + + @Nullable + @TargetApi(8) + public static File getExternalFilesDir(String type) { + return Base.getContext().getExternalFilesDir(type); + } + + public static File[] getExternalFilesDirs(String type) { + return ContextCompat.getExternalFilesDirs(Base.getContext(), type); + } + + @TargetApi(21) + public static File[] getExternalMediaDirs() { + return Base.getContext().getExternalMediaDirs(); + } + + public static File getFileStreamPath(String name) { + return Base.getContext().getFileStreamPath(name); + } + + public static File getFilesDir() { + return Base.getContext().getFilesDir(); + } + + public static Looper getMainLooper() { + return Base.getContext().getMainLooper(); + } + + public static File getNoBackupFilesDir() { + return ContextCompat.getNoBackupFilesDir(Base.getContext()); + } + + @TargetApi(11) + public static File getObbDir() { + return Base.getContext().getObbDir(); + } + + public static File[] getObbDirs() { + return ContextCompat.getObbDirs(Base.getContext()); + } + + @TargetApi(8) + public static String getPackageCodePath() { + return Base.getContext().getPackageCodePath(); + } + + public static PackageManager getPackageManager() { + return Base.getContext().getPackageManager(); + } + + public static String getPackageName() { + return Base.getContext().getPackageName(); + } + + @TargetApi(8) + public static String getPackageResourcePath() { + return Base.getContext().getPackageResourcePath(); + } + + public static Resources getResources() { + return Base.getContext().getResources(); + } + + public static SharedPreferences getSharedPreferences(String name, int mode) { + return Base.getContext().getSharedPreferences(name, mode); + } + + public static String getString(@StringRes int stringRes) { + return Base.getContext().getString(stringRes); + } + + public static String getString(@StringRes int stringRes, Object... formatArgs) { + return Base.getContext().getString(stringRes, formatArgs); + } + + @TargetApi(23) + public static T getSystemService(Class serviceClass) { + return Base.getContext().getSystemService(serviceClass); + } + + public static Object getSystemService(String name) { + return Base.getContext().getSystemService(name); + } + + @TargetApi(23) + public static String getSystemServiceName(Class serviceClass) { + return Base.getContext().getSystemServiceName(serviceClass); + } + + public static CharSequence getText(@StringRes int stringRes) { + return Base.getContext().getText(stringRes); + } + + public static Resources.Theme getTheme() { + return Base.getContext().getTheme(); + } + + public static Drawable getWallpaper() { + return WallpaperManager.getInstance(Base.getContext()).getDrawable(); + } + + public static int getWallpaperDesiredMinimumHeight() { + return WallpaperManager.getInstance(Base.getContext()).getDesiredMinimumHeight(); + } + + public static int getWallpaperDesiredMinimumWidth() { + return WallpaperManager.getInstance(Base.getContext()).getDesiredMinimumWidth(); + } + + public static void grantUriPermission(String toPackage, Uri uri, int modeFlags) { + Base.getContext().grantUriPermission(toPackage, uri, modeFlags); + } + + public static boolean isRestricted() { + return Base.getContext().isRestricted(); + } + + public static TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { + return Base.getContext().obtainStyledAttributes(attrs); + } + + public static TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + return Base.getContext().obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes); + } + + public static TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs) { + return Base.getContext().obtainStyledAttributes(set, attrs); + } + + public static TypedArray obtainStyledAttributes(@StyleRes int resid, @StyleableRes int[] attrs) { + return Base.getContext().obtainStyledAttributes(resid, attrs); + } + + public static FileInputStream openFileInput(String name) throws FileNotFoundException { + return Base.getContext().openFileInput(name); + } + + public static FileOutputStream openFileOutput(String name, int mode) throws FileNotFoundException { + return Base.getContext().openFileOutput(name, mode); + } + + public static SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory) { + return Base.getContext().openOrCreateDatabase(name, mode, factory); + } + + @TargetApi(11) + public static SQLiteDatabase openOrCreateDatabase(String name, int mode, SQLiteDatabase.CursorFactory factory, DatabaseErrorHandler errorHandler) { + return Base.getContext().openOrCreateDatabase(name, mode, factory, errorHandler); + } + + public static Drawable peekWallpaper() { + return WallpaperManager.getInstance(Base.getContext()).peekDrawable(); + } + + @TargetApi(14) + public static void registerComponentCallbacks(ComponentCallbacks callback) { + Base.getContext().registerComponentCallbacks(callback); + } + + public static Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) { + return Base.getContext().registerReceiver(receiver, filter); + } + + public static Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter, String broadcastPermission, Handler scheduler) { + return Base.getContext().registerReceiver(receiver, filter, broadcastPermission, scheduler); + } + +// public static void removeStickyBroadcast(Intent intent) { +// Base.getContext().removeStickyBroadcast(intent); +// } +// +// @TargetApi(17) +// public static void removeStickyBroadcastAsUser(Intent intent, UserHandle user) { +// Base.getContext().removeStickyBroadcastAsUser(intent, user); +// } + + public static void revokeUriPermission(Uri uri, int modeFlags) { + Base.getContext().revokeUriPermission(uri, modeFlags); + } + + public static void sendBroadcast(Intent intent, String receiverPermission) { + Base.getContext().sendBroadcast(intent, receiverPermission); + } + + public static void sendBroadcast(Intent intent) { + Base.getContext().sendBroadcast(intent); + } + + @TargetApi(17) + public static void sendBroadcastAsUser(Intent intent, UserHandle user) { + Base.getContext().sendBroadcastAsUser(intent, user); + } + + @TargetApi(17) + public static void sendBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission) { + Base.getContext().sendBroadcastAsUser(intent, user, receiverPermission); + } + + public static void sendOrderedBroadcast(Intent intent, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + Base.getContext().sendOrderedBroadcast(intent, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras); + } + + public static void sendOrderedBroadcast(Intent intent, String receiverPermission) { + Base.getContext().sendOrderedBroadcast(intent, receiverPermission); + } + + @TargetApi(17) + public static void sendOrderedBroadcastAsUser(Intent intent, UserHandle user, String receiverPermission, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { + Base.getContext().sendOrderedBroadcastAsUser(intent, user, receiverPermission, resultReceiver, scheduler, initialCode, initialData, initialExtras); + } + +// public static void sendStickyBroadcast(Intent intent) { +// Base.getContext().sendStickyBroadcast(intent); +// } +// +// @TargetApi(17) +// public static void sendStickyBroadcastAsUser(Intent intent, UserHandle user) { +// Base.getContext().sendStickyBroadcastAsUser(intent, user); +// } +// +// public static void sendStickyOrderedBroadcast(Intent intent, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { +// Base.getContext().sendStickyOrderedBroadcast(intent, resultReceiver, scheduler, initialCode, initialData, initialExtras); +// } +// +// @TargetApi(17) +// public static void sendStickyOrderedBroadcastAsUser(Intent intent, UserHandle user, BroadcastReceiver resultReceiver, Handler scheduler, int initialCode, String initialData, Bundle initialExtras) { +// Base.getContext().sendStickyOrderedBroadcastAsUser(intent, user, resultReceiver, scheduler, initialCode, initialData, initialExtras); +// } + + public static void setTheme(@StyleRes int styleRes) { + Base.getContext().setTheme(styleRes); + } + + @SuppressLint("MissingPermission") + public static void setWallpaper(InputStream data) throws IOException { + WallpaperManager.getInstance(Base.getContext()).setStream(data); + } + + @SuppressLint("MissingPermission") + public static void setWallpaper(Bitmap bitmap) throws IOException { + WallpaperManager.getInstance(Base.getContext()).setBitmap(bitmap); + } + + public static boolean startActivities(Intent[] intents, Bundle options) { + for (Intent intent : intents) + if (intent != null) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return ContextCompat.startActivities(Base.getContext(), intents, options); + } + + public static boolean startActivities(Intent[] intents) { + for (Intent intent : intents) + if (intent != null) + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + return ContextCompat.startActivities(Base.getContext(), intents); + } + + public static void startActivity(@NonNull Intent intent) { + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Base.getContext().startActivity(intent); + } + + @TargetApi(16) + public static void startActivity(Intent intent, Bundle options) { + Base.getContext().startActivity(intent, options); + } + + public static boolean startInstrumentation(ComponentName className, String profileFile, Bundle arguments) { + return Base.getContext().startInstrumentation(className, profileFile, arguments); + } + + @TargetApi(16) + public static void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) throws IntentSender.SendIntentException { + Base.getContext().startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags, options); + } + + public static void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { + Base.getContext().startIntentSender(intent, fillInIntent, flagsMask, flagsValues, extraFlags); + } + + public static ComponentName startService(Intent service) { + return Base.getContext().startService(service); + } + + public static boolean stopService(Intent service) { + return Base.getContext().stopService(service); + } + + public static void unbindService(ServiceConnection conn) { + Base.getContext().unbindService(conn); + } + + @TargetApi(14) + public static void unregisterComponentCallbacks(ComponentCallbacks callback) { + Base.getContext().unregisterComponentCallbacks(callback); + } + + public static void unregisterReceiver(BroadcastReceiver receiver) { + Base.getContext().unregisterReceiver(receiver); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/content/Ctx.java b/utils/src/main/java/com/thefinestartist/utils/content/Ctx.java new file mode 100644 index 00000000..5db6c951 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/content/Ctx.java @@ -0,0 +1,9 @@ +package com.thefinestartist.utils.content; + +/** + * Ctx is abbreviation class of {@link ContextUtil}. + * + * @author Leonardo Taehwan Kim + */ +public class Ctx extends ContextUtil { +} diff --git a/utils/src/main/java/com/thefinestartist/utils/content/Res.java b/utils/src/main/java/com/thefinestartist/utils/content/Res.java new file mode 100644 index 00000000..a4492cb7 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/content/Res.java @@ -0,0 +1,9 @@ +package com.thefinestartist.utils.content; + +/** + * Res is abbreviation class of {@link ResourcesUtil}. + * + * @author Leonardo Taehwan Kim + */ +public class Res extends ResourcesUtil { +} diff --git a/utils/src/main/java/com/thefinestartist/utils/content/ResourcesUtil.java b/utils/src/main/java/com/thefinestartist/utils/content/ResourcesUtil.java new file mode 100644 index 00000000..de61235d --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/content/ResourcesUtil.java @@ -0,0 +1,278 @@ +package com.thefinestartist.utils.content; + +import android.content.res.AssetFileDescriptor; +import android.content.res.AssetManager; +import android.content.res.ColorStateList; +import android.content.res.Configuration; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.Movie; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.TypedValue; + +import androidx.annotation.AnimRes; +import androidx.annotation.AnyRes; +import androidx.annotation.ArrayRes; +import androidx.annotation.BoolRes; +import androidx.annotation.ColorInt; +import androidx.annotation.ColorRes; +import androidx.annotation.DimenRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.IntegerRes; +import androidx.annotation.LayoutRes; +import androidx.annotation.PluralsRes; +import androidx.annotation.RawRes; +import androidx.annotation.StringRes; +import androidx.annotation.XmlRes; + +import com.thefinestartist.Base; +import com.thefinestartist.utils.etc.APILevel; + +import org.xmlpull.v1.XmlPullParserException; + +import java.io.IOException; +import java.io.InputStream; + +/** + * ResourcesUtil helps to manage {@link Resources} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ResourcesUtil { + + private static void finishPreloading() { + Base.getResources().finishPreloading(); + } + + private static void flushLayoutCache() { + Base.getResources().flushLayoutCache(); + } + + public static XmlResourceParser getAnimation(@AnimRes int animRes) { + return Base.getResources().getAnimation(animRes); + } + + public static AssetManager getAssets() { + return Base.getResources().getAssets(); + } + + public static boolean getBoolean(@BoolRes int boolRes) { + return Base.getResources().getBoolean(boolRes); + } + + @ColorInt + public static int getColor(@ColorRes int colorRes) { + return ContextUtil.getColor(colorRes); + } + + @ColorInt + public static int getColor(@ColorRes int colorRes, Resources.Theme theme) { + if (APILevel.require(23)) + return Base.getResources().getColor(colorRes, theme); + else + return getColor(colorRes); + } + + public static ColorStateList getColorStateList(@ColorRes int colorRes) { + return ContextUtil.getColorStateList(colorRes); + } + + public static ColorStateList getColorStateList(@ColorRes int colorRes, Resources.Theme theme) { + if (APILevel.require(23)) + return Base.getResources().getColorStateList(colorRes, theme); + else + return getColorStateList(colorRes); + } + + public static Configuration getConfiguration() { + return Base.getConfiguration(); + } + + public static float getDimension(@DimenRes int dimenRes) { + return Base.getResources().getDimension(dimenRes); + } + + public static int getDimensionPixelOffset(@DimenRes int dimenRes) { + return Base.getResources().getDimensionPixelOffset(dimenRes); + } + + public static int getDimensionPixelSize(@DimenRes int dimenRes) { + return Base.getResources().getDimensionPixelSize(dimenRes); + } + + public static DisplayMetrics getDisplayMetrics() { + return Base.getDisplayMetrics(); + } + + public static Drawable getDrawable(@DrawableRes int drawableRes) { + return ContextUtil.getDrawable(drawableRes); + } + + public static Drawable getDrawable(@DrawableRes int drawableRes, Resources.Theme theme) { + if (APILevel.require(21)) + return Base.getResources().getDrawable(drawableRes, theme); + else + return Base.getResources().getDrawable(drawableRes); + } + + public static Drawable getDrawableForDensity(@DrawableRes int drawableRes, int density) { + if (APILevel.require(21)) + return Base.getResources().getDrawableForDensity(drawableRes, density, Base.getContext().getTheme()); + else if (APILevel.require(15)) + return Base.getResources().getDrawableForDensity(drawableRes, density); + else + return Base.getResources().getDrawable(drawableRes); + } + + public static float getFraction(int id, int base, int pbase) { + return Base.getResources().getFraction(id, base, pbase); + } + + public static int getIdentifier(String name, String defType, String defPackage) { + return Base.getResources().getIdentifier(name, defType, defPackage); + } + + public static int[] getIntArray(@ArrayRes int arrayRes) { + return Base.getResources().getIntArray(arrayRes); + } + + public static int getInteger(@IntegerRes int integerRes) { + return Base.getResources().getInteger(integerRes); + } + + public static XmlResourceParser getLayout(@LayoutRes int layoutRes) { + return Base.getResources().getLayout(layoutRes); + } + + public static Movie getMovie(@RawRes int rawRes) { + return Base.getResources().getMovie(rawRes); + } + + public static String getQuantityString(int id, int quantity, Object... formatArgs) { + return Base.getResources().getQuantityString(id, quantity, formatArgs); + } + + public static String getQuantityString(@PluralsRes int pluralsRes, int quantity) throws Resources.NotFoundException { + return Base.getResources().getQuantityString(pluralsRes, quantity); + } + + public static CharSequence getQuantityText(int id, int quantity) { + return Base.getResources().getQuantityText(id, quantity); + } + + public static String getResourceEntryName(@AnyRes int anyRes) { + return Base.getResources().getResourceEntryName(anyRes); + } + + public static String getResourceName(@AnyRes int anyRes) { + return Base.getResources().getResourceName(anyRes); + } + + public static String getResourcePackageName(@AnyRes int anyRes) { + return Base.getResources().getResourcePackageName(anyRes); + } + + public static String getResourceTypeName(@AnyRes int anyRes) { + return Base.getResources().getResourceTypeName(anyRes); + } + + public static String getString(@StringRes int stringRes) { + return Base.getResources().getString(stringRes); + } + + public static String getString(@StringRes int stringRes, Object... formatArgs) { + return Base.getResources().getString(stringRes, formatArgs); + } + + public static String[] getStringArray(@ArrayRes int arrayRes) { + return Base.getResources().getStringArray(arrayRes); + } + + public static Resources getSystem() { + return Base.getResources().getSystem(); + } + + public static CharSequence getText(@StringRes int stringRes, CharSequence def) { + return Base.getResources().getText(stringRes, def); + } + + public static CharSequence getText(@StringRes int stringRes) { + return Base.getResources().getText(stringRes); + } + + public static CharSequence[] getTextArray(@ArrayRes int arrayRes) { + return Base.getResources().getTextArray(arrayRes); + } + + public static void getValue(String name, TypedValue outValue, boolean resolveRefs) { + Base.getResources().getValue(name, outValue, resolveRefs); + } + + public static void getValue(@AnyRes int anyRes, TypedValue outValue, boolean resolveRefs) { + Base.getResources().getValue(anyRes, outValue, resolveRefs); + } + + public static void getValueForDensity(@AnyRes int anyRes, int density, TypedValue outValue, boolean resolveRefs) { + if (APILevel.require(15)) + Base.getResources().getValueForDensity(anyRes, density, outValue, resolveRefs); + else + Base.getResources().getValue(anyRes, outValue, resolveRefs); + } + + public static XmlResourceParser getXml(@XmlRes int xmlRes) { + return Base.getResources().getXml(xmlRes); + } + + public static Resources.Theme newTheme() { + return Base.getResources().newTheme(); + } + + public static TypedArray obtainAttributes(AttributeSet set, int[] attrs) { + return Base.getResources().obtainAttributes(set, attrs); + } + + public static TypedArray obtainTypedArray(@ArrayRes int anyRes) { + return Base.getResources().obtainTypedArray(anyRes); + } + + public static InputStream openRawResource(@RawRes int rawRes) { + return Base.getResources().openRawResource(rawRes); + } + + public static InputStream openRawResource(@RawRes int rawRes, TypedValue value) { + return Base.getResources().openRawResource(rawRes, value); + } + + public static AssetFileDescriptor openRawResourceFd(@RawRes int rawRes) { + return Base.getResources().openRawResourceFd(rawRes); + } + + public static void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle) throws XmlPullParserException { + Base.getResources().parseBundleExtra(tagName, attrs, outBundle); + } + + public static void parseBundleExtras(XmlResourceParser parser, Bundle outBundle) throws XmlPullParserException, IOException { + Base.getResources().parseBundleExtras(parser, outBundle); + } + + public static void updateConfiguration(Configuration config, DisplayMetrics metrics) { + Base.getResources().updateConfiguration(config, metrics); + } + + // Added methods + public static int[] getColorArray(@ArrayRes int array) { + if (array == 0) + return null; + + TypedArray typedArray = Base.getResources().obtainTypedArray(array); + int[] colors = new int[typedArray.length()]; + for (int i = 0; i < typedArray.length(); i++) + colors[i] = typedArray.getColor(i, 0); + typedArray.recycle(); + return colors; + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/content/ThemeUtil.java b/utils/src/main/java/com/thefinestartist/utils/content/ThemeUtil.java new file mode 100644 index 00000000..c2393019 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/content/ThemeUtil.java @@ -0,0 +1,64 @@ +package com.thefinestartist.utils.content; + +import android.annotation.TargetApi; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.TypedValue; + +import androidx.annotation.AttrRes; +import androidx.annotation.DrawableRes; +import androidx.annotation.StyleRes; +import androidx.annotation.StyleableRes; + +import com.thefinestartist.Base; + +/** + * ThemeUtil helps to manage {@link Resources.Theme} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ThemeUtil { + + public static void applyStyle(int resId, boolean force) { + Base.getTheme().applyStyle(resId, force); + } + + public static void dump(int priority, String tag, String prefix) { + Base.getTheme().dump(priority, tag, prefix); + } + + @TargetApi(23) + public static int getChangingConfigurations() { + return Base.getTheme().getChangingConfigurations(); + } + + public static Drawable getDrawable(@DrawableRes int drawableRes) { + return ResourcesUtil.getDrawable(drawableRes); + } + + public static Resources getResources() { + return Base.getResources(); + } + + public static TypedArray obtainStyledAttributes(@StyleableRes int[] attrs) { + return Base.getTheme().obtainStyledAttributes(attrs); + } + + public static TypedArray obtainStyledAttributes(@StyleRes int resid, @StyleableRes int[] attrs) { + return Base.getTheme().obtainStyledAttributes(resid, attrs); + } + + public static TypedArray obtainStyledAttributes(AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { + return Base.getTheme().obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes); + } + + public static boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { + return Base.getTheme().resolveAttribute(resid, outValue, resolveRefs); + } + + public static void setTo(Resources.Theme other) { + Base.getTheme().setTo(other); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/content/TypedValueUtil.java b/utils/src/main/java/com/thefinestartist/utils/content/TypedValueUtil.java new file mode 100644 index 00000000..1a227730 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/content/TypedValueUtil.java @@ -0,0 +1,29 @@ +package com.thefinestartist.utils.content; + +import android.util.TypedValue; + +import com.thefinestartist.Base; + +/** + * TypedValueUtil helps to manage {@link TypedValue} class conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class TypedValueUtil { + + public static float applyDimension(int unit, float value) { + return TypedValue.applyDimension(unit, value, Base.getDisplayMetrics()); + } + + public static float complexToDimension(int data) { + return TypedValue.complexToDimension(data, Base.getDisplayMetrics()); + } + + public static int complexToDimensionPixelOffset(int data) { + return TypedValue.complexToDimensionPixelOffset(data, Base.getDisplayMetrics()); + } + + public static int complexToDimensionPixelSize(int data) { + return TypedValue.complexToDimensionPixelSize(data, Base.getDisplayMetrics()); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/etc/APILevel.java b/utils/src/main/java/com/thefinestartist/utils/etc/APILevel.java new file mode 100644 index 00000000..0c872fb8 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/etc/APILevel.java @@ -0,0 +1,237 @@ +package com.thefinestartist.utils.etc; + +import android.os.Build; + +/** + * APILevel helps to check device API {@link android.os.Build.VERSION} conveniently. + * + * @author Marcos Trujillo, Leonardo Taehwan Kim + */ +public class APILevel { + + /** + * @param level minimum API level version that has to support the device + * @return true when the caller API version is at least level + */ + public static boolean require(int level) { + return Build.VERSION.SDK_INT >= level; + } + + /** + * @return true when the caller API version is at least Cupcake 3 + */ + public static boolean requireCupcake() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE; + } + + /** + * @return true when the caller API version is at least Donut 4 + */ + public static boolean requireDonut() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT; + } + + /** + * @return true when the caller API version is at least Eclair 5 + */ + public static boolean requireEclair() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ECLAIR; + } + + /** + * @return true when the caller API version is at least Froyo 8 + */ + public static boolean requireFroyo() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO; + } + + /** + * @return true when the caller API version is at least GingerBread 9 + */ + public static boolean requireGingerbread() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD; + } + + /** + * @return true when the caller API version is at least Honeycomb 11 + */ + public static boolean requireHoneycomb() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB; + } + + /** + * @return true when the caller API version is at least Honeycomb 3.2, 13 + */ + public static boolean requireHoneycombMR2() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2; + } + + /** + * @return true when the caller API version is at least ICS 14 + */ + public static boolean requireIceCreamSandwich() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + /** + * @return true when the caller API version is at least JellyBean 16 + */ + public static boolean requireJellyBean() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; + } + + /** + * @return true when the caller API version is at least JellyBean MR1 17 + */ + public static boolean requireJellyBeanMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1; + } + + /** + * @return true when the caller API version is at least JellyBean MR2 18 + */ + public static boolean requireJellyBeanMR2() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2; + } + + /** + * @return true when the caller API version is at least Kitkat 19 + */ + public static boolean requireKitkat() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } + + /** + * @return true when the caller API version is at least Lollipop 21 + */ + public static boolean requireLollipop() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP; + } + + /** + * @return true when the caller API version is at least Lollipop MR1 22 + */ + public static boolean requireLollipopMR1() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1; + } + + /** + * @return true when the caller API version is at least Marshmallow 23 + */ + public static boolean requireMarshmallow() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M; + } + + /** + * @param level API level version that specific method or variable has been deprecated + * @return true when the caller API version is less than level + */ + public static boolean deprecatedAt(int level) { + return Build.VERSION.SDK_INT < level; + } + + /** + * @return true when the caller API version is less than Cupcake 3 + */ + public static boolean deprecatedAtCupcake() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.CUPCAKE; + } + + /** + * @return true when the caller API version is less than Donut 4 + */ + public static boolean deprecatedAtDonut() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.DONUT; + } + + /** + * @return true when the caller API version is less than Eclair 5 + */ + public static boolean deprecatedAtEclair() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.ECLAIR; + } + + /** + * @return true when the caller API version is less than Froyo 8 + */ + public static boolean deprecatedAtFroyo() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO; + } + + /** + * @return true when the caller API version is less than GingerBread 9 + */ + public static boolean deprecatedAtGingerbread() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD; + } + + /** + * @return true when the caller API version is less than Honeycomb 11 + */ + public static boolean deprecatedAtHoneycomb() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB; + } + + /** + * @return true when the caller API version is less than Honeycomb 3.2, 13 + */ + public static boolean deprecatedAtHoneycombMR2() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB_MR2; + } + + /** + * @return true when the caller API version is less than ICS 14 + */ + public static boolean deprecatedAtIceCreamSandwich() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH; + } + + /** + * @return true when the caller API version is less than JellyBean 16 + */ + public static boolean deprecatedAtJellyBean() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN; + } + + /** + * @return true when the caller API version is less than JellyBean MR1 17 + */ + public static boolean deprecatedAtJellyBeanMR1() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1; + } + + /** + * @return true when the caller API version is less than JellyBean MR2 18 + */ + public static boolean deprecatedAtJellyBeanMR2() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2; + } + + /** + * @return true when the caller API version is less than Kitkat 19 + */ + public static boolean deprecatedAtKitkat() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT; + } + + /** + * @return true when the caller API version is less than Lollipop 21 + */ + public static boolean deprecatedAtLollipop() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP; + } + + /** + * @return true when the caller API version is less than Lollipop MR1 22 + */ + public static boolean deprecatedAtLollipopMR1() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1; + } + + /** + * @return true when the caller API version is less than Marshmallow 23 + */ + public static boolean deprecatedAtMarshmallow() { + return Build.VERSION.SDK_INT < Build.VERSION_CODES.M; + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/etc/IntArrayUtil.java b/utils/src/main/java/com/thefinestartist/utils/etc/IntArrayUtil.java new file mode 100755 index 00000000..3a5feff9 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/etc/IntArrayUtil.java @@ -0,0 +1,29 @@ +package com.thefinestartist.utils.etc; + +/** + * IntArrayUtil helps to manage IntArray conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class IntArrayUtil { + + public static boolean contains(int[] array, int value) { + if (array == null) + return false; + + for (int i : array) + if (i == value) + return true; + return false; + } + + public static int[] add(int[] array, int value) { + if (array == null) + return new int[]{value}; + + int[] newArray = new int[array.length + 1]; + System.arraycopy(array, 0, newArray, 0, array.length); + newArray[array.length] = value; + return newArray; + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/etc/PackageUtil.java b/utils/src/main/java/com/thefinestartist/utils/etc/PackageUtil.java new file mode 100644 index 00000000..7ff8539e --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/etc/PackageUtil.java @@ -0,0 +1,58 @@ +package com.thefinestartist.utils.etc; + +import android.content.ActivityNotFoundException; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; + +import com.thefinestartist.Base; + +/** + * PackageUtil helps to handle methods related to package. + * + * @author Leonardo Taehwan Kim + */ +public class PackageUtil { + + public static final String FACEBOOK = "com.facebook.katana"; + public static final String TWITTER = "com.twitter.android"; + public static final String GOOGLE_PLUS = "com.google.android.apps.plus"; + public static final String GMAIL = "com.google.android.gm"; + public static final String PINTEREST = "com.pinterest"; + public static final String TUMBLR = "com.tumblr"; + public static final String FANCY = "com.thefancy.app"; + public static final String FLIPBOARD = "flipboard.app"; + public static final String KAKAOTALK = "com.kakao.talk"; + public static final String KAKAOSTORY = "com.kakao.story"; + + public static boolean isInstalled(String packageName) { + PackageManager packageManager = Base.getContext().getPackageManager(); + try { + packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES); + return true; + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + public static String getPackageName() { + return Base.getContext().getPackageName(); + } + + public static void openPlayStore() { + String packageName = Base.getContext().getPackageName(); + openPlayStore(packageName); + } + + public static void openPlayStore(String packageName) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + packageName)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Base.getContext().startActivity(intent); + } catch (ActivityNotFoundException exception) { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + packageName)); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + Base.getContext().startActivity(intent); + } + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/etc/SparseArrayUtil.java b/utils/src/main/java/com/thefinestartist/utils/etc/SparseArrayUtil.java new file mode 100644 index 00000000..66468a68 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/etc/SparseArrayUtil.java @@ -0,0 +1,24 @@ +package com.thefinestartist.utils.etc; + +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +/** + * SparseArrayUtil helps to manage SparseArray conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class SparseArrayUtil { + + public static List asArrayList(SparseArray sparseArray) { + if (sparseArray == null) + return new ArrayList(); + + ArrayList arrayList = new ArrayList(sparseArray.size()); + for (int i = 0; i < sparseArray.size(); i++) + arrayList.add(sparseArray.valueAt(i)); + return arrayList; + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/etc/ThreadUtil.java b/utils/src/main/java/com/thefinestartist/utils/etc/ThreadUtil.java new file mode 100644 index 00000000..6bce5ae5 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/etc/ThreadUtil.java @@ -0,0 +1,15 @@ +package com.thefinestartist.utils.etc; + +import android.os.Looper; + +/** + * ThreadUtil helps to manage thread conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ThreadUtil { + + public static boolean isMain() { + return Looper.myLooper() == Looper.getMainLooper(); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/etc/TypefaceUtil.java b/utils/src/main/java/com/thefinestartist/utils/etc/TypefaceUtil.java new file mode 100644 index 00000000..32e600fe --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/etc/TypefaceUtil.java @@ -0,0 +1,55 @@ +package com.thefinestartist.utils.etc; + +import android.graphics.Typeface; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.collection.SimpleArrayMap; + +import com.thefinestartist.Base; + +/** + * TypefaceUtil helps to retrieve typeface from assets folder. + * + * @author Leonardo Taehwan Kim + */ +public class TypefaceUtil { + + private static final SimpleArrayMap cache = new SimpleArrayMap<>(); + + public static Typeface get(@NonNull String path) { + synchronized (cache) { + if (cache.containsKey(path)) + return cache.get(path); + + try { + Typeface typeface = Typeface.createFromAsset(Base.getContext().getAssets(), path); + cache.put(path, typeface); + return typeface; + } catch (RuntimeException e) { + return null; + } + } + } + + public static void setTypeface(@NonNull String path, TextView... textViews) { + if (textViews == null) + return; + + for (TextView textView : textViews) + if (textView != null) + textView.setTypeface(get(path)); + } + + public static void setTypeface(@NonNull String path, boolean includeFontPadding, TextView... textViews) { + if (textViews == null) + return; + + for (TextView textView : textViews) { + if (textView != null) { + textView.setTypeface(get(path)); + textView.setIncludeFontPadding(includeFontPadding); + } + } + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/log/AndroidLogPrinter.java b/utils/src/main/java/com/thefinestartist/utils/log/AndroidLogPrinter.java new file mode 100644 index 00000000..17251694 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/AndroidLogPrinter.java @@ -0,0 +1,9 @@ +package com.thefinestartist.utils.log; + +/** + * AndroidLogPrinter log message via Android system. + * + * @author Leonardo Taehwan Kim + */ +public class AndroidLogPrinter extends LogPrinter { +} diff --git a/utils/src/main/java/com/thefinestartist/utils/log/FileLogPrinter.java b/utils/src/main/java/com/thefinestartist/utils/log/FileLogPrinter.java new file mode 100644 index 00000000..9c6b1944 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/FileLogPrinter.java @@ -0,0 +1,40 @@ +package com.thefinestartist.utils.log; + +/** + * FileLogPrinter log message via file system. + * + * @author Leonardo Taehwan Kim + */ +public class FileLogPrinter extends LogPrinter { + + @Override + public void v(String tag, String message) { + super.v(tag, message); + } + + @Override + public void d(String tag, String message) { + super.d(tag, message); + } + + @Override + public void i(String tag, String message) { + super.i(tag, message); + } + + @Override + public void w(String tag, String message) { + super.w(tag, message); + } + + @Override + public void e(String tag, String message) { + super.e(tag, message); + } + + @Override + public void wtf(String tag, String message) { + super.wtf(tag, message); + } +} +// TODO: Finish this file after FileUtil \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/log/L.java b/utils/src/main/java/com/thefinestartist/utils/log/L.java new file mode 100644 index 00000000..709edcd5 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/L.java @@ -0,0 +1,9 @@ +package com.thefinestartist.utils.log; + +/** + * L is abbreviation class of {@link LogUtil}. + * + * @author Leonardo Taehwan Kim + */ +public class L extends LogUtil { +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/log/LogHelper.java b/utils/src/main/java/com/thefinestartist/utils/log/LogHelper.java new file mode 100644 index 00000000..1db79eb5 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/LogHelper.java @@ -0,0 +1,702 @@ +package com.thefinestartist.utils.log; + +import android.text.TextUtils; +import android.util.Log; + +import androidx.annotation.StringRes; + +import com.thefinestartist.enums.LogLevel; +import com.thefinestartist.utils.content.ResourcesUtil; +import com.thefinestartist.utils.etc.APILevel; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; + +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Source; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.stream.StreamResult; +import javax.xml.transform.stream.StreamSource; + +/** + * LogHelper helps to deal with {@link Log} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class LogHelper { + + private static final int INDENT_SPACES = 4; + // http://unicode.org/cldr/utility/list-unicodeset.jsp?a=%5B%E2%94%80-%E2%95%BF%EF%BF%A8%5D + private static final String TOP_DIVIDER = "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; + private static final String MIDDLE_DIVIDER = "┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; + private static final String BOTTOM_DIVIDER = "┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"; + + protected Settings settings = new Settings(LogHelper.class.getSimpleName()); + + // Constructors + public LogHelper() { + } + + public LogHelper(String tag) { + settings.setTag(tag); + } + + public LogHelper(@StringRes int tagRes) { + settings.setTag(ResourcesUtil.getString(tagRes)); + } + + public LogHelper(Class clazz) { + settings.setTag(clazz.getSimpleName()); + } + + // Setters + public LogHelper tag(String tag) { + settings.setTag(tag); + return this; + } + + public LogHelper tag(@StringRes int tagRes) { + settings.setTag(tagRes); + return this; + } + + public LogHelper tag(Class clazz) { + settings.setTag(clazz); + return this; + } + + public LogHelper showThreadInfo(boolean showThreadInfo) { + settings.setShowThreadInfo(showThreadInfo); + return this; + } + + public LogHelper stackTraceCount(int stackTraceCount) { + settings.setStackTraceCount(stackTraceCount); + return this; + } + + public LogHelper logLevel(LogLevel logLevel) { + settings.setLogLevel(logLevel); + return this; + } + + public LogHelper showDivider(boolean showDivider) { + settings.setShowDivider(showDivider); + return this; + } + + public LogHelper logPrinter(LogPrinter logPrinter) { + settings.setLogPrinter(logPrinter); + return this; + } + + // Logging Verbose + public void v(byte message) { + log(LogLevel.VERBOSE, message); + } + + public void v(char message) { + log(LogLevel.VERBOSE, message); + } + + public void v(short message) { + log(LogLevel.VERBOSE, message); + } + + public void v(int message) { + log(LogLevel.VERBOSE, message); + } + + public void v(long message) { + log(LogLevel.VERBOSE, message); + } + + public void v(float message) { + log(LogLevel.VERBOSE, message); + } + + public void v(double message) { + log(LogLevel.VERBOSE, message); + } + + public void v(boolean message) { + log(LogLevel.VERBOSE, message); + } + + public void v(String message) { + log(LogLevel.VERBOSE, message); + } + + public void v(JSONObject message) { + log(LogLevel.VERBOSE, message); + } + + public void v(JSONArray message) { + log(LogLevel.VERBOSE, message); + } + + public void v(Exception message) { + log(LogLevel.VERBOSE, message); + } + + public void v(Object message) { + log(LogLevel.VERBOSE, message); + } + + // Logging Debug + public void d(byte message) { + log(LogLevel.DEBUG, message); + } + + public void d(char message) { + log(LogLevel.DEBUG, message); + } + + public void d(short message) { + log(LogLevel.DEBUG, message); + } + + public void d(int message) { + log(LogLevel.DEBUG, message); + } + + public void d(long message) { + log(LogLevel.DEBUG, message); + } + + public void d(float message) { + log(LogLevel.DEBUG, message); + } + + public void d(double message) { + log(LogLevel.DEBUG, message); + } + + public void d(boolean message) { + log(LogLevel.DEBUG, message); + } + + public void d(String message) { + log(LogLevel.DEBUG, message); + } + + public void d(JSONObject message) { + log(LogLevel.DEBUG, message); + } + + public void d(JSONArray message) { + log(LogLevel.DEBUG, message); + } + + public void d(Exception message) { + log(LogLevel.DEBUG, message); + } + + public void d(Object message) { + log(LogLevel.DEBUG, message); + } + + // Logging Information + public void i(byte message) { + log(LogLevel.INFO, message); + } + + public void i(char message) { + log(LogLevel.INFO, message); + } + + public void i(short message) { + log(LogLevel.INFO, message); + } + + public void i(int message) { + log(LogLevel.INFO, message); + } + + public void i(long message) { + log(LogLevel.INFO, message); + } + + public void i(float message) { + log(LogLevel.INFO, message); + } + + public void i(double message) { + log(LogLevel.INFO, message); + } + + public void i(boolean message) { + log(LogLevel.INFO, message); + } + + public void i(String message) { + log(LogLevel.INFO, message); + } + + public void i(JSONObject message) { + log(LogLevel.INFO, message); + } + + public void i(JSONArray message) { + log(LogLevel.INFO, message); + } + + public void i(Exception message) { + log(LogLevel.INFO, message); + } + + public void i(Object message) { + log(LogLevel.INFO, message); + } + + // Logging Warning + public void w(byte message) { + log(LogLevel.WARN, message); + } + + public void w(char message) { + log(LogLevel.WARN, message); + } + + public void w(short message) { + log(LogLevel.WARN, message); + } + + public void w(int message) { + log(LogLevel.WARN, message); + } + + public void w(long message) { + log(LogLevel.WARN, message); + } + + public void w(float message) { + log(LogLevel.WARN, message); + } + + public void w(double message) { + log(LogLevel.WARN, message); + } + + public void w(boolean message) { + log(LogLevel.WARN, message); + } + + public void w(String message) { + log(LogLevel.WARN, message); + } + + public void w(JSONObject message) { + log(LogLevel.WARN, message); + } + + public void w(JSONArray message) { + log(LogLevel.WARN, message); + } + + public void w(Exception message) { + log(LogLevel.WARN, message); + } + + public void w(Object message) { + log(LogLevel.WARN, message); + } + + // Logging Error + public void e(byte message) { + log(LogLevel.ERROR, message); + } + + public void e(char message) { + log(LogLevel.ERROR, message); + } + + public void e(short message) { + log(LogLevel.ERROR, message); + } + + public void e(int message) { + log(LogLevel.ERROR, message); + } + + public void e(long message) { + log(LogLevel.ERROR, message); + } + + public void e(float message) { + log(LogLevel.ERROR, message); + } + + public void e(double message) { + log(LogLevel.ERROR, message); + } + + public void e(boolean message) { + log(LogLevel.ERROR, message); + } + + public void e(String message) { + log(LogLevel.ERROR, message); + } + + public void e(JSONObject message) { + log(LogLevel.ERROR, message); + } + + public void e(JSONArray message) { + log(LogLevel.ERROR, message); + } + + public void e(Exception message) { + log(LogLevel.ERROR, message); + } + + public void e(Object message) { + log(LogLevel.ERROR, message); + } + + // Logging Assert + public void wtf(byte message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(char message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(short message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(int message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(long message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(float message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(double message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(boolean message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(String message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(JSONObject message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(JSONArray message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(Exception message) { + log(LogLevel.ASSERT, message); + } + + public void wtf(Object message) { + log(LogLevel.ASSERT, message); + } + + // Logging JsonString + public void json(String jsonString) { + json(LogLevel.DEBUG, jsonString); + } + + public void json(LogLevel logLevel, String jsonString) { + if (TextUtils.isEmpty(jsonString)) { + log(logLevel, "Json string is empty."); + } else { + jsonString = jsonString.trim(); + + try { + if (jsonString.startsWith("{")) { + JSONObject jsonObject = new JSONObject(jsonString); + String message = jsonObject.toString(INDENT_SPACES); + log(logLevel, message); + return; + } + if (jsonString.startsWith("[")) { + JSONArray jsonArray = new JSONArray(jsonString); + String message = jsonArray.toString(INDENT_SPACES); + log(logLevel, message); + } + } catch (JSONException e) { + log(logLevel, e); + } + } + } + + // Logging XmlString + public void xml(String xmlString) { + xml(LogLevel.DEBUG, xmlString); + } + + public void xml(LogLevel logLevel, String xmlString) { + if (TextUtils.isEmpty(xmlString)) { + log(logLevel, "Xml string is empty."); + } else { + if (APILevel.require(8)) { + try { + Source xmlInput = new StreamSource(new StringReader(xmlString)); + StreamResult xmlOutput = new StreamResult(new StringWriter()); + Transformer transformer = TransformerFactory.newInstance().newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2"); + transformer.transform(xmlInput, xmlOutput); + log(logLevel, xmlOutput.getWriter().toString().replaceFirst(">", ">\n")); + } catch (TransformerException e) { + log(logLevel, e); + } + } else { + log(logLevel, xmlString); + } + } + } + + // Printing + private void log(LogLevel logLevel, byte message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, char message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, short message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, int message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, long message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, float message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, double message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, boolean message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, String.valueOf(message)); + } + + private void log(LogLevel logLevel, String message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + printString(logLevel, message); + } + + private void log(LogLevel logLevel, JSONObject message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + try { + printString(logLevel, message.toString(INDENT_SPACES)); + } catch (JSONException e) { + log(logLevel, e); + } + } + + private void log(LogLevel logLevel, JSONArray message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + try { + printString(logLevel, message.toString(INDENT_SPACES)); + } catch (JSONException e) { + log(logLevel, e); + } + } + + private void log(LogLevel logLevel, Exception message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + StringBuilder builder = new StringBuilder(); + builder.append(String.valueOf(message)); + builder.append("\n"); + + StackTraceElement[] traces = message.getStackTrace(); + for (StackTraceElement trace : traces) { + builder.append(" at ") + .append(trace.getClassName()) + .append(".") + .append(trace.getMethodName()) + .append("(") + .append(trace.getFileName()) + .append(":") + .append(trace.getLineNumber()) + .append(")") + .append("\n"); + } + + printString(logLevel, builder.toString(), true); + } + + private void log(LogLevel logLevel, Object message) { + if (logLevel.ordinal() < settings.getLogLevel().ordinal()) + return; + + String log; + if (message instanceof byte[]) log = Arrays.toString((byte[]) message); + else if (message instanceof char[]) log = Arrays.toString((char[]) message); + else if (message instanceof short[]) log = Arrays.toString((short[]) message); + else if (message instanceof int[]) log = Arrays.toString((int[]) message); + else if (message instanceof long[]) log = Arrays.toString((long[]) message); + else if (message instanceof float[]) log = Arrays.toString((float[]) message); + else if (message instanceof double[]) log = Arrays.toString((double[]) message); + else if (message instanceof boolean[]) log = Arrays.toString((boolean[]) message); + else if (message instanceof Object[]) log = Arrays.toString((Object[]) message); + else log = String.valueOf(message); + + printString(logLevel, log); + } + + private void printString(LogLevel logLevel, String message) { + printString(logLevel, message, false); + } + + private synchronized void printString(LogLevel logLevel, String message, boolean fromException) { + // Create TAG + String TAG = settings.getTag(); + if (settings.getShowThreadInfo()) TAG += "(" + Thread.currentThread().getName() + ")"; + + // Top Divider + if (settings.getShowDivider()) printLine(logLevel, TAG, TOP_DIVIDER); + + // Log Content + String[] lines = message.split(System.getProperty("line.separator")); + for (String line : lines) + printLine(logLevel, TAG, settings.getShowDivider() ? + "┃ " + line : + line); + + if (settings.getStackTraceCount() > 0 && fromException) + printLine(logLevel, TAG, "Exception occurred"); + + // Middle Divider + if (settings.getShowDivider()) printLine(logLevel, TAG, MIDDLE_DIVIDER); + + // Log Stack Trace + StackTraceElement[] traces = Thread.currentThread().getStackTrace(); + int startIndex = 2; + while (LogUtil.class.getCanonicalName().equals(traces[startIndex].getClassName()) + || LogHelper.class.getCanonicalName().equals(traces[startIndex].getClassName())) + startIndex++; + + for (int i = startIndex; i < Math.min(traces.length, startIndex + settings.getStackTraceCount()); i++) { + StringBuilder builder = new StringBuilder(); + builder.append(" at ") + .append(traces[i].getClassName()) + .append(".") + .append(traces[i].getMethodName()) + .append("(") + .append(traces[i].getFileName()) + .append(":") + .append(traces[i].getLineNumber()) + .append(")"); + + printLine(logLevel, TAG, settings.getShowDivider() ? + "┃ " + builder.toString() : + builder.toString()); + } + + // Log ellipsized stack trance + int leftTraceCount = traces.length - startIndex - settings.getStackTraceCount(); + if (settings.getStackTraceCount() > 0 && leftTraceCount > 1) + printLine(logLevel, TAG, settings.getShowDivider() ? + "┃ at " + leftTraceCount + " more stack traces..." : + " at " + leftTraceCount + " more stack traces..."); + if (settings.getStackTraceCount() > 0 && leftTraceCount == 1) + printLine(logLevel, TAG, settings.getShowDivider() ? + "┃ at 1 more stack trace..." : + " at 1 more stack trace..."); + + // Middle Divider + if (settings.getShowDivider()) printLine(logLevel, TAG, BOTTOM_DIVIDER); + + // LogUtil setToDefault + if (this == LogUtil.getInstance()) setToDefault(); + } + + private void printLine(LogLevel logLevel, String tag, String message) { + switch (logLevel) { + case FULL: + case VERBOSE: + settings.getLogPrinter().v(tag, message); + break; + case DEBUG: + settings.getLogPrinter().d(tag, message); + break; + case INFO: + settings.getLogPrinter().i(tag, message); + break; + case WARN: + settings.getLogPrinter().w(tag, message); + break; + case ERROR: + settings.getLogPrinter().e(tag, message); + break; + case ASSERT: + settings.getLogPrinter().wtf(tag, message); + break; + } + } + + protected void setToDefault() { + settings.setTag(LogUtil.getDefaultSettings().getTag()); + settings.setShowThreadInfo(LogUtil.getDefaultSettings().getShowThreadInfo()); + settings.setStackTraceCount(LogUtil.getDefaultSettings().getStackTraceCount()); + settings.setLogLevel(LogUtil.getDefaultSettings().getLogLevel()); + settings.setShowDivider(LogUtil.getDefaultSettings().getShowDivider()); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/log/LogPrinter.java b/utils/src/main/java/com/thefinestartist/utils/log/LogPrinter.java new file mode 100644 index 00000000..7245ca55 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/LogPrinter.java @@ -0,0 +1,40 @@ +package com.thefinestartist.utils.log; + +import android.util.Log; + +import com.thefinestartist.utils.etc.APILevel; + +/** + * LogPrinter helps to print message for {@link LogHelper}. + * + * @author Leonardo Taehwan Kim + */ +public abstract class LogPrinter { + + public void v(String tag, String message) { + Log.v(tag, message); + } + + public void d(String tag, String message) { + Log.d(tag, message); + } + + public void i(String tag, String message) { + Log.i(tag, message); + } + + public void w(String tag, String message) { + Log.w(tag, message); + } + + public void e(String tag, String message) { + Log.e(tag, message); + } + + public void wtf(String tag, String message) { + if (APILevel.require(8)) + Log.wtf(tag, message); + else + Log.e(tag, message); + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/log/LogUtil.java b/utils/src/main/java/com/thefinestartist/utils/log/LogUtil.java new file mode 100644 index 00000000..5dfcd909 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/LogUtil.java @@ -0,0 +1,406 @@ +package com.thefinestartist.utils.log; + +import android.util.Log; + +import androidx.annotation.StringRes; + +import com.thefinestartist.enums.LogLevel; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * LogUtil helps to manage application-wide {@link Log} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class LogUtil { + + // Defaults + private static Settings defaultSettings = new Settings(LogUtil.class.getSimpleName()); + + // Singleton + private static volatile LogHelper logHelper = new LogHelper() + .tag(defaultSettings.getTag()) + .showThreadInfo(defaultSettings.getShowThreadInfo()) + .stackTraceCount(defaultSettings.getStackTraceCount()) + .logLevel(defaultSettings.getLogLevel()) + .showDivider(defaultSettings.getShowDivider()); + + public static Settings getDefaultSettings() { + return defaultSettings; + } + + public static LogHelper getInstance() { + return logHelper; + } + + // Builder + public static LogHelper tag(String tag) { + return logHelper.tag(tag); + } + + public static LogHelper tag(@StringRes int tagRes) { + return logHelper.tag(tagRes); + } + + public static LogHelper tag(Class clazz) { + return logHelper.tag(clazz); + } + + public static LogHelper showThreadInfo(boolean showThreadInfo) { + return logHelper.showThreadInfo(showThreadInfo); + } + + public static LogHelper stackTraceCount(int stackTraceCount) { + return logHelper.stackTraceCount(stackTraceCount); + } + + public static LogHelper logLevel(LogLevel logLevel) { + return logHelper.logLevel(logLevel); + } + + public static LogHelper showDivider(boolean showDivider) { + return logHelper.showDivider(showDivider); + } + + public LogHelper logPrinter(LogPrinter logPrinter) { + return logHelper.logPrinter(logPrinter); + } + + // Logging Verbose + public static void v(byte message) { + logHelper.v(message); + } + + public static void v(char message) { + logHelper.v(message); + } + + public static void v(short message) { + logHelper.v(message); + } + + public static void v(int message) { + logHelper.v(message); + } + + public static void v(long message) { + logHelper.v(message); + } + + public static void v(float message) { + logHelper.v(message); + } + + public static void v(double message) { + logHelper.v(message); + } + + public static void v(boolean message) { + logHelper.v(message); + } + + public static void v(String message) { + logHelper.v(message); + } + + public static void v(JSONObject message) { + logHelper.v(message); + } + + public static void v(JSONArray message) { + logHelper.v(message); + } + + public static void v(Exception message) { + logHelper.v(message); + } + + public static void v(Object message) { + logHelper.v(message); + } + + // Logging Debug + public static void d(byte message) { + logHelper.d(message); + } + + public static void d(char message) { + logHelper.d(message); + } + + public static void d(short message) { + logHelper.d(message); + } + + public static void d(int message) { + logHelper.d(message); + } + + public static void d(long message) { + logHelper.d(message); + } + + public static void d(float message) { + logHelper.d(message); + } + + public static void d(double message) { + logHelper.d(message); + } + + public static void d(boolean message) { + logHelper.d(message); + } + + public static void d(String message) { + logHelper.d(message); + } + + public static void d(JSONObject message) { + logHelper.d(message); + } + + public static void d(JSONArray message) { + logHelper.d(message); + } + + public static void d(Exception message) { + logHelper.d(message); + } + + public static void d(Object message) { + logHelper.d(message); + } + + // Logging Information + public static void i(byte message) { + logHelper.i(message); + } + + public static void i(char message) { + logHelper.i(message); + } + + public static void i(short message) { + logHelper.i(message); + } + + public static void i(int message) { + logHelper.i(message); + } + + public static void i(long message) { + logHelper.i(message); + } + + public static void i(float message) { + logHelper.i(message); + } + + public static void i(double message) { + logHelper.i(message); + } + + public static void i(boolean message) { + logHelper.i(message); + } + + public static void i(String message) { + logHelper.i(message); + } + + public static void i(JSONObject message) { + logHelper.i(message); + } + + public static void i(JSONArray message) { + logHelper.i(message); + } + + public static void i(Exception message) { + logHelper.i(message); + } + + public static void i(Object message) { + logHelper.i(message); + } + + // Logging Warning + public static void w(byte message) { + logHelper.w(message); + } + + public static void w(char message) { + logHelper.w(message); + } + + public static void w(short message) { + logHelper.w(message); + } + + public static void w(int message) { + logHelper.w(message); + } + + public static void w(long message) { + logHelper.w(message); + } + + public static void w(float message) { + logHelper.w(message); + } + + public static void w(double message) { + logHelper.w(message); + } + + public static void w(boolean message) { + logHelper.w(message); + } + + public static void w(String message) { + logHelper.w(message); + } + + public static void w(JSONObject message) { + logHelper.w(message); + } + + public static void w(JSONArray message) { + logHelper.w(message); + } + + public static void w(Exception message) { + logHelper.w(message); + } + + public static void w(Object message) { + logHelper.w(message); + } + + // Logging Error + public static void e(byte message) { + logHelper.e(message); + } + + public static void e(char message) { + logHelper.e(message); + } + + public static void e(short message) { + logHelper.e(message); + } + + public static void e(int message) { + logHelper.e(message); + } + + public static void e(long message) { + logHelper.e(message); + } + + public static void e(float message) { + logHelper.e(message); + } + + public static void e(double message) { + logHelper.e(message); + } + + public static void e(boolean message) { + logHelper.e(message); + } + + public static void e(String message) { + logHelper.e(message); + } + + public static void e(JSONObject message) { + logHelper.e(message); + } + + public static void e(JSONArray message) { + logHelper.e(message); + } + + public static void e(Exception message) { + logHelper.e(message); + } + + public static void e(Object message) { + logHelper.e(message); + } + + // Logging Assert + public static void wtf(byte message) { + logHelper.wtf(message); + } + + public static void wtf(char message) { + logHelper.wtf(message); + } + + public static void wtf(short message) { + logHelper.wtf(message); + } + + public static void wtf(int message) { + logHelper.wtf(message); + } + + public static void wtf(long message) { + logHelper.wtf(message); + } + + public static void wtf(float message) { + logHelper.wtf(message); + } + + public static void wtf(double message) { + logHelper.wtf(message); + } + + public static void wtf(boolean message) { + logHelper.wtf(message); + } + + public static void wtf(String message) { + logHelper.wtf(message); + } + + public static void wtf(JSONObject message) { + logHelper.wtf(message); + } + + public static void wtf(JSONArray message) { + logHelper.wtf(message); + } + + public static void wtf(Exception message) { + logHelper.wtf(message); + } + + public static void wtf(Object message) { + logHelper.wtf(message); + } + + // Logging JsonString + public static void json(String jsonString) { + json(LogLevel.DEBUG, jsonString); + } + + public static void json(LogLevel logLevel, String jsonString) { + logHelper.json(logLevel, jsonString); + } + + // Logging XmlString + public static void xml(String xmlString) { + xml(LogLevel.DEBUG, xmlString); + } + + public static void xml(LogLevel logLevel, String jsonString) { + logHelper.xml(logLevel, jsonString); + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/log/Settings.java b/utils/src/main/java/com/thefinestartist/utils/log/Settings.java new file mode 100644 index 00000000..ca68a989 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/log/Settings.java @@ -0,0 +1,109 @@ +package com.thefinestartist.utils.log; + + +import androidx.annotation.StringRes; + +import com.thefinestartist.enums.LogLevel; +import com.thefinestartist.utils.content.ResourcesUtil; + +/** + * Settings for {@link LogHelper}. + * + * @author Leonardo Taehwan Kim + */ +public class Settings { + + private String tag = Settings.class.getSimpleName(); + private boolean showThreadInfo = false; + private int stackTraceCount = 0; + private LogLevel logLevel = LogLevel.FULL; + private boolean showDivider = false; + private LogPrinter logPrinter = new AndroidLogPrinter(); + + public Settings() { + } + + public Settings(String tag) { + this.tag = tag; + } + + public Settings(@StringRes int tagRes) { + this.tag = ResourcesUtil.getString(tagRes); + } + + public Settings(Class clazz) { + this.tag = clazz.getSimpleName(); + } + + public String getTag() { + return tag; + } + + public Settings setTag(String tag) { + this.tag = tag; + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public Settings setTag(@StringRes int tagRes) { + this.tag = ResourcesUtil.getString(tagRes); + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public Settings setTag(Class clazz) { + this.tag = clazz.getSimpleName(); + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public boolean getShowThreadInfo() { + return showThreadInfo; + } + + public Settings setShowThreadInfo(boolean showThreadInfo) { + this.showThreadInfo = showThreadInfo; + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public int getStackTraceCount() { + return stackTraceCount; + } + + public Settings setStackTraceCount(int stackTraceCount) { + this.stackTraceCount = stackTraceCount; + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public LogLevel getLogLevel() { + return logLevel; + } + + public Settings setLogLevel(LogLevel logLevel) { + this.logLevel = logLevel; + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public boolean getShowDivider() { + return showDivider; + } + + public Settings setShowDivider(boolean showDivider) { + this.showDivider = showDivider; + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } + + public LogPrinter getLogPrinter() { + return logPrinter; + } + + public Settings setLogPrinter(LogPrinter logPrinter) { + this.logPrinter = logPrinter; + if (this == LogUtil.getDefaultSettings()) LogUtil.getInstance().setToDefault(); + return this; + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/preferences/Pref.java b/utils/src/main/java/com/thefinestartist/utils/preferences/Pref.java new file mode 100644 index 00000000..fd661c6b --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/preferences/Pref.java @@ -0,0 +1,9 @@ +package com.thefinestartist.utils.preferences; + +/** + * Pref is abbreviation class of {@link PreferencesUtil}. + * + * @author Robin Gustafsson + */ +public class Pref extends PreferencesUtil { +} diff --git a/utils/src/main/java/com/thefinestartist/utils/preferences/PreferencesUtil.java b/utils/src/main/java/com/thefinestartist/utils/preferences/PreferencesUtil.java new file mode 100755 index 00000000..ee680596 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/preferences/PreferencesUtil.java @@ -0,0 +1,271 @@ +package com.thefinestartist.utils.preferences; + +import android.annotation.TargetApi; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Build; +import android.util.Base64; + +import com.thefinestartist.Base; +import com.thefinestartist.utils.etc.APILevel; +import com.thefinestartist.utils.log.LogHelper; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.util.Set; + +/** + * PreferencesUtil helps to manage application-wide {@link SharedPreferences} conveniently. + * + * @author Robin Gustafsson + */ +public class PreferencesUtil { + + private static final LogHelper LogHelper = new LogHelper(PreferencesUtil.class); + private static String defaultName = PreferencesUtil.class.getCanonicalName(); + + private static SharedPreferences getPreferences(String name) { + return Base.getContext().getSharedPreferences(name, Context.MODE_PRIVATE); + } + + + public static String getDefaultName() { + return defaultName; + } + + public static void setDefaultName(String name) { + defaultName = name; + } + + + public static boolean get(String key, boolean defValue) { + return get(defaultName, key, defValue); + } + + public static int get(String key, int defValue) { + return get(defaultName, key, defValue); + } + + public static float get(String key, float defValue) { + return get(defaultName, key, defValue); + } + + public static long get(String key, long defValue) { + return get(defaultName, key, defValue); + } + + public static String get(String key, String defValue) { + return get(defaultName, key, defValue); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static Set get(String key, Set defValue) { + return get(defaultName, key, defValue); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public static C get(String key, C defValue) { + return get(defaultName, key, defValue); + } + + public static boolean get(String name, String key, boolean defValue) { + return getPreferences(name).getBoolean(key, defValue); + } + + public static int get(String name, String key, int defValue) { + return getPreferences(name).getInt(key, defValue); + } + + public static float get(String name, String key, float defValue) { + return getPreferences(name).getFloat(key, defValue); + } + + public static long get(String name, String key, long defValue) { + return getPreferences(name).getLong(key, defValue); + } + + public static String get(String name, String key, String defValue) { + return getPreferences(name).getString(key, defValue); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static Set get(String name, String key, Set defValue) { + return getPreferences(name).getStringSet(key, defValue); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public static C get(String name, String key, C defValue) { + ByteArrayInputStream bais = null; + ObjectInputStream ois = null; + C result = defValue; + + String value = getPreferences(name).getString(key, null); + if (value != null) { + try { + byte[] decoded = Base64.decode(value.getBytes(), Base64.DEFAULT); + bais = new ByteArrayInputStream(decoded); + ois = new ObjectInputStream(bais); + result = (C) ois.readObject(); + + } catch (Exception e) { + LogHelper.e(e); + } finally { + if (ois != null) { + try { + ois.close(); + } catch (IOException e) { + LogHelper.e(e); + } + } + if (bais != null) { + try { + bais.close(); + } catch (IOException e) { + LogHelper.e(e); + } + } + } + } + + return result; + } + + + public static void put(String key, boolean value) { + put(defaultName, key, value); + } + + public static void put(String key, int value) { + put(defaultName, key, value); + } + + public static void put(String key, float value) { + put(defaultName, key, value); + } + + public static void put(String key, long value) { + put(defaultName, key, value); + } + + public static void put(String key, String value) { + put(defaultName, key, value); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void put(String key, Set value) { + put(defaultName, key, value); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public static void put(String key, C value) { + put(defaultName, key, value); + } + + public static void put(String name, String key, boolean value) { + if (APILevel.require(9)) + getPreferences(name).edit().putBoolean(key, value).apply(); + else + getPreferences(name).edit().putBoolean(key, value).commit(); + } + + public static void put(String name, String key, int value) { + if (APILevel.require(9)) + getPreferences(name).edit().putInt(key, value).apply(); + else + getPreferences(name).edit().putInt(key, value).commit(); + } + + public static void put(String name, String key, float value) { + if (APILevel.require(9)) + getPreferences(name).edit().putFloat(key, value).apply(); + else + getPreferences(name).edit().putFloat(key, value).commit(); + } + + public static void put(String name, String key, long value) { + if (APILevel.require(9)) + getPreferences(name).edit().putLong(key, value).apply(); + else + getPreferences(name).edit().putLong(key, value).commit(); + } + + public static void put(String name, String key, String value) { + if (APILevel.require(9)) + getPreferences(name).edit().putString(key, value).apply(); + else + getPreferences(name).edit().putString(key, value).commit(); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void put(String name, String key, Set value) { + if (APILevel.require(9)) + getPreferences(name).edit().putStringSet(key, value).apply(); + else + getPreferences(name).edit().putStringSet(key, value).commit(); + } + + @TargetApi(Build.VERSION_CODES.FROYO) + public static void put(String name, String key, C value) { + ByteArrayOutputStream baos = null; + ObjectOutputStream oos = null; + + try { + baos = new ByteArrayOutputStream(); + oos = new ObjectOutputStream(baos); + oos.writeObject(value); + byte[] encoded = Base64.encode(baos.toByteArray(), Base64.DEFAULT); + if (APILevel.require(9)) + getPreferences(name).edit().putString(key, new String(encoded)).apply(); + else + getPreferences(name).edit().putString(key, new String(encoded)).commit(); + + } catch (IOException e) { + LogHelper.e(e); + throw new RuntimeException(e); + + } finally { + if (oos != null) { + try { + oos.close(); + } catch (IOException e) { + LogHelper.e(e); + } + } + if (baos != null) { + try { + baos.close(); + } catch (IOException e) { + LogHelper.e(e); + } + } + } + } + + + public static void remove(String key) { + remove(defaultName, key); + } + + public static void remove(String name, String key) { + if (APILevel.require(9)) + getPreferences(name).edit().remove(key).apply(); + else + getPreferences(name).edit().remove(key).commit(); + } + + + public static void clear() { + clear(defaultName); + } + + public static void clear(String name) { + if (APILevel.require(9)) + getPreferences(name).edit().clear().apply(); + else + getPreferences(name).edit().clear().commit(); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/service/ClipboardManagerUtil.java b/utils/src/main/java/com/thefinestartist/utils/service/ClipboardManagerUtil.java new file mode 100644 index 00000000..4c3fff3a --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/service/ClipboardManagerUtil.java @@ -0,0 +1,57 @@ +package com.thefinestartist.utils.service; + +import android.content.ClipData; +import android.content.ClipDescription; +import android.content.ClipboardManager; + +import com.thefinestartist.utils.etc.APILevel; + +/** + * ClipboardManagerUtil helps to manage {@link ClipboardManager} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ClipboardManagerUtil { + + public static void setText(CharSequence text) { + android.text.ClipboardManager clipboardManager = ServiceUtil.getClipboardManager(); + if (APILevel.require(11)) { + ClipboardManager cm = (ClipboardManager) clipboardManager; + ClipData clip = ClipData.newPlainText("ClipboardManagerUtil", text); + cm.setPrimaryClip(clip); + } else { + clipboardManager.setText(text); + } + } + + public static boolean hasText() { + android.text.ClipboardManager clipboardManager = ServiceUtil.getClipboardManager(); + if (APILevel.require(11)) { + ClipboardManager cm = (ClipboardManager) clipboardManager; + ClipDescription description = cm.getPrimaryClipDescription(); + ClipData clipData = cm.getPrimaryClip(); + return clipData != null + && description != null + && (description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)); + } else { + return clipboardManager.hasText(); + } + } + + public static CharSequence getText() { + android.text.ClipboardManager clipboardManager = ServiceUtil.getClipboardManager(); + if (APILevel.require(11)) { + ClipboardManager cm = (ClipboardManager) clipboardManager; + ClipDescription description = cm.getPrimaryClipDescription(); + ClipData clipData = cm.getPrimaryClip(); + if (clipData != null + && description != null + && description.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) + return clipData.getItemAt(0).getText(); + else + return null; + } else { + return clipboardManager.getText(); + } + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/service/ServiceUtil.java b/utils/src/main/java/com/thefinestartist/utils/service/ServiceUtil.java new file mode 100644 index 00000000..5557e219 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/service/ServiceUtil.java @@ -0,0 +1,330 @@ +package com.thefinestartist.utils.service; + +import android.accounts.AccountManager; +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.app.AlarmManager; +import android.app.AppOpsManager; +import android.app.DownloadManager; +import android.app.KeyguardManager; +import android.app.NotificationManager; +import android.app.SearchManager; +import android.app.UiModeManager; +import android.app.WallpaperManager; +import android.app.admin.DevicePolicyManager; +import android.app.job.JobScheduler; +import android.app.usage.NetworkStatsManager; +import android.app.usage.UsageStatsManager; +import android.appwidget.AppWidgetManager; +import android.bluetooth.BluetoothManager; +import android.content.Context; +import android.content.RestrictionsManager; +import android.content.pm.LauncherApps; +import android.hardware.ConsumerIrManager; +import android.hardware.SensorManager; +import android.hardware.camera2.CameraManager; +import android.hardware.display.DisplayManager; +import android.hardware.fingerprint.FingerprintManager; +import android.hardware.input.InputManager; +import android.hardware.usb.UsbManager; +import android.location.LocationManager; +import android.media.AudioManager; +import android.media.MediaRouter; +import android.media.midi.MidiManager; +import android.media.projection.MediaProjectionManager; +import android.media.session.MediaSessionManager; +import android.media.tv.TvInputManager; +import android.net.ConnectivityManager; +import android.net.nsd.NsdManager; +import android.net.wifi.WifiManager; +import android.net.wifi.p2p.WifiP2pManager; +import android.nfc.NfcManager; +import android.os.BatteryManager; +import android.os.DropBoxManager; +import android.os.PowerManager; +import android.os.UserManager; +import android.os.Vibrator; +import android.os.storage.StorageManager; +import android.print.PrintManager; +import android.telecom.TelecomManager; +import android.telephony.CarrierConfigManager; +import android.telephony.SubscriptionManager; +import android.telephony.TelephonyManager; +import android.text.ClipboardManager; +import android.view.LayoutInflater; +import android.view.WindowManager; +import android.view.accessibility.AccessibilityManager; +import android.view.accessibility.CaptioningManager; +import android.view.inputmethod.InputMethodManager; +import android.view.textservice.TextServicesManager; + +import androidx.annotation.NonNull; + +import com.thefinestartist.Base; + +/** + * ServiceUtil helps to manage Android system service conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ServiceUtil { + + public static Object getSystemService(@NonNull String serviceName) { + return Base.getContext().getSystemService(serviceName); + } + + public static AccessibilityManager getAccessibilityManager() { + return (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE); + } + + @TargetApi(19) + public static CaptioningManager getCaptioningManager() { + return (CaptioningManager) getSystemService(Context.CAPTIONING_SERVICE); + } + + public static AccountManager getAccountManager() { + return (AccountManager) getSystemService(Context.ACCOUNT_SERVICE); + } + + public static ActivityManager getActivityManager() { + return (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + } + + public static AlarmManager getAlarmManager() { + return (AlarmManager) getSystemService(Context.ALARM_SERVICE); + } + + public static AudioManager getAudioManager() { + return (AudioManager) getSystemService(Context.AUDIO_SERVICE); + } + + @TargetApi(16) + public static MediaRouter getMediaRouter() { + return (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE); + } + + @TargetApi(18) + public static BluetoothManager getBluetoothManager() { + return (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); + } + + public static ClipboardManager getClipboardManager() { + return (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + } + + public static ConnectivityManager getConnectivityManager() { + return (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + } + + @TargetApi(8) + public static DevicePolicyManager getDevicePolicyManager() { + return (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE); + } + + @TargetApi(9) + public static DownloadManager getDownloadManager() { + return (DownloadManager) getSystemService(Context.DOWNLOAD_SERVICE); + } + + @TargetApi(21) + public static BatteryManager getBatteryManager() { + return (BatteryManager) getSystemService(Context.BATTERY_SERVICE); + } + + @TargetApi(10) + public static NfcManager getNfcManager() { + return (NfcManager) getSystemService(Context.NFC_SERVICE); + } + + @TargetApi(8) + public static DropBoxManager getDropBoxManager() { + return (DropBoxManager) getSystemService(Context.DROPBOX_SERVICE); + } + + @TargetApi(16) + public static InputManager getInputManager() { + return (InputManager) getSystemService(Context.INPUT_SERVICE); + } + + @TargetApi(17) + public static DisplayManager getDisplayManager() { + return (DisplayManager) getSystemService(Context.DISPLAY_SERVICE); + } + + public static InputMethodManager getInputMethodManager() { + return (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + } + + @TargetApi(14) + public static TextServicesManager getTextServicesManager() { + return (TextServicesManager) getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + } + + public static KeyguardManager getKeyguardManager() { + return (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + } + + public static LayoutInflater getLayoutInflater() { + return (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + } + + public static LocationManager getLocationManager() { + return (LocationManager) getSystemService(Context.LOCATION_SERVICE); + } + + public static NotificationManager getNotificationManager() { + return (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + } + + @TargetApi(16) + public static NsdManager getNsdManager() { + return (NsdManager) getSystemService(Context.NSD_SERVICE); + } + + public static PowerManager getPowerManager() { + return (PowerManager) getSystemService(Context.POWER_SERVICE); + } + + public static SearchManager getSearchManager() { + return (SearchManager) getSystemService(Context.SEARCH_SERVICE); + } + + public static SensorManager getSensorManager() { + return (SensorManager) getSystemService(Context.SENSOR_SERVICE); + } + + @TargetApi(9) + public static StorageManager getStorageManager() { + return (StorageManager) getSystemService(Context.STORAGE_SERVICE); + } + + public static TelephonyManager getTelephonyManager() { + return (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); + } + + @TargetApi(22) + public static SubscriptionManager getSubscriptionManager() { + return (SubscriptionManager) getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE); + } + + @TargetApi(23) + public static CarrierConfigManager getCarrierConfigManager() { + return (CarrierConfigManager) getSystemService(Context.CARRIER_CONFIG_SERVICE); + } + + @TargetApi(21) + public static TelecomManager getTelecomManager() { + return (TelecomManager) getSystemService(Context.TELECOM_SERVICE); + } + + @TargetApi(8) + public static UiModeManager getUiModeManager() { + return (UiModeManager) getSystemService(Context.UI_MODE_SERVICE); + } + + @TargetApi(12) + public static UsbManager getUsbManager() { + return (UsbManager) getSystemService(Context.USB_SERVICE); + } + + public static Vibrator getVibrator() { + return (Vibrator) getSystemService(Context.VIBRATOR_SERVICE); + } + + public static WallpaperManager getWallpaperManager() { + return WallpaperManager.getInstance(Base.getContext()); + } + + public static WifiManager getWifiManager() { + return (WifiManager) getSystemService(Context.WIFI_SERVICE); + } + + @TargetApi(14) + public static WifiP2pManager getWifiP2pManager() { + return (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); + } + + public static WindowManager getWindowManager() { + return (WindowManager) getSystemService(Context.WINDOW_SERVICE); + } + + @TargetApi(17) + public static UserManager getUserManager() { + return (UserManager) getSystemService(Context.USER_SERVICE); + } + + @TargetApi(19) + public static AppOpsManager getAppOpsManager() { + return (AppOpsManager) getSystemService(Context.APP_OPS_SERVICE); + } + + @TargetApi(21) + public static CameraManager getCameraManager() { + return (CameraManager) getSystemService(Context.CAMERA_SERVICE); + } + + @TargetApi(21) + public static LauncherApps getLauncherApps() { + return (LauncherApps) getSystemService(Context.LAUNCHER_APPS_SERVICE); + } + + @TargetApi(21) + public static RestrictionsManager getRestrictionsManager() { + return (RestrictionsManager) getSystemService(Context.RESTRICTIONS_SERVICE); + } + + @TargetApi(19) + public static PrintManager getPrintManager() { + return (PrintManager) getSystemService(Context.PRINT_SERVICE); + } + + @TargetApi(19) + public static ConsumerIrManager getConsumerIrManager() { + return (ConsumerIrManager) getSystemService(Context.CONSUMER_IR_SERVICE); + } + + @TargetApi(21) + public static MediaSessionManager getMediaSessionManager() { + return (MediaSessionManager) getSystemService(Context.MEDIA_SESSION_SERVICE); + } + + @TargetApi(23) + public static FingerprintManager getFingerprintManager() { + return (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE); + } + + @TargetApi(21) + public static TvInputManager getTvInputManager() { + return (TvInputManager) getSystemService(Context.TV_INPUT_SERVICE); + } + + @TargetApi(22) + public static UsageStatsManager getUsageStatsManager() { + return (UsageStatsManager) getSystemService(Context.USAGE_STATS_SERVICE); + } + + @TargetApi(23) + public static NetworkStatsManager getNetworkStatsManager() { + return (NetworkStatsManager) getSystemService(Context.NETWORK_STATS_SERVICE); + } + + @TargetApi(21) + public static JobScheduler getJobScheduler() { + return (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE); + } + + @TargetApi(21) + public static MediaProjectionManager getMediaProjectionManager() { + return (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); + } + + @TargetApi(21) + public static AppWidgetManager getAppWidgetManager() { + return (AppWidgetManager) getSystemService(Context.APPWIDGET_SERVICE); + } + + @TargetApi(23) + public static MidiManager getMidiManager() { + return (MidiManager) getSystemService(Context.MIDI_SERVICE); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/service/VibratorUtil.java b/utils/src/main/java/com/thefinestartist/utils/service/VibratorUtil.java new file mode 100755 index 00000000..e24dbca2 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/service/VibratorUtil.java @@ -0,0 +1,48 @@ +package com.thefinestartist.utils.service; + +import android.annotation.TargetApi; +import android.media.AudioAttributes; +import android.os.Vibrator; + +/** + * VibratorUtil helps to manage {@link Vibrator} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class VibratorUtil { + + @TargetApi(11) + public static boolean hasVibrator() { + return ServiceUtil.getVibrator().hasVibrator(); + } + + public static void vibrate() { + vibrate(200); + } + + public static void vibrate(long milliseconds) { + vibrate(new long[]{milliseconds}); + } + + public static void vibrate(long[] pattern) { + vibrate(pattern, -1); + } + + public static void vibrate(long[] pattern, int repeat) { + ServiceUtil.getVibrator().vibrate(pattern, repeat); + } + + @TargetApi(21) + public static void vibrate(long milliseconds, AudioAttributes attributes) { + vibrate(new long[]{milliseconds}, -1, attributes); + } + + @TargetApi(21) + public static void vibrate(long[] pattern, int repeat, AudioAttributes attributes) { + ServiceUtil.getVibrator().vibrate(pattern, repeat, attributes); + } + + public static void cancel() { + ServiceUtil.getVibrator().cancel(); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/service/WindowManagerUtil.java b/utils/src/main/java/com/thefinestartist/utils/service/WindowManagerUtil.java new file mode 100644 index 00000000..dad44b82 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/service/WindowManagerUtil.java @@ -0,0 +1,21 @@ +package com.thefinestartist.utils.service; + +import android.view.Display; +import android.view.View; +import android.view.WindowManager; + +/** + * WindowManagerUtil helps to manage {@link WindowManager} conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class WindowManagerUtil { + + public static Display getDefaultDisplay() { + return ServiceUtil.getWindowManager().getDefaultDisplay(); + } + + public static void removeViewImmediate(View view) { + ServiceUtil.getWindowManager().removeViewImmediate(view); + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/ui/DisplayUtil.java b/utils/src/main/java/com/thefinestartist/utils/ui/DisplayUtil.java new file mode 100644 index 00000000..ddc38f41 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/ui/DisplayUtil.java @@ -0,0 +1,82 @@ +package com.thefinestartist.utils.ui; + +import android.graphics.Point; +import android.util.TypedValue; +import android.view.Display; + +import com.thefinestartist.enums.Rotation; +import com.thefinestartist.utils.content.ResourcesUtil; +import com.thefinestartist.utils.content.ThemeUtil; +import com.thefinestartist.utils.content.TypedValueUtil; +import com.thefinestartist.utils.etc.APILevel; +import com.thefinestartist.utils.service.WindowManagerUtil; + +/** + * DisplayUtil helps to calculate screen size conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class DisplayUtil { + + public static int getWidth() { + Display display = WindowManagerUtil.getDefaultDisplay(); + if (APILevel.require(13)) { + Point size = new Point(); + display.getSize(size); + return size.x; + } else { + return display.getWidth(); + } + } + + public static int getHeight() { + Display display = WindowManagerUtil.getDefaultDisplay(); + if (APILevel.require(13)) { + Point size = new Point(); + display.getSize(size); + return size.y; + } else { + return display.getHeight(); + } + } + + public static Rotation getRotation() { + if (APILevel.require(8)) + return Rotation.fromValue(WindowManagerUtil.getDefaultDisplay().getRotation()); + else + return Rotation.fromValue(WindowManagerUtil.getDefaultDisplay().getOrientation()); + } + + public static boolean isPortrait() { + return getHeight() >= getWidth(); + } + + public static boolean isLandscape() { + return getHeight() < getWidth(); + } + + public static int getStatusBarHeight() { + int resourceId = ResourcesUtil.getIdentifier("status_bar_height", "dimen", "android"); + return resourceId > 0 ? + ResourcesUtil.getDimensionPixelSize(resourceId) : + 0; + } + + public static int getToolbarHeight() { + return getActionBarHeight(); + } + + public static int getActionBarHeight() { + TypedValue tv = new TypedValue(); + return ThemeUtil.resolveAttribute(android.R.attr.actionBarSize, tv, true) ? + TypedValueUtil.complexToDimensionPixelSize(tv.data) : + 0; + } + + public static int getNavigationBarHeight() { + int resourceId = ResourcesUtil.getIdentifier("navigation_bar_height", "dimen", "android"); + return resourceId > 0 ? + ResourcesUtil.getDimensionPixelSize(resourceId) : + 0; + } +} diff --git a/utils/src/main/java/com/thefinestartist/utils/ui/Keyboard.java b/utils/src/main/java/com/thefinestartist/utils/ui/Keyboard.java new file mode 100644 index 00000000..ff224ccf --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/ui/Keyboard.java @@ -0,0 +1,9 @@ +package com.thefinestartist.utils.ui; + +/** + * Keyboard is abbreviation class of {@link KeyboardUtil}. + * + * @author Leonardo Taehwan Kim + */ +public class Keyboard extends KeyboardUtil { +} diff --git a/utils/src/main/java/com/thefinestartist/utils/ui/KeyboardUtil.java b/utils/src/main/java/com/thefinestartist/utils/ui/KeyboardUtil.java new file mode 100644 index 00000000..9ae42ccc --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/ui/KeyboardUtil.java @@ -0,0 +1,184 @@ +package com.thefinestartist.utils.ui; + +import android.annotation.TargetApi; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; + +import androidx.fragment.app.Fragment; + +import com.thefinestartist.Base; +import com.thefinestartist.converters.UnitConverter; +import com.thefinestartist.utils.etc.ThreadUtil; +import com.thefinestartist.utils.service.ServiceUtil; + +/** + * KeyboardUtil helps to show and hide keyboard conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class KeyboardUtil { + + public static int height = 0; + public static final String KEYBOARD_UTIL_PREF = "KEYBOARD_UTIL_PREF"; + public static final String KEYBOARD_HEIGHT = "KEYBOARD_HEIGHT"; + public static final int DEFAULT_KEYBOARD_HEIGHT = 200; + + /** + * Helps to show keyboard in {@link Activity#onCreate(Bundle)}, {@link Activity#onStart()}, + * {@link Activity#onResume()}, + * {@link MenuItem.OnActionExpandListener#onMenuItemActionExpand(MenuItem)}, + * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)} and etc + * This method guarantee to show keyboard every time. + */ + public static void show(final View view) { + if (view == null) + return; + + view.postDelayed(new Runnable() { + @Override + public void run() { + showInMainThread(view); + } + }, 200); + } + + /** + * Please note that this method does not guarantee to show keyboard every time. To guarantee + * to show keyboard, please use {@link #show(View)} instead. It doesn't have any delay, use + * this method when it is able to show keyboard immediately. EX) when user click a button to + * show keyboard + */ + public static void showImmediately(final View view) { + if (view == null) + return; + + if (ThreadUtil.isMain()) { + showInMainThread(view); + } else { + view.post(new Runnable() { + @Override + public void run() { + showInMainThread(view); + } + }); + } + } + + private static void showInMainThread(final View view) { + if (view == null) + return; + + view.requestFocus(); + ServiceUtil.getInputMethodManager().showSoftInput(view, InputMethodManager.SHOW_IMPLICIT); + } + + public static void hide(Fragment fragment) { + if (fragment == null || fragment.getActivity() == null) + return; + + hide(fragment.getActivity()); + } + + public static void hide(Fragment fragment, boolean clearFocus) { + if (fragment == null || fragment.getActivity() == null) + return; + + hide(fragment.getActivity()); + } + + public static void hide(Activity activity) { + hide(activity, true); + } + + public static void hide(Activity activity, boolean clearFocus) { + if (activity == null) + return; + + hide(activity.getCurrentFocus(), clearFocus); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void hide(android.app.Fragment fragment) { + hide(fragment, true); + } + + @TargetApi(Build.VERSION_CODES.HONEYCOMB) + public static void hide(android.app.Fragment fragment, boolean clearFocus) { + if (fragment == null || fragment.getActivity() == null) + return; + + hide(fragment.getActivity(), clearFocus); + } + + public static void hide(Dialog dialog) { + hide(dialog, true); + } + + public static void hide(Dialog dialog, boolean clearFocus) { + if (dialog == null) + return; + + hide(dialog.getCurrentFocus(), clearFocus); + } + + public static void hide(View view) { + hide(view, true); + } + + public static void hide(View view, boolean clearFocus) { + if (view == null) + return; + + if (clearFocus) { + view.clearFocus(); + } + + ServiceUtil.getInputMethodManager().hideSoftInputFromWindow(view.getWindowToken(), 0); + } + + public static int getHeight() { + if (height <= 0) + height = Base.getContext().getSharedPreferences(KEYBOARD_UTIL_PREF, Context.MODE_PRIVATE).getInt(KEYBOARD_HEIGHT, UnitConverter.dpToPx(DEFAULT_KEYBOARD_HEIGHT)); + + return height; + } + + public static void setHeight(int height) { + KeyboardUtil.height = height; + Base.getContext().getSharedPreferences(KEYBOARD_UTIL_PREF, Context.MODE_PRIVATE).edit().putInt(KEYBOARD_HEIGHT, height).apply(); + } + +// coordinatorLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { +// @Override +// public void onGlobalLayout() { +// Rect r = new Rect(); +// coordinatorLayout.getWindowVisibleDisplayFrame(r); +// if (ResourcesUtil.navigationBarHeight == -1) { +// ResourcesUtil.navigationBarHeight = coordinatorLayout.getRootView().getHeight() - r.height() - ResourcesUtil.statusBarHeight; +// } +// int usableHeight = coordinatorLayout.getRootView().getHeight() - ResourcesUtil.statusBarHeight - ResourcesUtil.navigationBarHeight; +// int keyboardHeight = usableHeight - r.height(); +// if (isKeyboardOpened) { +// if (keyboardHeight < 100) { +// onKeyboardChanged(usableHeight, keyboardHeight, false); +// isKeyboardOpened = false; +// } +// } else { +// if (keyboardHeight > 100) { +// onKeyboardChanged(usableHeight, keyboardHeight, true); +// isKeyboardOpened = true; +// } +// } +// } +// }); +} +//TODO: Support keyboard show and hide listener +//TODO: Keyboard height \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/utils/ui/ViewUtil.java b/utils/src/main/java/com/thefinestartist/utils/ui/ViewUtil.java new file mode 100644 index 00000000..38472793 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/utils/ui/ViewUtil.java @@ -0,0 +1,39 @@ +package com.thefinestartist.utils.ui; + +import android.graphics.drawable.Drawable; +import android.view.View; + +import androidx.annotation.DrawableRes; + +import com.thefinestartist.Base; +import com.thefinestartist.utils.etc.APILevel; + +/** + * ViewUtil helps to set background drawable conveniently. + * + * @author Leonardo Taehwan Kim + */ +public class ViewUtil { + + public static void setBackground(View view, Drawable drawable) { + if (view == null) + return; + + if (APILevel.require(16)) { + view.setBackground(drawable); + } else { + view.setBackgroundDrawable(drawable); + } + } + + public static void setBackground(View view, @DrawableRes int drawableRes) { + if (view == null) + return; + + if (APILevel.require(16)) { + view.setBackground(Base.getResources().getDrawable(drawableRes)); + } else { + view.setBackgroundDrawable(Base.getResources().getDrawable(drawableRes)); + } + } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/wip/AgeUtil.java b/utils/src/main/java/com/thefinestartist/wip/AgeUtil.java new file mode 100755 index 00000000..0856f9fe --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/AgeUtil.java @@ -0,0 +1,29 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist + */ +public class AgeUtil { + +// public static String getFromBirthDay(Date date) { +// if (date == null) +// return "?"; +// +// int year = Integer.parseInt((String) DateFormat.format("yyyy", date)); +// int month = Integer.parseInt((String) DateFormat.format("MM", date)); +// int day = Integer.parseInt((String) DateFormat.format("dd", date)); +// +// Calendar birthday = Calendar.getInstance(); +// birthday.set(year, month - 1, day); +// Calendar today = Calendar.getInstance(); +// +// +// int age = today.get(Calendar.YEAR) - birthday.get(Calendar.YEAR); +// +// if (today.get(Calendar.DAY_OF_YEAR) < birthday.get(Calendar.DAY_OF_YEAR)) { +// age--; +// } +// +// return String.format("%d", age); +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/AudioManagerUtil.java b/utils/src/main/java/com/thefinestartist/wip/AudioManagerUtil.java new file mode 100644 index 00000000..73c04d46 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/AudioManagerUtil.java @@ -0,0 +1,42 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/18/16. + */ +public class AudioManagerUtil { + +// public static void getMode() { +// AudioManager am = ServiceUtil.getAudioManager(); +// switch (am.getRingerMode()) { +// case AudioManager.RINGER_MODE_NORMAL: +// case AudioManager.RINGER_MODE_VIBRATE: +// case AudioManager.RINGER_MODE_SILENT: +// } +// } +// +// public static void getVolume() { +// AudioManager am = ServiceUtil.getAudioManager(); +// int voiceCall = am.getStreamVolume(AudioManager.STREAM_VOICE_CALL); +// int system = am.getStreamVolume(AudioManager.STREAM_SYSTEM); +// int ring = am.getStreamVolume(AudioManager.STREAM_RING); +// int music = am.getStreamVolume(AudioManager.STREAM_MUSIC); +// int alarm = am.getStreamVolume(AudioManager.STREAM_ALARM); +// int notification = am.getStreamVolume(AudioManager.STREAM_NOTIFICATION); +// } +// +// public static void setVolume() { +// AudioManager am = ServiceUtil.getAudioManager(); +// int currentVolumn = am.getStreamVolume(AudioManager.STREAM_SYSTEM); +// switch (am.getRingerMode()) { +// case AudioManager.RINGER_MODE_SILENT: +// am.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); +// break; +// case AudioManager.RINGER_MODE_VIBRATE: +// am.setStreamVolume(AudioManager.STREAM_MUSIC, 0, 0); +// break; +// case AudioManager.RINGER_MODE_NORMAL: +// am.setStreamVolume(AudioManager.STREAM_MUSIC, currentVolumn, 0); +// break; +// } +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/AwakeUtil.java b/utils/src/main/java/com/thefinestartist/wip/AwakeUtil.java new file mode 100644 index 00000000..dc78dbb5 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/AwakeUtil.java @@ -0,0 +1,52 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/10/16. + */ +public class AwakeUtil { + +// private static Map wakeLocks = new HashMap<>(); +// private static final String TAG = "AwakeUtil"; +// +// public static void awakeCPU() { +// awakeCPU(TAG); +// } +// +// public static void awakeCPU(@NonNull String tag) { +// PowerManager.WakeLock wakeLock = ServiceUtil.getPowerManager().newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, tag); +// wakeLock.acquire(); +// +// if (wakeLocks.get(tag) != null) +// releaseCPU(tag); +// wakeLocks.put(tag, wakeLock); +// } +// +// public static void releaseCPU() { +// releaseCPU(TAG); +// } +// +// public static void releaseCPU(@NonNull String tag) { +// if (wakeLocks.get(tag) == null) +// return; +// +// wakeLocks.get(tag).release(); +// wakeLocks.remove(tag); +// } +// +// public static void awakeScreen(@NonNull Activity activity) { +// activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); +// } +// +// public static void releaseScreen(@NonNull Activity activity) { +// activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON); +// } +// +// public void turnOnScreen() { +// ServiceUtil.getPowerManager().newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP, "tag").acquire(); +// } +// +// public void turnOffScreen() { +// if (APILevel.require(21)) +// ServiceUtil.getPowerManager().newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, "tag").acquire(); +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/BitmapUtil.java b/utils/src/main/java/com/thefinestartist/wip/BitmapUtil.java new file mode 100644 index 00000000..985003ed --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/BitmapUtil.java @@ -0,0 +1,213 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/9/16. + */ +public class BitmapUtil { + +// private final static String TAG = ImageUtils.class.getSimpleName(); +// +// private static final String ERROR_URI_NULL = "Uri cannot be null"; +// +// /*** +// * Scales the image depending upon the display density of the device. Maintains image aspect +// * ratio. +// * +// * When dealing with the bitmaps of bigger size, this method must be called from a non-UI +// * thread. +// * ***/ +// public static Bitmap scaleDownBitmap(Context ctx, Bitmap source, int newHeight) { +// final float densityMultiplier = Utils.getDensityMultiplier(ctx); +// +// // Log.v( TAG, "#scaleDownBitmap Original w: " + source.getWidth() + " h: " + +// // source.getHeight() ); +// +// int h = (int) (newHeight * densityMultiplier); +// int w = (int) (h * source.getWidth() / ((double) source.getHeight())); +// +// // Log.v( TAG, "#scaleDownBitmap Computed w: " + w + " h: " + h ); +// +// Bitmap photo = Bitmap.createScaledBitmap(source, w, h, true); +// +// // Log.v( TAG, "#scaleDownBitmap Final w: " + w + " h: " + h ); +// +// return photo; +// } +// +// /*** +// * Scales the image independently of the screen density of the device. Maintains image aspect +// * ratio. +// * +// * When dealing with the bitmaps of bigger size, this method must be called from a non-UI +// * thread. +// * ***/ +// public static Bitmap scaleBitmap(Context ctx, Bitmap source, int newHeight) { +// +// // Log.v( TAG, "#scaleDownBitmap Original w: " + source.getWidth() + " h: " + +// // source.getHeight() ); +// +// int w = (int) (newHeight * source.getWidth() / ((double) source.getHeight())); +// +// // Log.v( TAG, "#scaleDownBitmap Computed w: " + w + " h: " + newHeight ); +// +// Bitmap photo = Bitmap.createScaledBitmap(source, w, newHeight, true); +// +// // Log.v( TAG, "#scaleDownBitmap Final w: " + w + " h: " + newHeight ); +// +// return photo; +// } +// +// /*** +// * Scales the image independently of the screen density of the device. Maintains image aspect +// * ratio. +// * +// * @param uri +// * Uri of the source bitmap +// ****/ +// public static Bitmap scaleDownBitmap(Context ctx, Uri uri, int newHeight) throws FileNotFoundException, IOException { +// Bitmap original = Media.getBitmap(ctx.getContentResolver(), uri); +// return scaleBitmap(ctx, original, newHeight); +// } +// +// /*** +// * Scales the image independently of the screen density of the device. Maintains image aspect +// * ratio. +// * +// * @param uri +// * Uri of the source bitmap +// ****/ +// public static Uri scaleDownBitmapForUri(Context ctx, Uri uri, int newHeight) throws FileNotFoundException, IOException { +// +// if (uri == null) +// throw new NullPointerException(ERROR_URI_NULL); +// +// if (!isMediaContentUri(uri)) +// return null; +// +// Bitmap original = Media.getBitmap(ctx.getContentResolver(), uri); +// Bitmap bmp = scaleBitmap(ctx, original, newHeight); +// +// Uri destUri = null; +// String uriStr = Utils.writeImageToMedia(ctx, bmp, "", ""); +// +// if (uriStr != null) { +// destUri = Uri.parse(uriStr); +// } +// +// return destUri; +// } +// +// /*** +// * Gets the orientation of the image pointed to by the parameter uri +// * +// * @return Image orientation value corresponding to ExifInterface.ORIENTATION_*
+// * Returns -1 if the row for the {@link android.net.Uri} is not found. +// ****/ +// public static int getOrientation(Context context, Uri uri) { +// +// int invalidOrientation = -1; +// if (uri == null) { +// throw new NullPointerException(ERROR_URI_NULL); +// } +// +// if (!isMediaContentUri(uri)) { +// return invalidOrientation; +// } +// +// String filePath = Utils.getImagePathForUri(context, uri); +// ExifInterface exif = null; +// +// try { +// exif = new ExifInterface(filePath); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// +// int orientation = invalidOrientation; +// if (exif != null) { +// orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, invalidOrientation); +// } +// +// return orientation; +// } +// +// /*** +// * @deprecated Use {@link MediaUtils#isMediaContentUri(android.net.Uri)} instead.
+// * Checks if the parameter {@link android.net.Uri} is a +// * {@link android.provider.MediaStore.Audio.Media} content uri. +// ****/ +// public static boolean isMediaContentUri(Uri uri) { +// if (!uri.toString().contains("content://media/")) { +// Log.w(TAG, "#isContentUri The uri is not a media content uri"); +// return false; +// } else { +// return true; +// } +// } +// +// /*** +// * Rotate the image at the specified uri. For the rotation of the image the +// * {@link android.media.ExifInterface} data in the image will be used. +// * +// * @param uri +// * Uri of the image to be rotated. +// ****/ +// public static Uri rotateImage(Context context, Uri uri) throws FileNotFoundException, IOException { +// // rotate the image +// if (uri == null) { +// throw new NullPointerException(ERROR_URI_NULL); +// } +// +// if (!isMediaContentUri(uri)) { +// return null; +// } +// +// int invalidOrientation = -1; +// byte[] data = Utils.getMediaData(context, uri); +// +// int orientation = getOrientation(context, uri); +// +// Uri newUri = null; +// +// try { +// +// Log.d(TAG, "#rotateImage Exif orientation: " + orientation); +// +// if (orientation != invalidOrientation) { +// Matrix matrix = new Matrix(); +// +// switch (orientation) { +// case ExifInterface.ORIENTATION_ROTATE_90: +// matrix.postRotate(90); +// break; +// case ExifInterface.ORIENTATION_ROTATE_180: +// matrix.postRotate(180); +// break; +// case ExifInterface.ORIENTATION_ROTATE_270: +// matrix.postRotate(270); +// break; +// } +// +// // set some options so the memory is manager properly +// BitmapFactory.Options options = new BitmapFactory.Options(); +// // options.inPreferredConfig = Bitmap.Config.RGB_565; // try to enable this if +// // OutOfMem issue still persists +// options.inPurgeable = true; +// options.inInputShareable = true; +// +// Bitmap original = BitmapFactory.decodeByteArray(data, 0, data.length, options); +// Bitmap rotatedBitmap = Bitmap.createBitmap(original, 0, 0, original.getWidth(), original.getHeight(), matrix, true); // rotating +// // bitmap +// String newUrl = Media.insertImage(((Activity) context).getContentResolver(), rotatedBitmap, "", ""); +// +// if (newUrl != null) { +// newUri = Uri.parse(newUrl); +// } +// } +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// return newUri; +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/DateUtil.java b/utils/src/main/java/com/thefinestartist/wip/DateUtil.java new file mode 100644 index 00000000..6c237bb9 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/DateUtil.java @@ -0,0 +1,87 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 1/26/16. + */ +public class DateUtil { + +// public static String SERVER_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.'000Z'"; +// public static String POST_FORMAT = "yy/MM/dd HH:mm"; +// public static String CLICKER_FORMAT = "yyyy/MM/dd kk:mm"; +// public static String DATE_FORMAT = "yyyy/MM/dd"; +// public static String TIME_FORMAT = "HH:mm"; +// private static SimpleDateFormat server_format = new SimpleDateFormat(SERVER_FORMAT); +// private static SimpleDateFormat post_format = new SimpleDateFormat(POST_FORMAT); +// private static SimpleDateFormat date_format = new SimpleDateFormat(DATE_FORMAT); +// private static SimpleDateFormat time_format = new SimpleDateFormat(TIME_FORMAT); +// +// public static synchronized long getCurrentGMTTimeMillis() { +// final Date currentTime = new Date(); +// final SimpleDateFormat sdf = new SimpleDateFormat(SERVER_FORMAT); +// sdf.setTimeZone(TimeZone.getTimeZone("GMT")); +// String time = sdf.format(currentTime); +// return getTime(time); +// } +// +// public static String getCurrentTimeString() { +// return (String) DateFormat.format(CLICKER_FORMAT, System.currentTimeMillis()); +// } +// +// public static long getTime(String timeStr) { +// Date date; +// try { +// server_format.setTimeZone(TimeZone.getTimeZone("GMT")); +// date = server_format.parse(timeStr); +// return date.getTime(); +// } catch (ParseException e) { +// return 0; +// } +// } +// +// public static Date getDate(String timeStr) { +// Date date; +// try { +// server_format.setTimeZone(TimeZone.getTimeZone("GMT")); +// date = server_format.parse(timeStr); +// return date; +// } catch (ParseException e) { +// return null; +// } +// } +// +// public static String getPostFormatString(String timeStr) { +// Date date; +// try { +// server_format.setTimeZone(TimeZone.getTimeZone("GMT")); +// post_format.setTimeZone(TimeZone.getDefault()); +// date = server_format.parse(timeStr); +// return post_format.format(date); +// } catch (ParseException e) { +// return null; +// } +// } +// +// public static String getDateFormatString(String timeStr) { +// Date date; +// try { +// server_format.setTimeZone(TimeZone.getTimeZone("GMT")); +// date_format.setTimeZone(TimeZone.getDefault()); +// date = server_format.parse(timeStr); +// return date_format.format(date); +// } catch (ParseException e) { +// return null; +// } +// } +// +// public static String getTimeFormatString(String timeStr) { +// Date date; +// try { +// server_format.setTimeZone(TimeZone.getTimeZone("GMT")); +// time_format.setTimeZone(TimeZone.getDefault()); +// date = server_format.parse(timeStr); +// return time_format.format(date); +// } catch (ParseException e) { +// return null; +// } +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/EmailUtil.java b/utils/src/main/java/com/thefinestartist/wip/EmailUtil.java new file mode 100755 index 00000000..bc36b3c0 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/EmailUtil.java @@ -0,0 +1,20 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist + */ +public class EmailUtil { + +// public static void sendSupportMail(String url) { +// Intent i = new Intent(Intent.ACTION_SEND); +// i.setType("message/rfc822"); +// i.putExtra(Intent.EXTRA_EMAIL, new String[]{url}); +// i.putExtra(Intent.EXTRA_SUBJECT, "[FEEDBACK] Android App (" + Build.VERSION.CODENAME + ")"); +// i.putExtra(Intent.EXTRA_TEXT, ""); +// try { +// Base.getContext().startActivity(Intent.createChooser(i, "Send Feedback")); +// } catch (android.content.ActivityNotFoundException e) { +// e.printStackTrace(); +// } +// } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/wip/FileUtil.java b/utils/src/main/java/com/thefinestartist/wip/FileUtil.java new file mode 100644 index 00000000..7b369bbf --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/FileUtil.java @@ -0,0 +1,22 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/10/16. + */ +public class FileUtil { + +// public static String readJsonFile(String filePath) { +// try { +// String json = null; +// InputStream is = Base.getContext().getAssets().open(filePath); +// int size = is.available(); +// byte[] buffer = new byte[size]; +// is.read(buffer); +// is.close(); +// return new String(buffer, "UTF-8"); +// } catch (IOException e) { +// e.printStackTrace(); +// return null; +// } +// } +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/wip/LanguageDetector.java b/utils/src/main/java/com/thefinestartist/wip/LanguageDetector.java new file mode 100644 index 00000000..c68ee6ee --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/LanguageDetector.java @@ -0,0 +1,53 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2014. 9. 2.. + */ +public class LanguageDetector { + +// public static boolean isEnglish(CharSequence charSequence) { +// boolean isEnglish = true; +// for (char c : charSequence.toString().toCharArray()) { +// if (Character.UnicodeBlock.of(c) != Character.UnicodeBlock.BASIC_LATIN) { +// isEnglish = false; +// break; +// } +// } +// +// return isEnglish; +// } +// +// public static boolean hasKorean(CharSequence charSequence) { +// boolean hasKorean = false; +// for (char c : charSequence.toString().toCharArray()) { +// if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HANGUL_JAMO +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HANGUL_COMPATIBILITY_JAMO +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HANGUL_SYLLABLES) { +// hasKorean = true; +// break; +// } +// } +// +// return hasKorean; +// } +// +// public static boolean hasJapanese(CharSequence charSequence) { +// boolean hasJapanese = false; +// for (char c : charSequence.toString().toCharArray()) { +// if (Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HIRAGANA +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.KATAKANA +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.HALFWIDTH_AND_FULLWIDTH_FORMS +// || Character.UnicodeBlock.of(c) == Character.UnicodeBlock.CJK_SYMBOLS_AND_PUNCTUATION) { +// hasJapanese = true; +// break; +// } +// } +// +// return hasJapanese; +// } +// +// public enum Language {Korean, Japanese, English} + +} \ No newline at end of file diff --git a/utils/src/main/java/com/thefinestartist/wip/NetworkUtil.java b/utils/src/main/java/com/thefinestartist/wip/NetworkUtil.java new file mode 100644 index 00000000..5f72f5f0 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/NetworkUtil.java @@ -0,0 +1,80 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/21/16. + */ +public class NetworkUtil { + +// public static NetworkInfo getNetworkInfo() { +// return ServiceUtil.getConnectivityManager().getActiveNetworkInfo(); +// } +// +// public static boolean isConnected() { +// NetworkInfo info = getNetworkInfo(); +// return (info != null && info.isConnected()); +// } +// +// public static boolean isConnectedWifi() { +// NetworkInfo info = getNetworkInfo(); +// return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_WIFI); +// } +// +// public static boolean isConnectedMobile() { +// NetworkInfo info = getNetworkInfo(); +// return (info != null && info.isConnected() && info.getType() == ConnectivityManager.TYPE_MOBILE); +// } +// +// public static boolean isConnectedFast() { +// NetworkInfo info = getNetworkInfo(); +// return (info != null && info.isConnected() && isConnectionFast(info.getType(), info.getSubtype())); +// } +// +// public static boolean isConnectionFast(int type, int subType) { +// if (type == ConnectivityManager.TYPE_WIFI) { +// return true; +// } else if (type == ConnectivityManager.TYPE_MOBILE) { +// switch (subType) { +// case TelephonyManager.NETWORK_TYPE_1xRTT: +// return false; // ~ 50-100 kbps +// case TelephonyManager.NETWORK_TYPE_CDMA: +// return false; // ~ 14-64 kbps +// case TelephonyManager.NETWORK_TYPE_EDGE: +// return false; // ~ 50-100 kbps +// case TelephonyManager.NETWORK_TYPE_EVDO_0: +// return true; // ~ 400-1000 kbps +// case TelephonyManager.NETWORK_TYPE_EVDO_A: +// return true; // ~ 600-1400 kbps +// case TelephonyManager.NETWORK_TYPE_GPRS: +// return false; // ~ 100 kbps +// case TelephonyManager.NETWORK_TYPE_HSDPA: +// return true; // ~ 2-14 Mbps +// case TelephonyManager.NETWORK_TYPE_HSPA: +// return true; // ~ 700-1700 kbps +// case TelephonyManager.NETWORK_TYPE_HSUPA: +// return true; // ~ 1-23 Mbps +// case TelephonyManager.NETWORK_TYPE_UMTS: +// return true; // ~ 400-7000 kbps +// /* +// * Above API level 7, make sure to set android:targetSdkVersion +// * to appropriate level to use these +// */ +// case TelephonyManager.NETWORK_TYPE_EHRPD: // API level 11 +// return true; // ~ 1-2 Mbps +// case TelephonyManager.NETWORK_TYPE_EVDO_B: // API level 9 +// return true; // ~ 5 Mbps +// case TelephonyManager.NETWORK_TYPE_HSPAP: // API level 13 +// return true; // ~ 10-20 Mbps +// case TelephonyManager.NETWORK_TYPE_IDEN: // API level 8 +// return false; // ~25 kbps +// case TelephonyManager.NETWORK_TYPE_LTE: // API level 11 +// return true; // ~ 10+ Mbps +// // Unknown +// case TelephonyManager.NETWORK_TYPE_UNKNOWN: +// default: +// return false; +// } +// } else { +// return false; +// } +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/PhotoUtil.java b/utils/src/main/java/com/thefinestartist/wip/PhotoUtil.java new file mode 100755 index 00000000..2a47b3dd --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/PhotoUtil.java @@ -0,0 +1,126 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist + */ +public class PhotoUtil { + +// public static final int IMAGE_MAX_WIDTH = 800; +// +// public static SoftReference convertToByte(String path) { +// if (path == null) +// return new SoftReference<>(new byte[0]); +// +// BitmapFactory.Options options = new BitmapFactory.Options(); +// int inSampleSize = getInSampleSize(path, IMAGE_MAX_WIDTH * 2); +// options.inSampleSize = inSampleSize; +// Bitmap bitmap = BitmapFactory.decodeFile(path, options); +// +// if (bitmap == null) { +// inSampleSize = getInSampleSize(path, IMAGE_MAX_WIDTH * 2); +// options.inSampleSize = inSampleSize; +// bitmap = BitmapFactory.decodeFile(path, options); +// } +// +// if (bitmap == null) +// return new SoftReference<>(new byte[0]); +// +// // Scale the bitmap +// if (inSampleSize > 1) { +// float height; +// switch (getRotation(path)) { +// case ExifInterface.ORIENTATION_ROTATE_90: +// case ExifInterface.ORIENTATION_ROTATE_270: +// height = ((float) bitmap.getWidth()) * ((float) IMAGE_MAX_WIDTH) / ((float) bitmap.getHeight()); +// bitmap = Bitmap.createScaledBitmap(bitmap, (int) height, IMAGE_MAX_WIDTH, true); +// break; +// case ExifInterface.ORIENTATION_NORMAL: +// case ExifInterface.ORIENTATION_ROTATE_180: +// default: +// height = ((float) bitmap.getHeight()) * ((float) IMAGE_MAX_WIDTH) / ((float) bitmap.getWidth()); +// bitmap = Bitmap.createScaledBitmap(bitmap, IMAGE_MAX_WIDTH, (int) height, true); +// break; +// } +// } +// +// // Rotate the bitmap +// switch (getRotation(path)) { +// case ExifInterface.ORIENTATION_ROTATE_90: +// bitmap = rotateBitmap(bitmap, 90); +// break; +// case ExifInterface.ORIENTATION_ROTATE_270: +// bitmap = rotateBitmap(bitmap, 270); +// break; +// } +// +// byte[] bytes = null; +// try { +// ByteArrayOutputStream stream = new ByteArrayOutputStream(); +// bitmap.compress(Bitmap.CompressFormat.JPEG, 80, stream); +// bytes = stream.toByteArray(); +// stream.close(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// return new SoftReference<>(bytes); +// } +// +// public static Bitmap rotateBitmap(Bitmap bitmap, float angle) { +// Matrix matrix = new Matrix(); +// matrix.postRotate(angle); +// return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true); +// } +// +// +// public static int getInSampleSize(String path, int maxWidth) { +// SoftReference bitmap = getBitmap(path); +// int inSampleSize = 1; +// float currentWidth = 0; +// switch (getRotation(path)) { +// case ExifInterface.ORIENTATION_ROTATE_90: +// case ExifInterface.ORIENTATION_ROTATE_270: +// if (bitmap != null && bitmap.get() != null) +// currentWidth = bitmap.get().getHeight(); +// break; +// case ExifInterface.ORIENTATION_NORMAL: +// case ExifInterface.ORIENTATION_ROTATE_180: +// default: +// if (bitmap != null && bitmap.get() != null) +// currentWidth = bitmap.get().getWidth(); +// break; +// } +// +// currentWidth /= (float) maxWidth; +// while (currentWidth > 1) { +// inSampleSize *= 2; +// currentWidth /= 2; +// } +// +// return inSampleSize; +// } +// +// public static SoftReference getBitmap(String path) { +// SoftReference bitmap = null; +// +// try { +// bitmap = new SoftReference<>(BitmapFactory.decodeStream(new FileInputStream(new File(path)))); +// } catch (FileNotFoundException e) { +// e.printStackTrace(); +// } +// +// return bitmap; +// } +// +// public static int getRotation(String path) { +// int rotate = ExifInterface.ORIENTATION_NORMAL; +// try { +// File imageFile = new File(path); +// ExifInterface exif = new ExifInterface(imageFile.getAbsolutePath()); +// rotate = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// return rotate; +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/RippleUtil.java b/utils/src/main/java/com/thefinestartist/wip/RippleUtil.java new file mode 100644 index 00000000..02669da0 --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/RippleUtil.java @@ -0,0 +1,18 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/21/16. + */ +public class RippleUtil { + +// public static void forceEvent(View view, float x, float y) { +// Drawable background = view.getBackground(); +// if (APILevel.require(21)) { +// if (background instanceof RippleDrawable) { +// RippleDrawable ripple = (RippleDrawable) background; +// ripple.setHotspot(x, y); +// ripple.setVisible(true, true); +// } +// } +// } +} diff --git a/utils/src/main/java/com/thefinestartist/wip/Validator.java b/utils/src/main/java/com/thefinestartist/wip/Validator.java new file mode 100644 index 00000000..428c405e --- /dev/null +++ b/utils/src/main/java/com/thefinestartist/wip/Validator.java @@ -0,0 +1,48 @@ +package com.thefinestartist.wip; + +/** + * Created by TheFinestArtist on 2/18/16. + */ +public class Validator { + +// public static final String SPECIAL_CHARS = "\\p{Cntrl}\\(\\)<>@,;:'\\\\\\\"\\.\\[\\]"; +// public static final String VALID_CHARS = "[^\\s" + SPECIAL_CHARS + "]"; +// public static final String QUOTED_USER = "(\"[^\"]*\")"; +// public static final String WORD = "((" + VALID_CHARS + "|')+|" + QUOTED_USER + ")"; +// +// public static final String EMAIL_REGEX = "^\\s*?(.+)@(.+?)\\s*$"; +// public static final String IP_DOMAIN_REGEX = "^\\[(.*)\\]$"; +// public static final String USER_REGEX = "^\\s*" + WORD + "(\\." + WORD + ")*$"; +// +// public static final Pattern EMAIL_PATTERN = Pattern.compile(EMAIL_REGEX); +// public static final Pattern IP_DOMAIN_PATTERN = Pattern.compile(IP_DOMAIN_REGEX); +// public static final Pattern USER_PATTERN = Pattern.compile(USER_REGEX); +// +// +// // Regular expression strings for hostnames (derived from RFC2396 and RFC 1123) +// +// // RFC2396: domainlabel = alphanum | alphanum *( alphanum | "-" ) alphanum +// // Max 63 characters +// public static final String DOMAIN_LABEL_REGEX = "\\p{Alnum}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; +// +// // RFC2396 toplabel = alpha | alpha *( alphanum | "-" ) alphanum +// // Max 63 characters +// public static final String TOP_LABEL_REGEX = "\\p{Alpha}(?>[\\p{Alnum}-]{0,61}\\p{Alnum})?"; +// +// // RFC2396 hostname = *( domainlabel "." ) toplabel [ "." ] +// // Note that the regex currently requires both a domain label and a top level label, whereas +// // the RFC does not. This is because the regex is used to detect if a TLD is present. +// // If the match fails, input is checked against DOMAIN_LABEL_REGEX (hostnameRegex) +// // RFC1123 sec 2.1 allows hostnames to start with a digit +// public static final String DOMAIN_NAME_REGEX = "^(?:" + DOMAIN_LABEL_REGEX + "\\.)+" + "(" + TOP_LABEL_REGEX + ")\\.?$"; +// +// +// public static boolean isEmail(String email) { +// String emailPattern = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; +// Pattern pattern = Pattern.compile(emailPattern); +// Matcher matcher = pattern.matcher(email); +// return matcher.matches(); +// } +} +// https://github.com/throrin19/Android-Validator/tree/master/library/src/com/throrinstudio/android/common/libs/validator/validator +// https://github.com/ragunathjawahar/android-saripaar/tree/master/saripaar/src/main/java/commons/validator/routines \ No newline at end of file diff --git a/utils/src/main/res/anim/accelerate_cubic.xml b/utils/src/main/res/anim/accelerate_cubic.xml new file mode 100644 index 00000000..0e5bc093 --- /dev/null +++ b/utils/src/main/res/anim/accelerate_cubic.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/utils/src/main/res/anim/accelerate_quart.xml b/utils/src/main/res/anim/accelerate_quart.xml new file mode 100644 index 00000000..a7bbc3ab --- /dev/null +++ b/utils/src/main/res/anim/accelerate_quart.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/utils/src/main/res/anim/accelerate_quint.xml b/utils/src/main/res/anim/accelerate_quint.xml new file mode 100644 index 00000000..2f712c2f --- /dev/null +++ b/utils/src/main/res/anim/accelerate_quint.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/utils/src/main/res/anim/activity_close_enter.xml b/utils/src/main/res/anim/activity_close_enter.xml new file mode 100644 index 00000000..61e042c7 --- /dev/null +++ b/utils/src/main/res/anim/activity_close_enter.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/activity_close_exit.xml b/utils/src/main/res/anim/activity_close_exit.xml new file mode 100644 index 00000000..2f4f3476 --- /dev/null +++ b/utils/src/main/res/anim/activity_close_exit.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/activity_open_enter.xml b/utils/src/main/res/anim/activity_open_enter.xml new file mode 100644 index 00000000..10a8cbb5 --- /dev/null +++ b/utils/src/main/res/anim/activity_open_enter.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/activity_open_exit.xml b/utils/src/main/res/anim/activity_open_exit.xml new file mode 100644 index 00000000..c9e306a3 --- /dev/null +++ b/utils/src/main/res/anim/activity_open_exit.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/decelerate_cubic.xml b/utils/src/main/res/anim/decelerate_cubic.xml new file mode 100644 index 00000000..58432b6a --- /dev/null +++ b/utils/src/main/res/anim/decelerate_cubic.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/utils/src/main/res/anim/decelerate_quart.xml b/utils/src/main/res/anim/decelerate_quart.xml new file mode 100644 index 00000000..a3208eb5 --- /dev/null +++ b/utils/src/main/res/anim/decelerate_quart.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/utils/src/main/res/anim/decelerate_quint.xml b/utils/src/main/res/anim/decelerate_quint.xml new file mode 100644 index 00000000..45b308ba --- /dev/null +++ b/utils/src/main/res/anim/decelerate_quint.xml @@ -0,0 +1,4 @@ + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fade_in.xml b/utils/src/main/res/anim/fade_in.xml new file mode 100644 index 00000000..2b50377e --- /dev/null +++ b/utils/src/main/res/anim/fade_in.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fade_in_fast.xml b/utils/src/main/res/anim/fade_in_fast.xml new file mode 100644 index 00000000..980157e7 --- /dev/null +++ b/utils/src/main/res/anim/fade_in_fast.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fade_out.xml b/utils/src/main/res/anim/fade_out.xml new file mode 100644 index 00000000..6fc7e71b --- /dev/null +++ b/utils/src/main/res/anim/fade_out.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fade_out_fast.xml b/utils/src/main/res/anim/fade_out_fast.xml new file mode 100644 index 00000000..fc1e1afc --- /dev/null +++ b/utils/src/main/res/anim/fade_out_fast.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fade_out_slow.xml b/utils/src/main/res/anim/fade_out_slow.xml new file mode 100644 index 00000000..ae778370 --- /dev/null +++ b/utils/src/main/res/anim/fade_out_slow.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fragment_close_exit.xml b/utils/src/main/res/anim/fragment_close_exit.xml new file mode 100644 index 00000000..8edf6a1d --- /dev/null +++ b/utils/src/main/res/anim/fragment_close_exit.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fragment_close_exit_reverse.xml b/utils/src/main/res/anim/fragment_close_exit_reverse.xml new file mode 100644 index 00000000..4bcdcfa1 --- /dev/null +++ b/utils/src/main/res/anim/fragment_close_exit_reverse.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fragment_open_enter.xml b/utils/src/main/res/anim/fragment_open_enter.xml new file mode 100644 index 00000000..1c715722 --- /dev/null +++ b/utils/src/main/res/anim/fragment_open_enter.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/fragment_open_enter_reverse.xml b/utils/src/main/res/anim/fragment_open_enter_reverse.xml new file mode 100644 index 00000000..beb6167c --- /dev/null +++ b/utils/src/main/res/anim/fragment_open_enter_reverse.xml @@ -0,0 +1,22 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/modal_activity_close_enter.xml b/utils/src/main/res/anim/modal_activity_close_enter.xml new file mode 100644 index 00000000..61e042c7 --- /dev/null +++ b/utils/src/main/res/anim/modal_activity_close_enter.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/modal_activity_close_exit.xml b/utils/src/main/res/anim/modal_activity_close_exit.xml new file mode 100644 index 00000000..099e4a3a --- /dev/null +++ b/utils/src/main/res/anim/modal_activity_close_exit.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/modal_activity_open_enter.xml b/utils/src/main/res/anim/modal_activity_open_enter.xml new file mode 100644 index 00000000..7e8c40cd --- /dev/null +++ b/utils/src/main/res/anim/modal_activity_open_enter.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/modal_activity_open_exit.xml b/utils/src/main/res/anim/modal_activity_open_exit.xml new file mode 100644 index 00000000..c9e306a3 --- /dev/null +++ b/utils/src/main/res/anim/modal_activity_open_exit.xml @@ -0,0 +1,23 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/no_anim.xml b/utils/src/main/res/anim/no_anim.xml new file mode 100644 index 00000000..64fb0f17 --- /dev/null +++ b/utils/src/main/res/anim/no_anim.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/splash_out.xml b/utils/src/main/res/anim/splash_out.xml new file mode 100644 index 00000000..25829dca --- /dev/null +++ b/utils/src/main/res/anim/splash_out.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_blink.xml b/utils/src/main/res/anim/view_blink.xml new file mode 100644 index 00000000..e0dc1431 --- /dev/null +++ b/utils/src/main/res/anim/view_blink.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_bounce.xml b/utils/src/main/res/anim/view_bounce.xml new file mode 100644 index 00000000..60ef9091 --- /dev/null +++ b/utils/src/main/res/anim/view_bounce.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_fade_in.xml b/utils/src/main/res/anim/view_fade_in.xml new file mode 100644 index 00000000..4e7fb83d --- /dev/null +++ b/utils/src/main/res/anim/view_fade_in.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_fade_out.xml b/utils/src/main/res/anim/view_fade_out.xml new file mode 100644 index 00000000..4ef8fd15 --- /dev/null +++ b/utils/src/main/res/anim/view_fade_out.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_move.xml b/utils/src/main/res/anim/view_move.xml new file mode 100644 index 00000000..046ba1b2 --- /dev/null +++ b/utils/src/main/res/anim/view_move.xml @@ -0,0 +1,10 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_rotate.xml b/utils/src/main/res/anim/view_rotate.xml new file mode 100644 index 00000000..b446a9e8 --- /dev/null +++ b/utils/src/main/res/anim/view_rotate.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_slide_down.xml b/utils/src/main/res/anim/view_slide_down.xml new file mode 100644 index 00000000..00b5b532 --- /dev/null +++ b/utils/src/main/res/anim/view_slide_down.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_slide_up.xml b/utils/src/main/res/anim/view_slide_up.xml new file mode 100644 index 00000000..d62e1029 --- /dev/null +++ b/utils/src/main/res/anim/view_slide_up.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_zoom_in.xml b/utils/src/main/res/anim/view_zoom_in.xml new file mode 100644 index 00000000..20db7697 --- /dev/null +++ b/utils/src/main/res/anim/view_zoom_in.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/anim/view_zoom_out.xml b/utils/src/main/res/anim/view_zoom_out.xml new file mode 100644 index 00000000..812c1686 --- /dev/null +++ b/utils/src/main/res/anim/view_zoom_out.xml @@ -0,0 +1,14 @@ + + + + + + \ No newline at end of file diff --git a/utils/src/main/res/animator/card_flip_left_in.xml b/utils/src/main/res/animator/card_flip_left_in.xml new file mode 100644 index 00000000..5f7027cb --- /dev/null +++ b/utils/src/main/res/animator/card_flip_left_in.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/utils/src/main/res/animator/card_flip_left_out.xml b/utils/src/main/res/animator/card_flip_left_out.xml new file mode 100644 index 00000000..a6844291 --- /dev/null +++ b/utils/src/main/res/animator/card_flip_left_out.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/utils/src/main/res/animator/card_flip_right_in.xml b/utils/src/main/res/animator/card_flip_right_in.xml new file mode 100644 index 00000000..8089b930 --- /dev/null +++ b/utils/src/main/res/animator/card_flip_right_in.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + diff --git a/utils/src/main/res/animator/card_flip_right_out.xml b/utils/src/main/res/animator/card_flip_right_out.xml new file mode 100644 index 00000000..bdb958e2 --- /dev/null +++ b/utils/src/main/res/animator/card_flip_right_out.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + diff --git a/utils/src/main/res/values/colors_basic.xml b/utils/src/main/res/values/colors_basic.xml new file mode 100755 index 00000000..7554f196 --- /dev/null +++ b/utils/src/main/res/values/colors_basic.xml @@ -0,0 +1,145 @@ + + + + #FFFFFF + #FFFFF0 + #FFFFE0 + #FFFF00 + #FFFAFA + #FFFAF0 + #FFFACD + #FFF8DC + #FFF5EE + #FFF0F5 + #FFEFD5 + #FFEBCD + #FFE4E1 + #FFE4C4 + #FFE4B5 + #FFDEAD + #FFDAB9 + #FFD700 + #FFC0CB + #FFB6C1 + #FFA500 + #FFA07A + #FF8C00 + #FF7F50 + #FF69B4 + #FF6347 + #FF4500 + #FF1493 + #FF00FF + #FF00FF + #FF0000 + #FDF5E6 + #FAFAD2 + #FAF0E6 + #FAEBD7 + #FA8072 + #F8F8FF + #F5FFFA + #F5F5F5 + #F5F5DC + #F5DEB3 + #F4A460 + #F0FFFF + #F0FFF0 + #F0F8FF + #F0E68C + #F08080 + #EEE8AA + #EE82EE + #E9967A + #E6E6FA + #E0FFFF + #DEB887 + #DDA0DD + #DCDCDC + #DC143C + #DB7093 + #DAA520 + #DA70D6 + #D8BFD8 + #D3D3D3 + #D2B48C + #D2691E + #CD853F + #CD5C5C + #C71585 + #C0C0C0 + #BDB76B + #BC8F8F + #BA55D3 + #B8860B + #B22222 + #B0E0E6 + #B0C4DE + #AFEEEE + #ADFF2F + #ADD8E6 + #A9A9A9 + #A52A2A + #A0522D + #9ACD32 + #9932CC + #98FB98 + #9400D3 + #9370DB + #90EE90 + #8FBC8F + #8B4513 + #8B008B + #8B0000 + #8A2BE2 + #87CEFA + #87CEEB + #808080 + #808000 + #800080 + #800000 + #7FFFD4 + #7FFF00 + #7CFC00 + #7B68EE + #778899 + #708090 + #6B8E23 + #6A5ACD + #696969 + #66CDAA + #6495ED + #5F9EA0 + #556B2F + #4B0082 + #48D1CC + #483D8B + #4682B4 + #4169E1 + #40E0D0 + #3CB371 + #32CD32 + #2F4F4F + #2E8B57 + #228B22 + #20B2AA + #1E90FF + #191970 + #00FFFF + #00FFFF + #00FF7F + #00FF00 + #00FA9A + #00CED1 + #00BFFF + #008B8B + #008080 + #008000 + #006400 + #0000FF + #0000CD + #00008B + #000080 + #000000 + + \ No newline at end of file diff --git a/utils/src/main/res/values/colors_grey.xml b/utils/src/main/res/values/colors_grey.xml new file mode 100755 index 00000000..9286c7f5 --- /dev/null +++ b/utils/src/main/res/values/colors_grey.xml @@ -0,0 +1,315 @@ + + + + + + #0D0D0D + #1A1A1A + #262626 + #333333 + #404040 + #4D4D4D + #595959 + #666666 + #737373 + #808080 + #8C8C8C + #999999 + #A6A6A5 + #B3B3B3 + #BFBFBF + #CCCCCC + #D9D9D9 + #E6E6E6 + #F2F2F2 + + + #000000 + #010101 + #020202 + #030303 + #040404 + #050505 + #060606 + #070707 + #080808 + #090909 + #0a0a0a + #0b0b0b + #0c0c0c + #0d0d0d + #0e0e0e + #0f0f0f + + #101010 + #111111 + #121212 + #131313 + #141414 + #151515 + #161616 + #171717 + #181818 + #191919 + #1a1a1a + #1b1b1b + #1c1c1c + #1d1d1d + #1e1e1e + #1f1f1f + + #202020 + #212121 + #222222 + #232323 + #242424 + #252525 + #262626 + #272727 + #282828 + #292929 + #2a2a2a + #2b2b2b + #2c2c2c + #2d2d2d + #2e2e2e + #2f2f2f + + #303030 + #313131 + #323232 + #333333 + #343434 + #353535 + #363636 + #373737 + #383838 + #393939 + #3a3a3a + #3b3b3b + #3c3c3c + #3d3d3d + #3e3e3e + #3f3f3f + + #404040 + #414141 + #424242 + #434343 + #444444 + #454545 + #464646 + #474747 + #484848 + #494949 + #4a4a4a + #4b4b4b + #4c4c4c + #4d4d4d + #4e4e4e + #4f4f4f + + #505050 + #515151 + #525252 + #535353 + #545454 + #555555 + #565656 + #575757 + #585858 + #595959 + #5a5a5a + #5b5b5b + #5c5c5c + #5d5d5d + #5e5e5e + #5f5f5f + + #606060 + #616161 + #626262 + #636363 + #646464 + #656565 + #666666 + #676767 + #686868 + #696969 + #6a6a6a + #6b6b6b + #6c6c6c + #6d6d6d + #6e6e6e + #6f6f6f + + #707070 + #717171 + #727272 + #737373 + #747474 + #757575 + #767676 + #777777 + #787878 + #797979 + #7a7a7a + #7b7b7b + #7c7c7c + #7d7d7d + #7e7e7e + #7f7f7f + + #808080 + #818181 + #828282 + #838383 + #848484 + #858585 + #868686 + #878787 + #888888 + #898989 + #8a8a8a + #8b8b8b + #8c8c8c + #8d8d8d + #8e8e8e + #8f8f8f + + #909090 + #919191 + #929292 + #939393 + #949494 + #959595 + #969696 + #979797 + #989898 + #999999 + #9a9a9a + #9b9b9b + #9c9c9c + #9d9d9d + #9e9e9e + #9f9f9f + + #a0a0a0 + #a1a1a1 + #a2a2a2 + #a3a3a3 + #a4a4a4 + #a5a5a5 + #a6a6a6 + #a7a7a7 + #a8a8a8 + #a9a9a9 + #aaaaaa + #ababab + #acacac + #adadad + #aeaeae + #afafaf + + #b0b0b0 + #b1b1b1 + #b2b2b2 + #b3b3b3 + #b4b4b4 + #b5b5b5 + #b6b6b6 + #b7b7b7 + #b8b8b8 + #b9b9b9 + #bababa + #bbbbbb + #bcbcbc + #bdbdbd + #bebebe + #bfbfbf + + #c0c0c0 + #c1c1c1 + #c2c2c2 + #c3c3c3 + #c4c4c4 + #c5c5c5 + #c6c6c6 + #c7c7c7 + #c8c8c8 + #c9c9c9 + #cacaca + #cbcbcb + #cccccc + #cdcdcd + #cecece + #cfcfcf + + #d0d0d0 + #d1d1d1 + #d2d2d2 + #d3d3d3 + #d4d4d4 + #d5d5d5 + #d6d6d6 + #d7d7d7 + #d8d8d8 + #d9d9d9 + #dadada + #dbdbdb + #dcdcdc + #dddddd + #dedede + #dfdfdf + + #e0e0e0 + #e1e1e1 + #e2e2e2 + #e3e3e3 + #e4e4e4 + #e5e5e5 + #e6e6e6 + #e7e7e7 + #e8e8e8 + #e9e9e9 + #eaeaea + #ebebeb + #ececec + #ededed + #eeeeee + #efefef + + #f0f0f0 + #f1f1f1 + #f2f2f2 + #f3f3f3 + #f4f4f4 + #f5f5f5 + #f6f6f6 + #f7f7f7 + #f8f8f8 + #f9f9f9 + #fafafa + #fbfbfb + #fcfcfc + #fdfdfd + #fefefe + #ffffff + + \ No newline at end of file diff --git a/utils/src/main/res/values/colors_transparent.xml b/utils/src/main/res/values/colors_transparent.xml new file mode 100755 index 00000000..9ebb8c44 --- /dev/null +++ b/utils/src/main/res/values/colors_transparent.xml @@ -0,0 +1,91 @@ + + + + + + #11000000 + #22000000 + #33000000 + #44000000 + #55000000 + #66000000 + #77000000 + #88000000 + #99000000 + #aa000000 + #bb000000 + #cc000000 + #dd000000 + #ee000000 + #0D000000 + #1A000000 + #26000000 + #33000000 + #40000000 + #4D000000 + #59000000 + #66000000 + #73000000 + #80000000 + #8C000000 + #99000000 + #A6000000 + #B3000000 + #BF000000 + #CC000000 + #D9000000 + #E6000000 + #F2000000 + + + #11ffffff + #22ffffff + #33ffffff + #44ffffff + #55ffffff + #66ffffff + #77ffffff + #88ffffff + #99ffffff + #aaffffff + #bbffffff + #ccffffff + #ddffffff + #eeffffff + #0Dffffff + #1Affffff + #26ffffff + #33ffffff + #40ffffff + #4Dffffff + #59ffffff + #66ffffff + #73ffffff + #80ffffff + #8Cffffff + #99ffffff + #A6ffffff + #B3ffffff + #BFffffff + #CCffffff + #D9ffffff + #E6ffffff + #F2ffffff + + \ No newline at end of file diff --git a/utils/src/test/java/com/thefinestartist/helpers/ExampleUnitTest.java b/utils/src/test/java/com/thefinestartist/helpers/ExampleUnitTest.java new file mode 100644 index 00000000..99a00220 --- /dev/null +++ b/utils/src/test/java/com/thefinestartist/helpers/ExampleUnitTest.java @@ -0,0 +1,15 @@ +package com.thefinestartist.utils; + +import static org.junit.Assert.*; + +import org.junit.Test; + +/** + * To work on unit tests, switch the Test Artifact in the Build Variants view. + */ +public class ExampleUnitTest { + @Test + public void addition_isCorrect() throws Exception { + assertEquals(4, 2 + 2); + } +} \ No newline at end of file